diff --git a/examples/audio/raw_stream/main.go b/examples/audio/raw_stream/main.go index 6fd7afa..d8a9ddf 100644 --- a/examples/audio/raw_stream/main.go +++ b/examples/audio/raw_stream/main.go @@ -39,7 +39,7 @@ func main() { for !rl.WindowShouldClose() { // Refill audio stream if required - if rl.IsAudioBufferProcessed(stream) { + if rl.IsAudioStreamProcessed(stream) { numSamples := int32(0) if samplesLeft >= maxSamplesPerUpdate { numSamples = maxSamplesPerUpdate diff --git a/examples/core/2d_camera/main.go b/examples/core/2d_camera/main.go index e0aa19f..cce6fe5 100644 --- a/examples/core/2d_camera/main.go +++ b/examples/core/2d_camera/main.go @@ -38,19 +38,17 @@ func main() { camera := rl.Camera2D{} camera.Target = rl.NewVector2(float32(player.X+20), float32(player.Y+20)) - camera.Offset = rl.NewVector2(0, 0) + camera.Offset = rl.NewVector2(float32(screenWidth/2), float32(screenHeight/2)) camera.Rotation = 0.0 camera.Zoom = 1.0 - rl.SetTargetFPS(30) + rl.SetTargetFPS(60) for !rl.WindowShouldClose() { if rl.IsKeyDown(rl.KeyRight) { - player.X += 2 // Player movement - camera.Offset.X -= 2 // Camera displacement with player movement + player.X += 2 // Player movement } else if rl.IsKeyDown(rl.KeyLeft) { - player.X -= 2 // Player movement - camera.Offset.X += 2 // Camera displacement with player movement + player.X -= 2 // Player movement } // Camera target follows player diff --git a/examples/core/vr_simulator/main.go b/examples/core/vr_simulator/main.go index a0de239..2dab09e 100644 --- a/examples/core/vr_simulator/main.go +++ b/examples/core/vr_simulator/main.go @@ -5,17 +5,19 @@ import ( ) func main() { - hmd := rl.GetVrDeviceInfo(rl.HmdOculusRiftCv1) // Oculus Rift CV1 - rl.InitWindow(int32(hmd.HScreenSize), int32(hmd.VScreenSize), "raylib [core] example - vr simulator") + //hmd := rl.GetVrDeviceInfo(rl.HmdOculusRiftCv1) // Oculus Rift CV1 + //rl.InitWindow(int32(hmd.HScreenSize), int32(hmd.VScreenSize), "raylib [core] example - vr simulator") + rl.InitWindow(800, 450, "raylib [core] example - vr simulator") // NOTE: default device (simulator) - rl.InitVrSimulator(hmd) // Init VR device + //rl.InitVrSimulator(hmd) // Init VR device + rl.InitVrSimulator() // Init VR device camera := rl.Camera{} camera.Position = rl.NewVector3(5.0, 2.0, 5.0) // Camera position camera.Target = rl.NewVector3(0.0, 2.0, 0.0) // Camera looking at point camera.Up = rl.NewVector3(0.0, 1.0, 0.0) // Camera up vector (rotation towards target) - camera.Fovy = 60.0 // Camera field-of-view Y + camera.Fovy = 60.0 // Camera field-of-view Y cubePosition := rl.NewVector3(0.0, 0.0, 0.0) diff --git a/examples/models/cubicmap/main.go b/examples/models/cubicmap/main.go index 32e395c..a8e225c 100644 --- a/examples/models/cubicmap/main.go +++ b/examples/models/cubicmap/main.go @@ -1,6 +1,8 @@ package main import ( + "fmt" + "github.com/gen2brain/raylib-go/raylib" ) @@ -21,11 +23,14 @@ func main() { cubicmap := rl.LoadTextureFromImage(image) // Convert image to texture to display (VRAM) mesh := rl.GenMeshCubicmap(*image, rl.NewVector3(1.0, 1.0, 1.0)) + fmt.Printf("%+v\n", mesh) + model := rl.LoadModelFromMesh(mesh) // NOTE: By default each cube is mapped to one part of texture atlas - texture := rl.LoadTexture("cubicmap_atlas.png") // Load map texture - model.Material.Maps[rl.MapDiffuse].Texture = texture // Set map diffuse texture + texture := rl.LoadTexture("cubicmap_atlas.png") // Load map texture + model.Materials = make([]rl.Material, 1) + model.Materials[0].Maps[rl.MapDiffuse].Texture = texture // Set map diffuse texture mapPosition := rl.NewVector3(-16.0, 0.0, -8.0) // Set model position diff --git a/examples/models/heightmap/main.go b/examples/models/heightmap/main.go index b74bf42..44f57ef 100644 --- a/examples/models/heightmap/main.go +++ b/examples/models/heightmap/main.go @@ -1,6 +1,8 @@ package main import ( + //"fmt" + "github.com/gen2brain/raylib-go/raylib" ) @@ -20,10 +22,12 @@ func main() { texture := rl.LoadTextureFromImage(image) // Convert image to texture (VRAM) mesh := rl.GenMeshHeightmap(*image, rl.NewVector3(16, 8, 16)) // Generate heightmap mesh (RAM and VRAM) - model := rl.LoadModelFromMesh(mesh) // Load model from generated mesh - - model.Material.Maps[rl.MapDiffuse].Texture = texture // Set map diffuse texture - mapPosition := rl.NewVector3(-8.0, 0.0, -8.0) // Set model position + //fmt.Printf("%+v\n", mesh) + model := rl.LoadModelFromMesh(mesh) // Load model from generated mesh + //fmt.Printf("%+v\n", model) + model.Materials = make([]rl.Material, 1) + model.Materials[0].Maps[rl.MapDiffuse].Texture = texture // Set map diffuse texture + mapPosition := rl.NewVector3(-8.0, 0.0, -8.0) // Set model position rl.UnloadImage(image) // Unload heightmap image from RAM, already uploaded to VRAM diff --git a/examples/models/obj_loading/main.go b/examples/models/obj_loading/main.go index b15071a..4d63859 100644 --- a/examples/models/obj_loading/main.go +++ b/examples/models/obj_loading/main.go @@ -19,7 +19,8 @@ func main() { dwarf := rl.LoadModel("dwarf.obj") // Load OBJ model texture := rl.LoadTexture("dwarf_diffuse.png") // Load model texture - dwarf.Material.Maps[rl.MapDiffuse].Texture = texture // Set dwarf model diffuse texture + dwarf.Materials = make([]rl.Material, 1) + dwarf.Materials[0].Maps[rl.MapDiffuse].Texture = texture // Set dwarf model diffuse texture position := rl.NewVector3(0.0, 0.0, 0.0) // Set model position diff --git a/examples/others/resources/main.go b/examples/others/resources/main.go index 1b9bd84..d91fe65 100644 --- a/examples/others/resources/main.go +++ b/examples/others/resources/main.go @@ -29,10 +29,10 @@ func main() { //b := MustAsset("data.rres") //reader := bytes.NewReader(b) - res := rres.LoadResource(reader, 0, []byte("passwordpassword")) - wav := rl.LoadWaveEx(res.Data, int32(res.Param1), int32(res.Param2), int32(res.Param3), int32(res.Param4)) - snd := rl.LoadSoundFromWave(wav) - rl.UnloadWave(wav) + //res := rres.LoadResource(reader, 0, []byte("passwordpassword")) + //wav := rl.LoadWaveEx(res.Data, int32(res.Param1), int32(res.Param2), int32(res.Param3), int32(res.Param4)) + //snd := rl.LoadSoundFromWave(wav) + //rl.UnloadWave(wav) textures := make([]rl.Texture2D, numTextures) for i := 0; i < numTextures; i++ { @@ -48,7 +48,7 @@ func main() { for !rl.WindowShouldClose() { if rl.IsKeyPressed(rl.KeySpace) { - rl.PlaySound(snd) + //rl.PlaySound(snd) } if rl.IsMouseButtonPressed(rl.MouseLeftButton) { @@ -84,7 +84,7 @@ func main() { rl.EndDrawing() } - rl.UnloadSound(snd) + //rl.UnloadSound(snd) for _, t := range textures { rl.UnloadTexture(t) diff --git a/examples/shaders/custom_uniform/main.go b/examples/shaders/custom_uniform/main.go index 3a130e8..8b11a6f 100644 --- a/examples/shaders/custom_uniform/main.go +++ b/examples/shaders/custom_uniform/main.go @@ -21,7 +21,8 @@ func main() { dwarf := rl.LoadModel("dwarf.obj") // Load OBJ model texture := rl.LoadTexture("dwarf_diffuse.png") // Load model texture - dwarf.Material.Maps[rl.MapDiffuse].Texture = texture // Set dwarf model diffuse texture + dwarf.Materials = make([]rl.Material, 1) + dwarf.Materials[0].Maps[rl.MapDiffuse].Texture = texture // Set dwarf model diffuse texture position := rl.NewVector3(0.0, 0.0, 0.0) // Set model position diff --git a/examples/shaders/model_shader/main.go b/examples/shaders/model_shader/main.go index 6f813c8..ca1b586 100644 --- a/examples/shaders/model_shader/main.go +++ b/examples/shaders/model_shader/main.go @@ -24,8 +24,9 @@ func main() { texture := rl.LoadTexture("dwarf_diffuse.png") // Load model texture shader := rl.LoadShader("glsl330/base.vs", "glsl330/grayscale.fs") // Load model shader - dwarf.Material.Shader = shader // Set shader effect to 3d model - dwarf.Material.Maps[rl.MapDiffuse].Texture = texture // Set dwarf model diffuse texture + dwarf.Materials = make([]rl.Material, 1) + dwarf.Materials[0].Shader = shader // Set shader effect to 3d model + dwarf.Materials[0].Maps[rl.MapDiffuse].Texture = texture // Set dwarf model diffuse texture position := rl.NewVector3(0.0, 0.0, 0.0) // Set model position diff --git a/examples/text/ttf_loading/main.go b/examples/text/ttf_loading/main.go index 38ed446..b634761 100644 --- a/examples/text/ttf_loading/main.go +++ b/examples/text/ttf_loading/main.go @@ -19,7 +19,7 @@ func main() { fontChars := int32(0) // TTF Font loading with custom generation parameters - font := rl.LoadFontEx("fonts/KAISG.ttf", 96, 0, &fontChars) + font := rl.LoadFontEx("fonts/KAISG.ttf", 96, &fontChars, 0) // Generate mipmap levels to use trilinear filtering // NOTE: On 2D drawing it won't be noticeable, it looks like FILTER_BILINEAR @@ -69,7 +69,7 @@ func main() { if count == 1 { // Only support one ttf file dropped rl.UnloadFont(font) - font = rl.LoadFontEx(droppedFiles[0], fontSize, 0, &fontChars) + font = rl.LoadFontEx(droppedFiles[0], fontSize, &fontChars, 0) rl.ClearDroppedFiles() } } diff --git a/examples/textures/image_drawing/main.go b/examples/textures/image_drawing/main.go index 46d14f2..365c579 100644 --- a/examples/textures/image_drawing/main.go +++ b/examples/textures/image_drawing/main.go @@ -11,15 +11,15 @@ func main() { rl.InitWindow(screenWidth, screenHeight, "raylib [textures] example - texture loading and drawing") // NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required) - cat := rl.LoadImage("cat.png") // Load image in CPU memory (RAM) + cat := rl.LoadImage("cat.png") // Load image in CPU memory (RAM) rl.ImageCrop(cat, rl.NewRectangle(100, 10, 280, 380)) // Crop an image piece - rl.ImageFlipHorizontal(cat) // Flip cropped image horizontally - rl.ImageResize(cat, 150, 200) // Resize flipped-cropped image + rl.ImageFlipHorizontal(cat) // Flip cropped image horizontally + rl.ImageResize(cat, 150, 200) // Resize flipped-cropped image parrots := rl.LoadImage("parrots.png") // Load image in CPU memory (RAM) // Draw one image over the other with a scaling of 1.5f - rl.ImageDraw(parrots, cat, rl.NewRectangle(0, 0, float32(cat.Width), float32(cat.Height)), rl.NewRectangle(30, 40, float32(cat.Width)*1.5, float32(cat.Height)*1.5)) + rl.ImageDraw(parrots, cat, rl.NewRectangle(0, 0, float32(cat.Width), float32(cat.Height)), rl.NewRectangle(30, 40, float32(cat.Width)*1.5, float32(cat.Height)*1.5), rl.White) rl.ImageCrop(parrots, rl.NewRectangle(0, 50, float32(parrots.Width), float32(parrots.Height-100))) // Crop resulting image rl.UnloadImage(cat) // Unload image from RAM diff --git a/examples/textures/image_text/main.go b/examples/textures/image_text/main.go index 9c092a6..b416442 100644 --- a/examples/textures/image_text/main.go +++ b/examples/textures/image_text/main.go @@ -12,7 +12,7 @@ func main() { // TTF Font loading with custom generation parameters var fontChars int32 - font := rl.LoadFontEx("fonts/KAISG.ttf", 64, 0, &fontChars) + font := rl.LoadFontEx("fonts/KAISG.ttf", 64, &fontChars, 0) parrots := rl.LoadImage("parrots.png") // Load image in CPU memory (RAM) diff --git a/raylib/camera.h b/raylib/camera.h index 2ed35f3..ac4139b 100644 --- a/raylib/camera.h +++ b/raylib/camera.h @@ -8,7 +8,7 @@ * * #define CAMERA_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 +* 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. * * #define CAMERA_STANDALONE @@ -16,13 +16,13 @@ * functions must be redefined to manage inputs accordingly. * * CONTRIBUTORS: -* Marc Palau: Initial implementation (2014) * Ramon Santamaria: Supervision, review, update and maintenance +* Marc Palau: Initial implementation (2014) * * * LICENSE: zlib/libpng * -* Copyright (c) 2015-2017 Ramon Santamaria (@raysan5) +* Copyright (c) 2015-2019 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. @@ -54,15 +54,6 @@ // NOTE: Below types are required for CAMERA_STANDALONE usage //---------------------------------------------------------------------------------- #if defined(CAMERA_STANDALONE) - // Camera modes - typedef enum { - CAMERA_CUSTOM = 0, - CAMERA_FREE, - CAMERA_ORBITAL, - CAMERA_FIRST_PERSON, - CAMERA_THIRD_PERSON - } CameraMode; - // Vector2 type typedef struct Vector2 { float x; @@ -77,12 +68,30 @@ } Vector3; // Camera type, defines a camera position/orientation in 3d space - typedef struct Camera { - Vector3 position; - Vector3 target; - Vector3 up; - float fovy; - } Camera; + typedef struct Camera3D { + 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) in perspective, used as near plane width in orthographic + int type; // Camera type, defines projection type: CAMERA_PERSPECTIVE or CAMERA_ORTHOGRAPHIC + } Camera3D; + + typedef Camera3D Camera; // Camera type fallback, defaults to Camera3D + + // Camera system modes + typedef enum { + CAMERA_CUSTOM = 0, + CAMERA_FREE, + CAMERA_ORBITAL, + CAMERA_FIRST_PERSON, + CAMERA_THIRD_PERSON + } CameraMode; + + // Camera projection modes + typedef enum { + CAMERA_PERSPECTIVE = 0, + CAMERA_ORTHOGRAPHIC + } CameraType; #endif #ifdef __cplusplus @@ -103,9 +112,9 @@ void UpdateCamera(Camera *camera); // Update camera pos 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 SetCameraSmoothZoomControl(int szKey); // Set camera smooth zoom key to combine with mouse (free camera) -void SetCameraMoveControls(int frontKey, int backKey, - int rightKey, int leftKey, +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) #endif @@ -124,16 +133,14 @@ void SetCameraMoveControls(int frontKey, int backKey, #if defined(CAMERA_IMPLEMENTATION) -#include // Required for: sqrt(), sin(), cos() +#include // Required for: sqrt(), sinf(), cosf() #ifndef PI #define PI 3.14159265358979323846 #endif - #ifndef DEG2RAD #define DEG2RAD (PI/180.0f) #endif - #ifndef RAD2DEG #define RAD2DEG (180.0f/PI) #endif @@ -181,21 +188,21 @@ void SetCameraMoveControls(int frontKey, int backKey, // Types and Structures Definition //---------------------------------------------------------------------------------- // Camera move modes (first person and third person cameras) -typedef enum { - MOVE_FRONT = 0, - MOVE_BACK, - MOVE_RIGHT, - MOVE_LEFT, - MOVE_UP, - MOVE_DOWN +typedef enum { + MOVE_FRONT = 0, + MOVE_BACK, + MOVE_RIGHT, + MOVE_LEFT, + MOVE_UP, + MOVE_DOWN } CameraMove; //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- -static Vector2 cameraAngle = { 0.0f, 0.0f }; // TODO: Remove! Compute it in UpdateCamera() -static float cameraTargetDistance = 0.0f; // TODO: Remove! Compute it in UpdateCamera() -static float playerEyesPosition = 1.85f; // Default player eyes position from ground (in meters) +static Vector2 cameraAngle = { 0.0f, 0.0f }; // Camera angle in plane XZ +static float cameraTargetDistance = 0.0f; // Camera distance from position to target +static float playerEyesPosition = 1.85f; // Default player eyes position from ground (in meters) static int cameraMoveControl[6] = { 'W', 'S', 'D', 'A', 'E', 'Q' }; static int cameraPanControlKey = 2; // raylib: MOUSE_MIDDLE_BUTTON @@ -227,36 +234,24 @@ static Vector2 GetMousePosition() { return (Vector2){ 0.0f, 0.0f }; } // Select camera mode (multiple camera modes available) void SetCameraMode(Camera camera, int mode) { - // TODO: cameraTargetDistance and cameraAngle should be - // calculated using camera parameters on UpdateCamera() - Vector3 v1 = camera.position; Vector3 v2 = camera.target; - + float dx = v2.x - v1.x; float dy = v2.y - v1.y; float dz = v2.z - v1.z; - + cameraTargetDistance = sqrtf(dx*dx + dy*dy + dz*dz); - - Vector2 distance = { 0.0f, 0.0f }; - distance.x = sqrtf(dx*dx + dz*dz); - distance.y = sqrtf(dx*dx + dy*dy); - + // Camera angle calculation - cameraAngle.x = asinf( (float)fabs(dx)/distance.x); // Camera angle in plane XZ (0 aligned with Z, move positive CCW) - cameraAngle.y = -asinf( (float)fabs(dy)/distance.y); // Camera angle in plane XY (0 aligned with X, move positive CW) - - // NOTE: Just testing what cameraAngle means - //cameraAngle.x = 0.0f*DEG2RAD; // Camera angle in plane XZ (0 aligned with Z, move positive CCW) - //cameraAngle.y = -60.0f*DEG2RAD; // Camera angle in plane XY (0 aligned with X, move positive CW) - + cameraAngle.x = atan2f(dx, dz); // Camera angle in plane XZ (0 aligned with Z, move positive CCW) + cameraAngle.y = atan2f(dy, sqrtf(dx*dx + dz*dz)); // Camera angle in plane XY (0 aligned with X, move positive CW) + playerEyesPosition = camera.position.y; - + // Lock cursor for first person and third person cameras - if ((mode == CAMERA_FIRST_PERSON) || - (mode == CAMERA_THIRD_PERSON)) DisableCursor(); - else EnableCursor(); + if ((mode == CAMERA_FIRST_PERSON) || (mode == CAMERA_THIRD_PERSON)) DisableCursor(); + else EnableCursor(); cameraMode = mode; } @@ -273,24 +268,24 @@ void UpdateCamera(Camera *camera) static Vector2 previousMousePosition = { 0.0f, 0.0f }; // TODO: Compute cameraTargetDistance and cameraAngle here - + // Mouse movement detection Vector2 mousePositionDelta = { 0.0f, 0.0f }; Vector2 mousePosition = GetMousePosition(); int mouseWheelMove = GetMouseWheelMove(); - + // Keys input detection bool panKey = IsMouseButtonDown(cameraPanControlKey); bool altKey = IsKeyDown(cameraAltControlKey); bool szoomKey = IsKeyDown(cameraSmoothZoomControlKey); - + bool direction[6] = { IsKeyDown(cameraMoveControl[MOVE_FRONT]), IsKeyDown(cameraMoveControl[MOVE_BACK]), IsKeyDown(cameraMoveControl[MOVE_RIGHT]), IsKeyDown(cameraMoveControl[MOVE_LEFT]), IsKeyDown(cameraMoveControl[MOVE_UP]), IsKeyDown(cameraMoveControl[MOVE_DOWN]) }; - + // TODO: Consider touch inputs for camera if (cameraMode != CAMERA_CUSTOM) @@ -314,7 +309,7 @@ void UpdateCamera(Camera *camera) if (cameraTargetDistance > CAMERA_FREE_DISTANCE_MAX_CLAMP) cameraTargetDistance = CAMERA_FREE_DISTANCE_MAX_CLAMP; } // Camera looking down - // TODO: Review, weird comparisson of cameraTargetDistance == 120.0f? + // TODO: Review, weird comparisson of cameraTargetDistance == 120.0f? else if ((camera->position.y > camera->target.y) && (cameraTargetDistance == CAMERA_FREE_DISTANCE_MAX_CLAMP) && (mouseWheelMove < 0)) { camera->target.x += mouseWheelMove*(camera->target.x - camera->position.x)*CAMERA_MOUSE_SCROLL_SENSITIVITY/cameraTargetDistance; @@ -335,7 +330,7 @@ void UpdateCamera(Camera *camera) if (cameraTargetDistance < CAMERA_FREE_DISTANCE_MIN_CLAMP) cameraTargetDistance = CAMERA_FREE_DISTANCE_MIN_CLAMP; } // Camera looking up - // TODO: Review, weird comparisson of cameraTargetDistance == 120.0f? + // TODO: Review, weird comparisson of cameraTargetDistance == 120.0f? else if ((camera->position.y < camera->target.y) && (cameraTargetDistance == CAMERA_FREE_DISTANCE_MAX_CLAMP) && (mouseWheelMove < 0)) { camera->target.x += mouseWheelMove*(camera->target.x - camera->position.x)*CAMERA_MOUSE_SCROLL_SENSITIVITY/cameraTargetDistance; @@ -380,34 +375,42 @@ void UpdateCamera(Camera *camera) else { // Camera panning - camera->target.x += ((mousePositionDelta.x*-CAMERA_FREE_MOUSE_SENSITIVITY)*cosf(cameraAngle.x) + (mousePositionDelta.y*CAMERA_FREE_MOUSE_SENSITIVITY)*sinf(cameraAngle.x)*sinf(cameraAngle.y))*(cameraTargetDistance/CAMERA_FREE_PANNING_DIVIDER); + camera->target.x += ((mousePositionDelta.x*CAMERA_FREE_MOUSE_SENSITIVITY)*cosf(cameraAngle.x) + (mousePositionDelta.y*CAMERA_FREE_MOUSE_SENSITIVITY)*sinf(cameraAngle.x)*sinf(cameraAngle.y))*(cameraTargetDistance/CAMERA_FREE_PANNING_DIVIDER); camera->target.y += ((mousePositionDelta.y*CAMERA_FREE_MOUSE_SENSITIVITY)*cosf(cameraAngle.y))*(cameraTargetDistance/CAMERA_FREE_PANNING_DIVIDER); - camera->target.z += ((mousePositionDelta.x*CAMERA_FREE_MOUSE_SENSITIVITY)*sinf(cameraAngle.x) + (mousePositionDelta.y*CAMERA_FREE_MOUSE_SENSITIVITY)*cosf(cameraAngle.x)*sinf(cameraAngle.y))*(cameraTargetDistance/CAMERA_FREE_PANNING_DIVIDER); + camera->target.z += ((mousePositionDelta.x*-CAMERA_FREE_MOUSE_SENSITIVITY)*sinf(cameraAngle.x) + (mousePositionDelta.y*CAMERA_FREE_MOUSE_SENSITIVITY)*cosf(cameraAngle.x)*sinf(cameraAngle.y))*(cameraTargetDistance/CAMERA_FREE_PANNING_DIVIDER); } } + // Update camera position with changes + camera->position.x = -sinf(cameraAngle.x)*cameraTargetDistance*cosf(cameraAngle.y) + camera->target.x; + camera->position.y = -sinf(cameraAngle.y)*cameraTargetDistance + camera->target.y; + camera->position.z = -cosf(cameraAngle.x)*cameraTargetDistance*cosf(cameraAngle.y) + camera->target.z; } break; case CAMERA_ORBITAL: { cameraAngle.x += CAMERA_ORBITAL_SPEED; // Camera orbit angle cameraTargetDistance -= (mouseWheelMove*CAMERA_MOUSE_SCROLL_SENSITIVITY); // Camera zoom - + // Camera distance clamp if (cameraTargetDistance < CAMERA_THIRD_PERSON_DISTANCE_CLAMP) cameraTargetDistance = CAMERA_THIRD_PERSON_DISTANCE_CLAMP; - + + // Update camera position with changes + camera->position.x = sinf(cameraAngle.x)*cameraTargetDistance*cosf(cameraAngle.y) + camera->target.x; + camera->position.y = ((cameraAngle.y <= 0.0f)? 1 : -1)*sinf(cameraAngle.y)*cameraTargetDistance*sinf(cameraAngle.y) + camera->target.y; + camera->position.z = cosf(cameraAngle.x)*cameraTargetDistance*cosf(cameraAngle.y) + camera->target.z; + } break; case CAMERA_FIRST_PERSON: - case CAMERA_THIRD_PERSON: { camera->position.x += (sinf(cameraAngle.x)*direction[MOVE_BACK] - sinf(cameraAngle.x)*direction[MOVE_FRONT] - cosf(cameraAngle.x)*direction[MOVE_LEFT] + cosf(cameraAngle.x)*direction[MOVE_RIGHT])/PLAYER_MOVEMENT_SENSITIVITY; - + camera->position.y += (sinf(cameraAngle.y)*direction[MOVE_FRONT] - sinf(cameraAngle.y)*direction[MOVE_BACK] + 1.0f*direction[MOVE_UP] - 1.0f*direction[MOVE_DOWN])/PLAYER_MOVEMENT_SENSITIVITY; - + camera->position.z += (cosf(cameraAngle.x)*direction[MOVE_BACK] - cosf(cameraAngle.x)*direction[MOVE_FRONT] + sinf(cameraAngle.x)*direction[MOVE_LEFT] - @@ -416,63 +419,70 @@ void UpdateCamera(Camera *camera) bool isMoving = false; // Required for swinging for (int i = 0; i < 6; i++) if (direction[i]) { isMoving = true; break; } - + // Camera orientation calculation cameraAngle.x += (mousePositionDelta.x*-CAMERA_MOUSE_MOVE_SENSITIVITY); cameraAngle.y += (mousePositionDelta.y*-CAMERA_MOUSE_MOVE_SENSITIVITY); - - if (cameraMode == CAMERA_THIRD_PERSON) - { - // Angle clamp - if (cameraAngle.y > CAMERA_THIRD_PERSON_MIN_CLAMP*DEG2RAD) cameraAngle.y = CAMERA_THIRD_PERSON_MIN_CLAMP*DEG2RAD; - else if (cameraAngle.y < CAMERA_THIRD_PERSON_MAX_CLAMP*DEG2RAD) cameraAngle.y = CAMERA_THIRD_PERSON_MAX_CLAMP*DEG2RAD; - // Camera zoom - cameraTargetDistance -= (mouseWheelMove*CAMERA_MOUSE_SCROLL_SENSITIVITY); + // Angle clamp + if (cameraAngle.y > CAMERA_FIRST_PERSON_MIN_CLAMP*DEG2RAD) cameraAngle.y = CAMERA_FIRST_PERSON_MIN_CLAMP*DEG2RAD; + else if (cameraAngle.y < CAMERA_FIRST_PERSON_MAX_CLAMP*DEG2RAD) cameraAngle.y = CAMERA_FIRST_PERSON_MAX_CLAMP*DEG2RAD; - // Camera distance clamp - if (cameraTargetDistance < CAMERA_THIRD_PERSON_DISTANCE_CLAMP) cameraTargetDistance = CAMERA_THIRD_PERSON_DISTANCE_CLAMP; + // Camera is always looking at player + camera->target.x = camera->position.x - sinf(cameraAngle.x)*CAMERA_FIRST_PERSON_FOCUS_DISTANCE; + camera->target.y = camera->position.y + sinf(cameraAngle.y)*CAMERA_FIRST_PERSON_FOCUS_DISTANCE; + camera->target.z = camera->position.z - cosf(cameraAngle.x)*CAMERA_FIRST_PERSON_FOCUS_DISTANCE; - // Camera is always looking at player - camera->target.x = camera->position.x + CAMERA_THIRD_PERSON_OFFSET.x*cosf(cameraAngle.x) + CAMERA_THIRD_PERSON_OFFSET.z*sinf(cameraAngle.x); - camera->target.y = camera->position.y + CAMERA_THIRD_PERSON_OFFSET.y; - camera->target.z = camera->position.z + CAMERA_THIRD_PERSON_OFFSET.z*sinf(cameraAngle.x) - CAMERA_THIRD_PERSON_OFFSET.x*sinf(cameraAngle.x); - } - else // CAMERA_FIRST_PERSON - { - // Angle clamp - if (cameraAngle.y > CAMERA_FIRST_PERSON_MIN_CLAMP*DEG2RAD) cameraAngle.y = CAMERA_FIRST_PERSON_MIN_CLAMP*DEG2RAD; - else if (cameraAngle.y < CAMERA_FIRST_PERSON_MAX_CLAMP*DEG2RAD) cameraAngle.y = CAMERA_FIRST_PERSON_MAX_CLAMP*DEG2RAD; + if (isMoving) swingCounter++; - // Camera is always looking at player - camera->target.x = camera->position.x - sinf(cameraAngle.x)*CAMERA_FIRST_PERSON_FOCUS_DISTANCE; - camera->target.y = camera->position.y + sinf(cameraAngle.y)*CAMERA_FIRST_PERSON_FOCUS_DISTANCE; - camera->target.z = camera->position.z - cosf(cameraAngle.x)*CAMERA_FIRST_PERSON_FOCUS_DISTANCE; - - if (isMoving) swingCounter++; + // Camera position update + // NOTE: On CAMERA_FIRST_PERSON player Y-movement is limited to player 'eyes position' + camera->position.y = playerEyesPosition - sinf(swingCounter/CAMERA_FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER)/CAMERA_FIRST_PERSON_STEP_DIVIDER; - // Camera position update - // NOTE: On CAMERA_FIRST_PERSON player Y-movement is limited to player 'eyes position' - camera->position.y = playerEyesPosition - sinf(swingCounter/CAMERA_FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER)/CAMERA_FIRST_PERSON_STEP_DIVIDER; + camera->up.x = sinf(swingCounter/(CAMERA_FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER*2))/CAMERA_FIRST_PERSON_WAVING_DIVIDER; + camera->up.z = -sinf(swingCounter/(CAMERA_FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER*2))/CAMERA_FIRST_PERSON_WAVING_DIVIDER; + + + } break; + case CAMERA_THIRD_PERSON: + { + camera->position.x += (sinf(cameraAngle.x)*direction[MOVE_BACK] - + sinf(cameraAngle.x)*direction[MOVE_FRONT] - + cosf(cameraAngle.x)*direction[MOVE_LEFT] + + cosf(cameraAngle.x)*direction[MOVE_RIGHT])/PLAYER_MOVEMENT_SENSITIVITY; + + camera->position.y += (sinf(cameraAngle.y)*direction[MOVE_FRONT] - + sinf(cameraAngle.y)*direction[MOVE_BACK] + + 1.0f*direction[MOVE_UP] - 1.0f*direction[MOVE_DOWN])/PLAYER_MOVEMENT_SENSITIVITY; + + camera->position.z += (cosf(cameraAngle.x)*direction[MOVE_BACK] - + cosf(cameraAngle.x)*direction[MOVE_FRONT] + + sinf(cameraAngle.x)*direction[MOVE_LEFT] - + sinf(cameraAngle.x)*direction[MOVE_RIGHT])/PLAYER_MOVEMENT_SENSITIVITY; + + // Camera orientation calculation + cameraAngle.x += (mousePositionDelta.x*-CAMERA_MOUSE_MOVE_SENSITIVITY); + cameraAngle.y += (mousePositionDelta.y*-CAMERA_MOUSE_MOVE_SENSITIVITY); + + // Angle clamp + if (cameraAngle.y > CAMERA_THIRD_PERSON_MIN_CLAMP*DEG2RAD) cameraAngle.y = CAMERA_THIRD_PERSON_MIN_CLAMP*DEG2RAD; + else if (cameraAngle.y < CAMERA_THIRD_PERSON_MAX_CLAMP*DEG2RAD) cameraAngle.y = CAMERA_THIRD_PERSON_MAX_CLAMP*DEG2RAD; + + // Camera zoom + cameraTargetDistance -= (mouseWheelMove*CAMERA_MOUSE_SCROLL_SENSITIVITY); + + // Camera distance clamp + if (cameraTargetDistance < CAMERA_THIRD_PERSON_DISTANCE_CLAMP) cameraTargetDistance = CAMERA_THIRD_PERSON_DISTANCE_CLAMP; + + // 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(cameraAngle.x)*cameraTargetDistance*cosf(cameraAngle.y) + camera->target.x; + if (cameraAngle.y <= 0.0f) camera->position.y = sinf(cameraAngle.y)*cameraTargetDistance*sinf(cameraAngle.y) + camera->target.y; + else camera->position.y = -sinf(cameraAngle.y)*cameraTargetDistance*sinf(cameraAngle.y) + camera->target.y; + camera->position.z = cosf(cameraAngle.x)*cameraTargetDistance*cosf(cameraAngle.y) + camera->target.z; - camera->up.x = sinf(swingCounter/(CAMERA_FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER*2))/CAMERA_FIRST_PERSON_WAVING_DIVIDER; - camera->up.z = -sinf(swingCounter/(CAMERA_FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER*2))/CAMERA_FIRST_PERSON_WAVING_DIVIDER; - } } break; default: break; } - - // Update camera position with changes - if ((cameraMode == CAMERA_FREE) || - (cameraMode == CAMERA_ORBITAL) || - (cameraMode == CAMERA_THIRD_PERSON)) - { - // 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(cameraAngle.x)*cameraTargetDistance*cosf(cameraAngle.y) + camera->target.x; - if (cameraAngle.y <= 0.0f) camera->position.y = sinf(cameraAngle.y)*cameraTargetDistance*sinf(cameraAngle.y) + camera->target.y; - else camera->position.y = -sinf(cameraAngle.y)*cameraTargetDistance*sinf(cameraAngle.y) + camera->target.y; - camera->position.z = cosf(cameraAngle.x)*cameraTargetDistance*cosf(cameraAngle.y) + camera->target.z; - } } // Set camera pan key to combine with mouse movement (free camera) @@ -482,7 +492,7 @@ void SetCameraPanControl(int panKey) { cameraPanControlKey = panKey; } void SetCameraAltControl(int altKey) { cameraAltControlKey = altKey; } // Set camera smooth zoom key to combine with mouse (free camera) -void SetCameraSmoothZoomControl(int szKey) { cameraSmoothZoomControlKey = szKey; } +void SetCameraSmoothZoomControl(int szoomKey) { cameraSmoothZoomControlKey = 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) diff --git a/raylib/config.h b/raylib/config.h index 1425eeb..169eefd 100644 --- a/raylib/config.h +++ b/raylib/config.h @@ -6,7 +6,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2018 Ahmad Fatoum & Ramon Santamaria (@raysan5) +* Copyright (c) 2018-2019 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,7 +25,7 @@ * **********************************************************************************************/ -#define RAYLIB_VERSION "2.1-dev" +#define RAYLIB_VERSION "2.6-dev" // Edit to control what features Makefile'd raylib is compiled with #if defined(RAYLIB_CMAKE) @@ -42,6 +42,10 @@ #define SUPPORT_GESTURES_SYSTEM 1 // Mouse gestures are directly mapped like touches and processed by gestures system #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 // Use busy wait loop for timing sync, if not defined, a high-resolution timer is setup and used //#define SUPPORT_BUSY_WAIT_LOOP 1 // Wait for events passively (sleeping while no events) instead of polling them actively every frame @@ -50,15 +54,16 @@ #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 +// Support CompressData() and DecompressData() functions +#define SUPPORT_COMPRESSION_API 1 //------------------------------------------------------------------------------------ // Module: rlgl - Configuration Flags //------------------------------------------------------------------------------------ // Support VR simulation functionality (stereo rendering) #define SUPPORT_VR_SIMULATOR 1 -// Include stereo rendering distortion shader (shader_distortion.h) -#define SUPPORT_DISTORTION_SHADER 1 //------------------------------------------------------------------------------------ @@ -66,10 +71,10 @@ //------------------------------------------------------------------------------------ // 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 +#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 +#define SUPPORT_QUADS_DRAW_MODE 1 //------------------------------------------------------------------------------------ // Module: textures - Configuration Flags @@ -81,10 +86,10 @@ //#define SUPPORT_FILEFORMAT_JPG 1 //#define SUPPORT_FILEFORMAT_GIF 1 //#define SUPPORT_FILEFORMAT_PSD 1 -#define SUPPORT_FILEFORMAT_DDS 1 +//#define SUPPORT_FILEFORMAT_DDS 1 #define SUPPORT_FILEFORMAT_HDR 1 -#define SUPPORT_FILEFORMAT_KTX 1 -#define SUPPORT_FILEFORMAT_ASTC 1 +//#define SUPPORT_FILEFORMAT_KTX 1 +//#define SUPPORT_FILEFORMAT_ASTC 1 //#define SUPPORT_FILEFORMAT_PKM 1 //#define SUPPORT_FILEFORMAT_PVR 1 @@ -114,6 +119,8 @@ // Selected desired model fileformats to be supported for loading #define SUPPORT_FILEFORMAT_OBJ 1 #define SUPPORT_FILEFORMAT_MTL 1 +#define SUPPORT_FILEFORMAT_IQM 1 +#define SUPPORT_FILEFORMAT_GLTF 1 // Support procedural mesh generation functions, uses external par_shapes.h library // NOTE: Some generated meshes DO NOT include generated texture coordinates #define SUPPORT_MESH_GENERATION 1 @@ -127,8 +134,8 @@ #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 +#define SUPPORT_FILEFORMAT_MP3 1 //------------------------------------------------------------------------------------ diff --git a/raylib/core.c b/raylib/core.c index 67f4fb5..4eea4bf 100644 --- a/raylib/core.c +++ b/raylib/core.c @@ -8,7 +8,7 @@ * - PLATFORM_DESKTOP: FreeBSD, OpenBSD, NetBSD, DragonFly (X11 desktop) * - PLATFORM_DESKTOP: OSX/macOS * - PLATFORM_ANDROID: Android 4.0 (ARM, ARM64) -* - PLATFORM_RPI: Raspberry Pi 0,1,2,3 (Raspbian) +* - PLATFORM_RPI: Raspberry Pi 0,1,2,3,4 (Raspbian) * - PLATFORM_WEB: HTML5 with asm.js (Chrome, Firefox) * - PLATFORM_UWP: Windows 10 App, Windows Phone, Xbox One * @@ -47,6 +47,17 @@ * #define SUPPORT_MOUSE_GESTURES * Mouse gestures are directly mapped like touches and processed by gestures system. * +* #define SUPPORT_TOUCH_AS_MOUSE +* Touch input and mouse input are shared. Mouse functions also return touch information. +* +* #define SUPPORT_SSH_KEYBOARD_RPI (Raspberry Pi only) +* Reconfigure standard input to receive key inputs, works with SSH connection. +* 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_BUSY_WAIT_LOOP * Use busy wait loop for timing sync, if not defined, a high-resolution timer is setup and used * @@ -59,6 +70,15 @@ * #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 +* * DEPENDENCIES: * rglfw - Manage graphic device, OpenGL context and inputs on PLATFORM_DESKTOP (Windows, Linux, OSX. FreeBSD, OpenBSD, NetBSD, DragonFly) * raymath - 3D math functionality (Vector2, Vector3, Matrix, Quaternion) @@ -68,7 +88,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2018 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2019 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. @@ -87,9 +107,15 @@ * **********************************************************************************************/ -#include "config.h" // Defines module configuration flags #include "raylib.h" // Declares module functions +// 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 "2.6-dev" +#endif + #if (defined(__linux__) || defined(PLATFORM_WEB)) && _POSIX_C_SOURCE < 199309L #undef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 199309L // Required for CLOCK_MONOTONIC if compiled with c99 without gnu ext. @@ -108,7 +134,7 @@ #include "gestures.h" // Gestures detection functionality #endif -#if defined(SUPPORT_CAMERA_SYSTEM) && !defined(PLATFORM_ANDROID) +#if defined(SUPPORT_CAMERA_SYSTEM) #define CAMERA_IMPLEMENTATION #include "camera.h" // Camera system functionality #endif @@ -118,6 +144,10 @@ #include "external/rgif.h" // Support GIF recording #endif +#if defined(__APPLE__) + #define SUPPORT_HIGH_DPI // Force HighDPI support on macOS +#endif + #include // Standard input / output lib #include // Required for: malloc(), free(), rand(), atexit() #include // Required for: typedef unsigned long long int uint64_t, used by hi-res timer @@ -128,7 +158,7 @@ #include // Required for: tolower() [Used in IsFileExtension()] #include // Required for stat() [Used in GetLastWriteTime()] -#if defined(PLATFORM_DESKTOP) && defined(_WIN32) && defined(_MSC_VER) +#if (defined(PLATFORM_DESKTOP) || defined(PLATFORM_UWP)) && defined(_WIN32) && (defined(_MSC_VER) || defined(__TINYC__)) #include "external/dirent.h" // Required for: DIR, opendir(), closedir() [Used in GetDirectoryFiles()] #else #include // Required for: DIR, opendir(), closedir() [Used in GetDirectoryFiles()] @@ -171,20 +201,17 @@ #include // Required for: glfwGetX11Window() #elif defined(__APPLE__) #include // Required for: usleep() - #include // Required for: objc_msgsend(), sel_registerName() //#define GLFW_EXPOSE_NATIVE_COCOA // WARNING: Fails due to type redefinition - #define GLFW_EXPOSE_NATIVE_NSGL - #include // Required for: glfwGetCocoaWindow(), glfwGetNSGLContext() - + #include // Required for: glfwGetCocoaWindow() #endif #endif #if defined(__linux__) - #include // for NAME_MAX and PATH_MAX defines - #define MAX_FILEPATH_LENGTH PATH_MAX // Use Linux define (4096) + #include // for NAME_MAX and PATH_MAX defines + #define MAX_FILEPATH_LENGTH PATH_MAX // Use Linux define (4096) #else - #define MAX_FILEPATH_LENGTH 256 // Use common value + #define MAX_FILEPATH_LENGTH 512 // Use common value #endif #if defined(PLATFORM_ANDROID) @@ -230,6 +257,12 @@ #include // Emscripten HTML5 library #endif +#if defined(SUPPORT_COMPRESSION_API) + // NOTE: Those declarations require stb_image and stb_image_write definitions, included in textures module + unsigned char *stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality); + char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen); +#endif + //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- @@ -253,6 +286,8 @@ #define MAX_GAMEPAD_BUTTONS 32 // Max bumber of buttons supported (per gamepad) #define MAX_GAMEPAD_AXIS 8 // Max number of axis supported (per gamepad) +#define MAX_CHARS_QUEUE 16 // Max number of characters in the input queue + #define STORAGE_FILENAME "storage.data" //---------------------------------------------------------------------------------- @@ -271,19 +306,18 @@ static GLFWwindow *window; // Native window (graphic device #endif static bool windowReady = false; // Check if window has been initialized successfully static bool windowMinimized = false; // Check if window has been minimized +static bool windowResized = false; // Check if window has been resized static const char *windowTitle = NULL; // Window text title... -#if defined(__APPLE__) -static int windowNeedsUpdating = 2; // Times the Cocoa window needs to be updated initially -#endif - static unsigned int displayWidth, displayHeight;// Display width and height (monitor, device-screen, LCD, ...) static int screenWidth, screenHeight; // Screen width and height (used render area) static int renderWidth, renderHeight; // Framebuffer width and height (render area, including black bars if required) +static int currentWidth, currentHeight; // Current render width and height, it could change on BeginTextureMode() static int renderOffsetX = 0; // Offset X from render area (must be divided by 2) static int renderOffsetY = 0; // Offset Y from render area (must be divided by 2) static bool fullscreen = false; // Fullscreen mode (useful only for PLATFORM_DESKTOP) -static Matrix downscaleView; // Matrix to downscale view (in case screen size bigger than display size) +static bool alwaysRun = false; // Keep window update/draw running on minimized +static Matrix screenScaling = { 0 }; // Matrix to scale screen (framebuffer rendering) #if defined(PLATFORM_RPI) static EGL_DISPMANX_WINDOW_T nativeWindow; // Native window (graphic device) @@ -294,7 +328,7 @@ static EGLDisplay display; // Native display device (physic static EGLSurface surface; // Surface to draw on, framebuffers (connected to context) static EGLContext context; // Graphic context, mode in which drawing can be done static EGLConfig config; // Graphic config -static uint64_t baseTime; // Base time measure for hi-res timer +static uint64_t baseTime = 0; // Base time measure for hi-res timer static bool windowShouldClose = false; // Flag to set window for closing #endif @@ -307,7 +341,7 @@ extern EGLNativeWindowType uwpWindow; // Native EGL window handler for static struct android_app *androidApp; // Android activity static struct android_poll_source *source; // Android events polling source static int ident, events; // Android ALooper_pollAll() variables -static const char *internalDataPath; // Android internal data path to write data (/data/data//files) +static const char *internalDataPath = NULL; // Android internal data path to write data (/data/data//files) static bool appEnabled = true; // Used to detec if app is active static bool contextRebindRequired = false; // Used to know context rebind required @@ -318,18 +352,21 @@ static bool contextRebindRequired = false; // Used to know context rebind r // Keyboard states static char previousKeyState[512] = { 0 }; // Registers previous frame key state static char currentKeyState[512] = { 0 }; // Registers current frame key state -static int lastKeyPressed = -1; // Register last key pressed static int exitKey = KEY_ESCAPE; // Default exit key (ESC) +static unsigned int keyPressedQueue[MAX_CHARS_QUEUE] = { 0 }; // Input characters queue +static int keyPressedQueueCount = 0; // Input characters queue count + #if defined(PLATFORM_RPI) // NOTE: For keyboard we will use the standard input (but reconfigured...) static struct termios defaultKeyboardSettings; // Used to store default keyboard settings -static int defaultKeyboardMode; // Used to store default keyboard mode +static int defaultKeyboardMode = 0; // Used to store default keyboard mode #endif // Mouse states -static Vector2 mousePosition; // Mouse position on screen -static float mouseScale = 1.0f; // Mouse default scale +static Vector2 mousePosition = { 0.0f, 0.0f }; // Mouse position on screen +static Vector2 mouseScale = { 1.0f, 1.0f }; // Mouse scaling +static Vector2 mouseOffset = { 0.0f, 0.0f }; // Mouse offset static bool cursorHidden = false; // Track if cursor is hidden static bool cursorOnScreen = false; // Tracks if cursor is inside client area static Vector2 touchPosition[MAX_TOUCH_POINTS]; // Touch position on screen @@ -359,6 +396,15 @@ typedef struct { static InputEventWorker eventWorkers[10]; // List of worker threads for every monitored "/dev/input/event" +typedef struct{ + int Contents[8]; + char Head; + char Tail; +} KeyEventFifo; + +static KeyEventFifo lastKeyPressedEvdev = { 0 }; // Buffer for holding keydown events as they arrive (Needed due to multitreading of event workers) +static char currentKeyStateEvdev[512] = { 0 }; // Registers current frame key state from event based driver (Needs to be seperate because the legacy console based method clears keys on every frame) + #endif #if defined(PLATFORM_WEB) static bool toggleCursorLock = false; // Ask for cursor pointer lock on next click @@ -394,13 +440,11 @@ static double targetTime = 0.0; // Desired time for one frame, if 0 // Config internal variables //----------------------------------------------------------------------------------- -static unsigned char configFlags = 0; // Configuration flags (bit based) -static bool showLogo = false; // Track if showing logo at init is enabled +static unsigned int configFlags = 0; // Configuration flags (bit based) -#if defined(PLATFORM_DESKTOP) static char **dropFilesPath; // Store dropped files paths as strings static int dropFilesCount = 0; // Count dropped files strings -#endif + static char **dirFilesPath; // Store directory files paths as strings static int dirFilesCount = 0; // Count directory files strings @@ -418,8 +462,8 @@ static bool gifRecording = false; // GIF recording state // Other Modules Functions Declaration (required by core) //---------------------------------------------------------------------------------- #if defined(SUPPORT_DEFAULT_FONT) -extern void LoadDefaultFont(void); // [Module: text] Loads default font on InitWindow() -extern void UnloadDefaultFont(void); // [Module: text] Unloads default font from GPU memory +extern void LoadFontDefault(void); // [Module: text] Loads default font on InitWindow() +extern void UnloadFontDefault(void); // [Module: text] Unloads default font from GPU memory #endif //---------------------------------------------------------------------------------- @@ -427,7 +471,7 @@ extern void UnloadDefaultFont(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(void); // Set viewport parameters +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 InitTimer(void); // Initialize timer @@ -435,10 +479,10 @@ static void Wait(float ms); // Wait for some millise static bool GetKeyStatus(int key); // Returns if a key has been pressed static bool GetMouseButtonStatus(int button); // Returns if a mouse button has been pressed +static int GetGamepadButton(int button); // Get gamepad button generic to all platforms +static int GetGamepadAxis(int axis); // Get gamepad axis generic to all platforms static void PollInputEvents(void); // Register user events -static void LogoAnimation(void); // Plays raylib logo appearing animation - #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 @@ -449,8 +493,6 @@ static void ScrollCallback(GLFWwindow *window, double xoffset, double yoffset); static void CursorEnterCallback(GLFWwindow *window, int enter); // GLFW3 Cursor Enter Callback, cursor enters client area static void WindowSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 WindowSize Callback, runs when window is resized static void WindowIconifyCallback(GLFWwindow *window, int iconified); // GLFW3 WindowIconify Callback, runs when window is minimized/restored -#endif -#if defined(PLATFORM_DESKTOP) static void WindowDropCallback(GLFWwindow *window, int count, const char **paths); // GLFW3 Window Drop Callback, runs when drop files into window #endif @@ -468,19 +510,22 @@ static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadE #endif #if defined(PLATFORM_RPI) +#if defined(SUPPORT_SSH_KEYBOARD_RPI) static void InitKeyboard(void); // Init raw keyboard system (standard input reading) static void ProcessKeyboard(void); // Process keyboard events static void RestoreKeyboard(void); // Restore keyboard system -static void InitMouse(void); // Mouse initialization (including mouse thread) -static void EventThreadSpawn(char *device); // Indetifies a input device and spawns a thread to handle it if needed -static void *EventThread(void *arg); // Input device event reading thread -static void InitGamepad(void); // Init raw gamepad input -static void *GamepadThread(void *arg); // Mouse reading thread +#else +static void InitTerminal(void); // Init terminal (block echo and signal short cuts) +static void RestoreTerminal(void); // Restore terminal #endif -#if defined(PLATFORM_UWP) -// TODO: Define functions required to manage inputs -#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 *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(_WIN32) // NOTE: We include Sleep() function signature here to avoid windows.h inclusion @@ -513,7 +558,52 @@ struct android_app *GetAndroidApp(void) return androidApp; } #endif +#if defined(PLATFORM_RPI) && !defined(SUPPORT_SSH_KEYBOARD_RPI) +// Init terminal (block echo and signal short cuts) +static void InitTerminal(void) +{ + TraceLog(LOG_INFO, "Reconfigure Terminal ..."); + // Save terminal keyboard settings and reconfigure terminal with new settings + struct termios keyboardNewSettings; + tcgetattr(STDIN_FILENO, &defaultKeyboardSettings); // Get current keyboard settings + keyboardNewSettings = defaultKeyboardSettings; + // New terminal settings for keyboard: turn off buffering (non-canonical mode), echo + // NOTE: ISIG controls if ^C and ^Z generate break signals or not + keyboardNewSettings.c_lflag &= ~(ICANON | ECHO | ISIG); + keyboardNewSettings.c_cc[VMIN] = 1; + keyboardNewSettings.c_cc[VTIME] = 0; + + // Set new keyboard settings (change occurs immediately) + tcsetattr(STDIN_FILENO, TCSANOW, &keyboardNewSettings); + + // Save old keyboard mode to restore it at the end + if (ioctl(STDIN_FILENO, KDGKBMODE, &defaultKeyboardMode) < 0) + { + // NOTE: It could mean we are using a remote keyboard through ssh or from the desktop + TraceLog(LOG_WARNING, "Could not change keyboard mode (Not a local Terminal)"); + } + else + { + + ioctl(STDIN_FILENO, KDSKBMODE, K_XLATE); + } + + // Register terminal restore when program finishes + atexit(RestoreTerminal); +} +// Restore terminal +static void RestoreTerminal(void) +{ + TraceLog(LOG_INFO, "Restore Terminal ..."); + + // Reset to default keyboard settings + tcsetattr(STDIN_FILENO, TCSANOW, &defaultKeyboardSettings); + + // Reconfigure keyboard to default mode + ioctl(STDIN_FILENO, KDSKBMODE, defaultKeyboardMode); +} +#endif // Initialize window and OpenGL context // NOTE: data parameter could be used to pass any kind of required data to the initialization void InitWindow(int width, int height, const char *title) @@ -524,6 +614,8 @@ void InitWindow(int width, int height, const char *title) #if defined(PLATFORM_ANDROID) screenWidth = width; screenHeight = height; + currentWidth = width; + currentHeight = height; // Input data is android app pointer internalDataPath = androidApp->activity->internalDataPath; @@ -586,14 +678,18 @@ void InitWindow(int width, int height, const char *title) #if defined(SUPPORT_DEFAULT_FONT) // Load default font // NOTE: External function (defined in module: text) - LoadDefaultFont(); + LoadFontDefault(); #endif #if defined(PLATFORM_RPI) // Init raw input system - InitMouse(); // Mouse init - InitKeyboard(); // Keyboard init + InitEvdevInput(); // Evdev inputs initialization InitGamepad(); // Gamepad init +#if defined(SUPPORT_SSH_KEYBOARD_RPI) + InitKeyboard(); // Keyboard init +#else + InitTerminal(); // Terminal init +#endif #endif #if defined(PLATFORM_WEB) @@ -610,8 +706,6 @@ void InitWindow(int width, int height, const char *title) emscripten_set_touchend_callback("#canvas", NULL, 1, EmscriptenTouchCallback); emscripten_set_touchmove_callback("#canvas", NULL, 1, EmscriptenTouchCallback); emscripten_set_touchcancel_callback("#canvas", NULL, 1, EmscriptenTouchCallback); - //emscripten_set_touchstart_callback(0, NULL, 1, Emscripten_HandleTouch); - //emscripten_set_touchend_callback("#canvas", data, 0, Emscripten_HandleTouch); // Support gamepad events (not provided by GLFW3 on emscripten) emscripten_set_gamepadconnected_callback(NULL, 1, EmscriptenGamepadCallback); @@ -620,14 +714,7 @@ void InitWindow(int width, int height, const char *title) mousePosition.x = (float)screenWidth/2.0f; mousePosition.y = (float)screenHeight/2.0f; - - // raylib logo appearing animation (if enabled) - if (showLogo) - { - SetTargetFPS(60); - LogoAnimation(); - } -#endif // defined(PLATFORM_ANDROID) +#endif // PLATFORM_ANDROID } // Close window and unload OpenGL context @@ -642,7 +729,7 @@ void CloseWindow(void) #endif #if defined(SUPPORT_DEFAULT_FONT) - UnloadDefaultFont(); + UnloadFontDefault(); #endif rlglClose(); // De-init rlgl @@ -688,12 +775,13 @@ void CloseWindow(void) for (int i = 0; i < sizeof(eventWorkers)/sizeof(InputEventWorker); ++i) { - if (eventWorkers[i].threadId == 0) + if (eventWorkers[i].threadId) { pthread_join(eventWorkers[i].threadId, NULL); } } - pthread_join(gamepadThreadId, NULL); + + if (gamepadThreadId) pthread_join(gamepadThreadId, NULL); #endif TraceLog(LOG_INFO, "Window closed successfully"); @@ -710,7 +798,7 @@ bool WindowShouldClose(void) { #if defined(PLATFORM_WEB) // Emterpreter-Async required to run sync code - // https://github.com/kripken/emscripten/wiki/Emterpreter#emterpreter-async-run-synchronous-code + // https://github.com/emscripten-core/emscripten/wiki/Emterpreter#emterpreter-async-run-synchronous-code // By default, this function is never called on a web-ready raylib example because we encapsulate // frame code in a UpdateDrawFrame() function, to allow browser manage execution asynchronously // but now emscripten allows sync code to be executed in an interpreted way, using emterpreter! @@ -722,7 +810,7 @@ bool WindowShouldClose(void) if (windowReady) { // While window minimized, stop loop execution - while (windowMinimized) glfwWaitEvents(); + while (!alwaysRun && windowMinimized) glfwWaitEvents(); return (glfwWindowShouldClose(window)); } @@ -745,6 +833,25 @@ bool IsWindowMinimized(void) #endif } +// Check if window has been resized +bool IsWindowResized(void) +{ +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP) + return windowResized; +#else + return false; +#endif +} + +// Check if window is currently hidden +bool IsWindowHidden(void) +{ +#if defined(PLATFORM_DESKTOP) + return (glfwGetWindowAttrib(window, GLFW_VISIBLE) == GL_FALSE); +#endif + return false; +} + // Toggle fullscreen mode (only PLATFORM_DESKTOP) void ToggleFullscreen(void) { @@ -762,30 +869,30 @@ void ToggleFullscreen(void) } // Set icon for window (only PLATFORM_DESKTOP) +// NOTE: Image must be in RGBA format, 8bit per channel void SetWindowIcon(Image image) { #if defined(PLATFORM_DESKTOP) - Image imicon = ImageCopy(image); - ImageFormat(&imicon, UNCOMPRESSED_R8G8B8A8); + if (image.format == UNCOMPRESSED_R8G8B8A8) + { + GLFWimage icon[1] = { 0 }; - GLFWimage icon[1] = { 0 }; + icon[0].width = image.width; + icon[0].height = image.height; + icon[0].pixels = (unsigned char *)image.data; - icon[0].width = imicon.width; - icon[0].height = imicon.height; - icon[0].pixels = (unsigned char *)imicon.data; - - // NOTE 1: We only support one image icon - // NOTE 2: The specified image data is copied before this function returns - glfwSetWindowIcon(window, 1, icon); - - // TODO: Support multi-image icons --> image.mipmaps - UnloadImage(imicon); + // NOTE 1: We only support one image icon + // NOTE 2: The specified image data is copied before this function returns + glfwSetWindowIcon(window, 1, icon); + } + else TraceLog(LOG_WARNING, "Window icon image must be in R8G8B8A8 pixel format"); #endif } // Set title for window (only PLATFORM_DESKTOP) void SetWindowTitle(const char *title) { + windowTitle = title; #if defined(PLATFORM_DESKTOP) glfwSetWindowTitle(window, title); #endif @@ -825,6 +932,7 @@ void SetWindowMinSize(int width, int height) } // Set window dimensions +// TODO: Issues on HighDPI scaling void SetWindowSize(int width, int height) { #if defined(PLATFORM_DESKTOP) @@ -832,6 +940,22 @@ void SetWindowSize(int width, int height) #endif } +// Show the window +void UnhideWindow(void) +{ +#if defined(PLATFORM_DESKTOP) + glfwShowWindow(window); +#endif +} + +// Hide the window +void HideWindow(void) +{ +#if defined(PLATFORM_DESKTOP) + glfwHideWindow(window); +#endif +} + // Get current screen width int GetScreenWidth(void) { @@ -847,9 +971,9 @@ int GetScreenHeight(void) // Get native window handle void *GetWindowHandle(void) { -#if defined(_WIN32) +#if defined(PLATFORM_DESKTOP) && defined(_WIN32) // NOTE: Returned handle is: void *HWND (windows.h) - return glfwGetWin32Window(window); + return glfwGetWin32Window(window); #elif defined(__linux__) // NOTE: Returned handle is: unsigned long Window (X.h) // typedef unsigned long XID; @@ -946,6 +1070,17 @@ int GetMonitorPhysicalHeight(int monitor) return 0; } +// Get window position XY on monitor +Vector2 GetWindowPosition(void) +{ + int x = 0; + int y = 0; +#if defined(PLATFORM_DESKTOP) + glfwGetWindowPos(window, &x, &y); +#endif + return (Vector2){ (float)x, (float)y }; +} + // Get the human-readable, UTF-8 encoded name of the primary monitor const char *GetMonitorName(int monitor) { @@ -962,50 +1097,89 @@ const char *GetMonitorName(int monitor) return ""; } +// Get clipboard text content +// NOTE: returned string is allocated and freed by GLFW +const char *GetClipboardText(void) +{ +#if defined(PLATFORM_DESKTOP) + return glfwGetClipboardString(window); +#else + return NULL; +#endif +} + +// Set clipboard text content +void SetClipboardText(const char *text) +{ +#if defined(PLATFORM_DESKTOP) + glfwSetClipboardString(window, text); +#endif +} + // Show mouse cursor -void ShowCursor() +void ShowCursor(void) { #if defined(PLATFORM_DESKTOP) glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); +#endif +#if defined(PLATFORM_UWP) + UWPMessage *msg = CreateUWPMessage(); + msg->type = UWP_MSG_SHOW_MOUSE; + SendMessageToUWP(msg); #endif cursorHidden = false; } // Hides mouse cursor -void HideCursor() +void HideCursor(void) { #if defined(PLATFORM_DESKTOP) glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); +#endif +#if defined(PLATFORM_UWP) + UWPMessage *msg = CreateUWPMessage(); + msg->type = UWP_MSG_HIDE_MOUSE; + SendMessageToUWP(msg); #endif cursorHidden = true; } // Check if cursor is not visible -bool IsCursorHidden() +bool IsCursorHidden(void) { return cursorHidden; } // Enables cursor (unlock cursor) -void EnableCursor() +void EnableCursor(void) { #if defined(PLATFORM_DESKTOP) glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); #endif #if defined(PLATFORM_WEB) toggleCursorLock = true; +#endif +#if defined(PLATFORM_UWP) + UWPMessage *msg = CreateUWPMessage(); + msg->type = UWP_MSG_LOCK_MOUSE; + SendMessageToUWP(msg); #endif cursorHidden = false; } // Disables cursor (lock cursor) -void DisableCursor() +void DisableCursor(void) { #if defined(PLATFORM_DESKTOP) glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); #endif #if defined(PLATFORM_WEB) toggleCursorLock = true; +#endif +#if defined(PLATFORM_UWP) + UWPMessage *msg = CreateUWPMessage(); + msg->type = UWP_MSG_UNLOCK_MOUSE; + SendMessageToUWP(msg); #endif cursorHidden = true; } @@ -1025,7 +1199,7 @@ void BeginDrawing(void) previousTime = currentTime; rlLoadIdentity(); // Reset current matrix (MODELVIEW) - rlMultMatrixf(MatrixToFloat(downscaleView)); // If downscale required, apply it here + rlMultMatrixf(MatrixToFloat(screenScaling)); // Apply screen scaling //rlTranslatef(0.375, 0.375, 0); // HACK to have 2D pixel-perfect drawing on OpenGL 1.1 // NOTE: Not required with OpenGL 3.3+ @@ -1034,6 +1208,12 @@ 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, + // we draw a small rectangle for user reference + DrawRectangle(mousePosition.x, mousePosition.y, 3, 3, MAROON); +#endif + rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) #if defined(SUPPORT_GIF_RECORDING) @@ -1052,7 +1232,7 @@ void EndDrawing(void) unsigned char *screenData = rlReadScreenPixels(screenWidth, screenHeight); GifWriteFrame(screenData, screenWidth, screenHeight, 10, 8, false); - free(screenData); // Free image data + RL_FREE(screenData); // Free image data } if (((gifFramesCounter/15)%2) == 1) @@ -1086,6 +1266,8 @@ void EndDrawing(void) frameTime += extraTime; } + + return; } // Initialize 2D mode with custom camera (2D) @@ -1095,15 +1277,11 @@ void BeginMode2D(Camera2D camera) rlLoadIdentity(); // Reset current matrix (MODELVIEW) - // Camera rotation and scaling is always relative to target - Matrix matOrigin = MatrixTranslate(-camera.target.x, -camera.target.y, 0.0f); - Matrix matRotation = MatrixRotate((Vector3){ 0.0f, 0.0f, 1.0f }, camera.rotation*DEG2RAD); - Matrix matScale = MatrixScale(camera.zoom, camera.zoom, 1.0f); - Matrix matTranslation = MatrixTranslate(camera.offset.x + camera.target.x, camera.offset.y + camera.target.y, 0.0f); + // Apply screen scaling if required + rlMultMatrixf(MatrixToFloat(screenScaling)); - Matrix matTransform = MatrixMultiply(MatrixMultiply(matOrigin, MatrixMultiply(matScale, matRotation)), matTranslation); - - rlMultMatrixf(MatrixToFloat(matTransform)); + // Apply 2d camera transformation to modelview + rlMultMatrixf(MatrixToFloat(GetCameraMatrix2D(camera))); } // Ends 2D mode with custom camera @@ -1112,6 +1290,7 @@ void EndMode2D(void) rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) rlLoadIdentity(); // Reset current matrix (MODELVIEW) + rlMultMatrixf(MatrixToFloat(screenScaling)); // Apply screen scaling if required } // Initializes 3D mode with custom camera (3D) @@ -1123,7 +1302,7 @@ void BeginMode3D(Camera3D camera) rlPushMatrix(); // Save previous matrix, which contains the settings for the 2d ortho projection rlLoadIdentity(); // Reset current matrix (PROJECTION) - float aspect = (float)screenWidth/(float)screenHeight; + float aspect = (float)currentWidth/(float)currentHeight; if (camera.type == CAMERA_PERSPECTIVE) { @@ -1165,6 +1344,8 @@ void EndMode3D(void) rlMatrixMode(RL_MODELVIEW); // Get back to modelview matrix rlLoadIdentity(); // Reset current matrix (MODELVIEW) + rlMultMatrixf(MatrixToFloat(screenScaling)); // Apply screen scaling if required + rlDisableDepthTest(); // Disable DEPTH_TEST for 2D } @@ -1175,8 +1356,6 @@ void BeginTextureMode(RenderTexture2D target) rlEnableRenderTexture(target.id); // Enable render target - rlClearScreenBuffers(); // Clear render texture buffers - // Set viewport to framebuffer size rlViewport(0, 0, target.texture.width, target.texture.height); @@ -1191,6 +1370,11 @@ void BeginTextureMode(RenderTexture2D target) rlLoadIdentity(); // Reset current matrix (MODELVIEW) //rlScalef(0.0f, -1.0f, 0.0f); // Flip Y-drawing (?) + + // Setup current width/height for proper aspect ratio + // calculation when using BeginMode3D() + currentWidth = target.texture.width; + currentHeight = target.texture.height; } // Ends drawing to render texture @@ -1200,18 +1384,29 @@ void EndTextureMode(void) rlDisableRenderTexture(); // Disable render target - // Set viewport to default framebuffer size (screen size) - SetupViewport(); + // Set viewport to default framebuffer size + SetupViewport(renderWidth, renderHeight); - rlMatrixMode(RL_PROJECTION); // Switch to PROJECTION matrix - rlLoadIdentity(); // Reset current matrix (PROJECTION) + // Reset current screen size + currentWidth = GetScreenWidth(); + currentHeight = GetScreenHeight(); +} - // Set orthographic projection to current framebuffer size - // NOTE: Configured top-left corner as (0, 0) - rlOrtho(0, GetScreenWidth(), GetScreenHeight(), 0, 0.0f, 1.0f); +// 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 - rlMatrixMode(RL_MODELVIEW); // Switch back to MODELVIEW matrix - rlLoadIdentity(); // Reset current matrix (MODELVIEW) + rlEnableScissorTest(); + rlScissor(x, GetScreenHeight() - (y + height), width, height); +} + +// End scissor mode +void EndScissorMode(void) +{ + rlglDraw(); // Force drawing elements + rlDisableScissorTest(); } // Returns a ray trace from mouse position @@ -1228,8 +1423,6 @@ Ray GetMouseRay(Vector2 mousePosition, Camera camera) // Store values in a vector Vector3 deviceCoords = { x, y, z }; - TraceLog(LOG_DEBUG, "Device coordinates: (%f, %f, %f)", deviceCoords.x, deviceCoords.y, deviceCoords.z); - // Calculate view matrix from camera look at Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); @@ -1271,6 +1464,40 @@ Ray GetMouseRay(Vector2 mousePosition, Camera camera) return ray; } +// Get transform matrix for camera +Matrix GetCameraMatrix(Camera camera) +{ + return MatrixLookAt(camera.position, camera.target, camera.up); +} + +// Returns camera 2d transform matrix +Matrix GetCameraMatrix2D(Camera2D camera) +{ + Matrix matTransform = { 0 }; + // The camera in world-space is set by + // 1. Move it to target + // 2. Rotate by -rotation and scale by (1/zoom) + // When setting higher scale, it's more intuitive for the world to become bigger (= camera become smaller), + // not for the camera getting bigger, hence the invert. Same deal with rotation. + // 3. Move it by (-offset); + // Offset defines target transform relative to screen, but since we're effectively "moving" screen (camera) + // we need to do it into opposite direction (inverse transform) + + // Having camera transform in world-space, inverse of it gives the modelview transform. + // Since (A*B*C)' = C'*B'*A', the modelview is + // 1. Move to offset + // 2. Rotate and Scale + // 3. Move by -target + Matrix matOrigin = MatrixTranslate(-camera.target.x, -camera.target.y, 0.0f); + Matrix matRotation = MatrixRotate((Vector3){ 0.0f, 0.0f, 1.0f }, camera.rotation*DEG2RAD); + Matrix matScale = MatrixScale(camera.zoom, camera.zoom, 1.0f); + Matrix matTranslation = MatrixTranslate(camera.offset.x, camera.offset.y, 0.0f); + + matTransform = MatrixMultiply(MatrixMultiply(matOrigin, MatrixMultiply(matScale, matRotation)), matTranslation); + + return matTransform; +} + // Returns the screen space position from a 3d world space position Vector2 GetWorldToScreen(Vector3 position, Camera camera) { @@ -1313,10 +1540,22 @@ Vector2 GetWorldToScreen(Vector3 position, Camera camera) return screenPosition; } -// Get transform matrix for camera -Matrix GetCameraMatrix(Camera camera) +// Returns the screen space position for a 2d camera world space position +Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera) { - return MatrixLookAt(camera.position, camera.target, camera.up); + Matrix matCamera = GetCameraMatrix2D(camera); + Vector3 transform = Vector3Transform((Vector3){ position.x, position.y, 0 }, matCamera); + + return (Vector2){ transform.x, transform.y }; +} + +// Returns the world space position for a 2d camera screen space position +Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera) +{ + Matrix invMatCamera = MatrixInvert(GetCameraMatrix2D(camera)); + Vector3 transform = Vector3Transform((Vector3){ position.x, position.y, 0 }, invMatCamera); + + return (Vector2){ transform.x, transform.y }; } // Set target FPS (maximum) @@ -1357,6 +1596,11 @@ double GetTime(void) return (double)(time - baseTime)*1e-9; // Elapsed time since InitTimer() #endif + +#if defined(PLATFORM_UWP) + // Updated through messages + return currentTime; +#endif } // Returns hexadecimal value for a Color @@ -1378,6 +1622,19 @@ Vector4 ColorNormalize(Color color) return result; } +// Returns color from normalized values [0..1] +Color ColorFromNormalized(Vector4 normalized) +{ + Color result; + + result.r = normalized.x*255.0f; + result.g = normalized.y*255.0f; + result.b = normalized.z*255.0f; + result.a = normalized.w*255.0f; + + return result; +} + // Returns HSV values for a Color // NOTE: Hue is returned as degrees [0..360] Vector3 ColorToHSV(Color color) @@ -1386,11 +1643,11 @@ Vector3 ColorToHSV(Color color) Vector3 hsv = { 0.0f, 0.0f, 0.0f }; float min, max, delta; - min = rgb.x < rgb.y ? rgb.x : rgb.y; - min = min < rgb.z ? min : rgb.z; + min = rgb.x < rgb.y? rgb.x : rgb.y; + min = min < rgb.z? min : rgb.z; - max = rgb.x > rgb.y ? rgb.x : rgb.y; - max = max > rgb.z ? max : rgb.z; + max = rgb.x > rgb.y? rgb.x : rgb.y; + max = max > rgb.z? max : rgb.z; hsv.z = max; // Value delta = max - min; @@ -1430,6 +1687,41 @@ Vector3 ColorToHSV(Color color) return hsv; } +// 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) +{ + Color color = { 0, 0, 0, 255 }; + float h = hsv.x, s = hsv.y, v = hsv.z; + + // Red channel + float k = fmod((5.0f + h/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 = (v - v*s*k)*255; + + // Green channel + k = fmod((3.0f + h/60.0f), 6); + t = 4.0f - k; + k = (t < k)? t : k; + k = (k < 1)? k : 1; + k = (k > 0)? k : 0; + color.g = (v - v*s*k)*255; + + // Blue channel + k = fmod((1.0f + h/60.0f), 6); + t = 4.0f - k; + k = (t < k)? t : k; + k = (k < 1)? k : 1; + k = (k > 0)? k : 0; + color.b = (v - v*s*k)*255; + + return color; +} + // Returns a Color struct from hexadecimal value Color GetColor(int hexValue) { @@ -1443,8 +1735,6 @@ Color GetColor(int hexValue) return color; } - - // Returns a random value between min and max (both included) int GetRandomValue(int min, int max) { @@ -1455,7 +1745,7 @@ int GetRandomValue(int min, int max) min = tmp; } - return (rand()%(abs(max-min)+1) + min); + return (rand()%(abs(max - min) + 1) + min); } // Color fade-in or fade-out, alpha goes from 0.0f to 1.0f @@ -1467,35 +1757,44 @@ Color Fade(Color color, float alpha) return (Color){color.r, color.g, color.b, (unsigned char)(255.0f*alpha)}; } -// Activate raylib logo at startup (can be done with flags) -void ShowLogo(void) -{ - showLogo = true; -} - // Setup window configuration flags (view FLAGS) -void SetConfigFlags(unsigned char flags) +void SetConfigFlags(unsigned int flags) { configFlags = flags; - if (configFlags & FLAG_SHOW_LOGO) showLogo = true; if (configFlags & FLAG_FULLSCREEN_MODE) fullscreen = true; + if (configFlags & FLAG_WINDOW_ALWAYS_RUN) alwaysRun = true; } // NOTE TraceLog() function is located in [utils.h] // Takes a screenshot of current screen (saved a .png) +// NOTE: This function could work in any platform but some platforms: PLATFORM_ANDROID and PLATFORM_WEB +// have their own internal file-systems, to dowload image to user file-system some additional mechanism is required void TakeScreenshot(const char *fileName) { -#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) unsigned char *imgData = rlReadScreenPixels(renderWidth, renderHeight); - Image image = { imgData, renderWidth, renderHeight, 1, UNCOMPRESSED_R8G8B8A8 }; - ExportImage(image, fileName); - free(imgData); - TraceLog(LOG_INFO, "Screenshot taken: %s", fileName); + char path[512] = { 0 }; +#if defined(PLATFORM_ANDROID) + strcpy(path, internalDataPath); + strcat(path, "/"); + strcat(path, fileName); +#else + strcpy(path, fileName); #endif + + ExportImage(image, path); + RL_FREE(imgData); + +#if defined(PLATFORM_WEB) + // Download file from MEMFS (emscripten memory filesystem) + // saveFileFromMEMFSToDisk() function is defined in raylib/src/shell.html + emscripten_run_script(TextFormat("saveFileFromMEMFSToDisk('%s','%s')", GetFileName(path), GetFileName(path))); +#endif + + TraceLog(LOG_INFO, "Screenshot taken: %s", path); } // Check if the file exists @@ -1504,11 +1803,10 @@ bool FileExists(const char *fileName) bool result = false; #if defined(_WIN32) - if (_access(fileName, 0) != -1) + if (_access(fileName, 0) != -1) result = true; #else - if (access(fileName, F_OK) != -1) + if (access(fileName, F_OK) != -1) result = true; #endif - result = true; return result; } @@ -1517,29 +1815,36 @@ bool FileExists(const char *fileName) bool IsFileExtension(const char *fileName, const char *ext) { bool result = false; - const char *fileExt; + const char *fileExt = GetExtension(fileName); - if ((fileExt = strrchr(fileName, '.')) != NULL) + if (fileExt != NULL) { - #if defined(_WIN32) - result = true; - int extLen = strlen(ext); + int extCount = 0; + const char **checkExts = TextSplit(ext, ';', &extCount); - if (strlen(fileExt) == extLen) + for (int i = 0; i < extCount; i++) { - for (int i = 0; i < extLen; i++) + if (strcmp(fileExt, checkExts[i] + 1) == 0) { - if (tolower(fileExt[i]) != tolower(ext[i])) - { - result = false; - break; - } + result = true; + break; } } - else result = false; - #else - if (strcmp(fileExt, ext) == 0) result = true; - #endif + } + + return result; +} + +// Check if a directory path exists +bool DirectoryExists(const char *dirPath) +{ + bool result = false; + DIR *dir = opendir(dirPath); + + if (dir != NULL) + { + result = true; + closedir(dir); } return result; @@ -1566,64 +1871,76 @@ static const char *strprbrk(const char *s, const char *charset) // Get pointer to filename for a path string const char *GetFileName(const char *filePath) { - const char *fileName = strprbrk(filePath, "\\/"); + const char *fileName = NULL; + if (filePath != NULL) fileName = strprbrk(filePath, "\\/"); - if (!fileName || fileName == filePath) return filePath; + if (!fileName || (fileName == filePath)) return filePath; return fileName + 1; } -// Get filename string without extension (memory should be freed) +// Get filename string without extension (uses static string) const char *GetFileNameWithoutExt(const char *filePath) { - char *result, *lastDot, *lastSep; + #define MAX_FILENAMEWITHOUTEXT_LENGTH 128 - char nameDot = '.'; // Default filename to extension separator character - char pathSep = '/'; // Default filepath separator character + static char fileName[MAX_FILENAMEWITHOUTEXT_LENGTH]; + memset(fileName, 0, MAX_FILENAMEWITHOUTEXT_LENGTH); - // Error checks and allocate string - if (filePath == NULL) return NULL; + if (filePath != NULL) strcpy(fileName, GetFileName(filePath)); // Get filename with extension - // Try to allocate new string, same size as original - // NOTE: By default strlen() does not count the '\0' character - if ((result = malloc(strlen(filePath) + 1)) == NULL) return NULL; + int len = strlen(fileName); - strcpy(result, filePath); // Make a copy of the string - - // NOTE: strrchr() returns a pointer to the last occurrence of character - lastDot = strrchr(result, nameDot); - lastSep = (pathSep == 0) ? NULL : strrchr(result, pathSep); - - if (lastDot != NULL) // Check if it has an extension separator... + for (int i = 0; (i < len) && (i < MAX_FILENAMEWITHOUTEXT_LENGTH); i++) { - if (lastSep != NULL) // ...and it's before the extenstion separator... + if (fileName[i] == '.') { - if (lastSep < lastDot) - { - *lastDot = '\0'; // ...then remove it - } + // NOTE: We break on first '.' found + fileName[i] = '\0'; + break; } - else *lastDot = '\0'; // Has extension separator with no path separator } - return result; // Return the modified string + return fileName; } -// Get directory for a given fileName (with path) -const char *GetDirectoryPath(const char *fileName) +// Get directory for a given filePath +const char *GetDirectoryPath(const char *filePath) { const char *lastSlash = NULL; - static char filePath[MAX_FILEPATH_LENGTH]; - memset(filePath, 0, MAX_FILEPATH_LENGTH); + static char dirPath[MAX_FILEPATH_LENGTH]; + memset(dirPath, 0, MAX_FILEPATH_LENGTH); - lastSlash = strprbrk(fileName, "\\/"); + lastSlash = strprbrk(filePath, "\\/"); if (!lastSlash) return NULL; // NOTE: Be careful, strncpy() is not safe, it does not care about '\0' - strncpy(filePath, fileName, strlen(fileName) - (strlen(lastSlash) - 1)); - filePath[strlen(fileName) - strlen(lastSlash)] = '\0'; // Add '\0' manually + strncpy(dirPath, filePath, strlen(filePath) - (strlen(lastSlash) - 1)); + dirPath[strlen(filePath) - strlen(lastSlash)] = '\0'; // Add '\0' manually - return filePath; + return dirPath; +} + +// Get previous directory path for a given path +const char *GetPrevDirectoryPath(const char *dirPath) +{ + static char prevDirPath[MAX_FILEPATH_LENGTH]; + memset(prevDirPath, 0, MAX_FILEPATH_LENGTH); + int pathLen = strlen(dirPath); + + if (pathLen <= 3) strcpy(prevDirPath, dirPath); + + for (int i = (pathLen - 1); (i > 0) && (pathLen > 3); i--) + { + if ((dirPath[i] == '\\') || (dirPath[i] == '/')) + { + if (i == 2) i++; // Check for root: "C:\" + strncpy(prevDirPath, dirPath, i); + break; + } + } + + return prevDirPath; } // Get current working directory @@ -1646,8 +1963,8 @@ char **GetDirectoryFiles(const char *dirPath, int *fileCount) ClearDirectoryFiles(); // Memory allocation for MAX_DIRECTORY_FILES - dirFilesPath = (char **)malloc(sizeof(char *)*MAX_DIRECTORY_FILES); - for (int i = 0; i < MAX_DIRECTORY_FILES; i++) dirFilesPath[i] = (char *)malloc(sizeof(char)*MAX_FILEPATH_LENGTH); + 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); int counter = 0; struct dirent *ent; @@ -1680,11 +1997,12 @@ void ClearDirectoryFiles(void) { if (dirFilesCount > 0) { - for (int i = 0; i < dirFilesCount; i++) free(dirFilesPath[i]); + for (int i = 0; i < MAX_DIRECTORY_FILES; i++) RL_FREE(dirFilesPath[i]); - free(dirFilesPath); - dirFilesCount = 0; + RL_FREE(dirFilesPath); } + + dirFilesCount = 0; } // Change working directory, returns true if success @@ -1696,42 +2014,32 @@ bool ChangeDirectory(const char *dir) // Check if a file has been dropped into window bool IsFileDropped(void) { -#if defined(PLATFORM_DESKTOP) if (dropFilesCount > 0) return true; else return false; -#else - return false; -#endif } // Get dropped files names char **GetDroppedFiles(int *count) { -#if defined(PLATFORM_DESKTOP) *count = dropFilesCount; return dropFilesPath; -#else - return NULL; -#endif } // Clear dropped files paths buffer void ClearDroppedFiles(void) { -#if defined(PLATFORM_DESKTOP) if (dropFilesCount > 0) { - for (int i = 0; i < dropFilesCount; i++) free(dropFilesPath[i]); + for (int i = 0; i < dropFilesCount; i++) RL_FREE(dropFilesPath[i]); - free(dropFilesPath); + RL_FREE(dropFilesPath); dropFilesCount = 0; } -#endif } // Get file modification time (last write time) -RLAPI long GetFileModTime(const char *fileName) +long GetFileModTime(const char *fileName) { struct stat result = { 0 }; @@ -1745,13 +2053,39 @@ RLAPI long GetFileModTime(const char *fileName) return 0; } +// Compress data (DEFLATE algorythm) +unsigned char *CompressData(unsigned char *data, int dataLength, int *compDataLength) +{ + #define COMPRESSION_QUALITY_DEFLATE 8 + + unsigned char *compData = NULL; + +#if defined(SUPPORT_COMPRESSION_API) + compData = stbi_zlib_compress(data, dataLength, compDataLength, COMPRESSION_QUALITY_DEFLATE); +#endif + + return compData; +} + +// Decompress data (DEFLATE algorythm) +unsigned char *DecompressData(unsigned char *compData, int compDataLength, int *dataLength) +{ + char *data = NULL; + +#if defined(SUPPORT_COMPRESSION_API) + data = stbi_zlib_decode_malloc((char *)compData, compDataLength, dataLength); +#endif + + return (unsigned char *)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 StorageSaveValue(int position, int value) { FILE *storageFile = NULL; - char path[128]; + char path[512] = { 0 }; #if defined(PLATFORM_ANDROID) strcpy(path, internalDataPath); strcat(path, "/"); @@ -1791,7 +2125,7 @@ int StorageLoadValue(int position) { int value = 0; - char path[128]; + char path[512] = { 0 }; #if defined(PLATFORM_ANDROID) strcpy(path, internalDataPath); strcat(path, "/"); @@ -1825,30 +2159,31 @@ int StorageLoadValue(int position) } // Open URL with default system browser (if available) -// NOTE: This function is onlyl safe to use if you control the URL given. +// 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 void OpenURL(const char *url) { - // Small security check trying to avoid (partially) malicious code... + // Small security check trying to avoid (partially) malicious code... // sorry for the inconvenience when you hit this point... if (strchr(url, '\'') != NULL) { TraceLog(LOG_WARNING, "Provided URL does not seem to be valid."); - } else { - char *cmd = calloc(strlen(url) + 10, sizeof(char)); + } + else + { + char *cmd = (char *)RL_CALLOC(strlen(url) + 10, sizeof(char)); #if defined(_WIN32) - sprintf(cmd, "explorer '%s'", url); + sprintf(cmd, "explorer %s", url); #elif defined(__linux__) sprintf(cmd, "xdg-open '%s'", url); // Alternatives: firefox, x-www-browser #elif defined(__APPLE__) sprintf(cmd, "open '%s'", url); #endif system(cmd); - - free(cmd); + RL_FREE(cmd); } } @@ -1894,7 +2229,22 @@ bool IsKeyUp(int key) // Get the last key pressed int GetKeyPressed(void) { - return lastKeyPressed; + int value = 0; + + if (keyPressedQueueCount > 0) + { + // Get character from the queue head + value = keyPressedQueue[0]; + + // Shift elements 1 step toward the head. + for (int i = 0; i < (keyPressedQueueCount - 1); i++) keyPressedQueue[i] = keyPressedQueue[i + 1]; + + // Reset last character in the queue + keyPressedQueue[keyPressedQueueCount] = 0; + keyPressedQueueCount--; + } + + return value; } // Set a custom key to exit program @@ -2045,6 +2395,12 @@ bool IsMouseButtonPressed(int button) if ((currentMouseState[button] != previousMouseState[button]) && (currentMouseState[button] == 1)) pressed = true; #endif +/* +#if defined(PLATFORM_WEB) + Vector2 pos = GetTouchPosition(0); + if ((pos.x > 0) && (pos.y > 0)) pressed = true; // There was a touch! +#endif +*/ return pressed; } @@ -2092,7 +2448,7 @@ int GetMouseX(void) #if defined(PLATFORM_ANDROID) return (int)touchPosition[0].x; #else - return (int)(mousePosition.x*mouseScale); + return (int)((mousePosition.x + mouseOffset.x)*mouseScale.x); #endif } @@ -2100,39 +2456,62 @@ int GetMouseX(void) int GetMouseY(void) { #if defined(PLATFORM_ANDROID) - return (int)touchPosition[0].x; + return (int)touchPosition[0].y; #else - return (int)(mousePosition.y*mouseScale); + return (int)((mousePosition.y + mouseOffset.y)*mouseScale.y); #endif } // Returns mouse position XY Vector2 GetMousePosition(void) { + Vector2 position = { 0.0f, 0.0f }; + #if defined(PLATFORM_ANDROID) - return GetTouchPosition(0); + position = GetTouchPosition(0); #else - return (Vector2){ mousePosition.x*mouseScale, mousePosition.y*mouseScale }; + position = (Vector2){ (mousePosition.x + mouseOffset.x)*mouseScale.x, (mousePosition.y + mouseOffset.y)*mouseScale.y }; #endif +/* +#if defined(PLATFORM_WEB) + Vector2 pos = GetTouchPosition(0); + + // Touch position has priority over mouse position + if ((pos.x > 0) && (pos.y > 0)) position = pos; // There was a touch! +#endif +*/ + return position; } // Set mouse position XY -void SetMousePosition(Vector2 position) +void SetMousePosition(int x, int y) { - mousePosition = position; + mousePosition = (Vector2){ (float)x, (float)y }; #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) // NOTE: emscripten not implemented - glfwSetCursorPos(window, position.x, position.y); + glfwSetCursorPos(window, mousePosition.x, mousePosition.y); #endif +#if defined(PLATFORM_UWP) + UWPMessage *msg = CreateUWPMessage(); + msg->type = UWP_MSG_SET_MOUSE_LOCATION; + msg->paramVector0.x = mousePosition.x; + msg->paramVector0.y = mousePosition.y; + SendMessageToUWP(msg); +#endif +} + +// Set mouse offset +// NOTE: Useful when rendering to different size targets +void SetMouseOffset(int offsetX, int offsetY) +{ + mouseOffset = (Vector2){ (float)offsetX, (float)offsetY }; } // Set mouse scaling // NOTE: Useful when rendering to different size targets -void SetMouseScale(float scale) +void SetMouseScale(float scaleX, float scaleY) { -#if !defined(PLATFORM_ANDROID) - mouseScale = scale; -#endif + mouseScale = (Vector2){ scaleX, scaleY }; } // Returns mouse wheel movement Y @@ -2210,12 +2589,11 @@ static bool InitGraphicsDevice(int width, int height) screenWidth = width; // User desired width screenHeight = height; // User desired height + screenScaling = MatrixIdentity(); // No draw scaling required by default + // NOTE: Framebuffer (render area - renderWidth, renderHeight) could include black bars... // ...in top-down or left-right to match display aspect ratio (no weird scalings) - // Downscale matrix is required in case desired screen area is bigger than display area - downscaleView = MatrixIdentity(); - #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) glfwSetErrorCallback(ErrorCallback); @@ -2246,14 +2624,14 @@ static bool InitGraphicsDevice(int width, int height) // Screen size security check if (screenWidth <= 0) screenWidth = displayWidth; if (screenHeight <= 0) screenHeight = displayHeight; -#endif // defined(PLATFORM_DESKTOP) +#endif // PLATFORM_DESKTOP #if defined(PLATFORM_WEB) displayWidth = screenWidth; displayHeight = screenHeight; -#endif // defined(PLATFORM_WEB) +#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 @@ -2262,13 +2640,20 @@ 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) + // NOTE: If using external GLFW, it requires latest GLFW 3.3 for this functionality + 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 (configFlags & FLAG_WINDOW_HIDDEN) glfwWindowHint(GLFW_VISIBLE, GL_FALSE); // Visible window + else glfwWindowHint(GLFW_VISIBLE, GL_TRUE); // Window initially hidden + if (configFlags & FLAG_WINDOW_RESIZABLE) glfwWindowHint(GLFW_RESIZABLE, GL_TRUE); // Resizable window else glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); // Avoid window being resizable - if (configFlags & FLAG_WINDOW_UNDECORATED) glfwWindowHint(GLFW_DECORATED, GL_FALSE); // Border and buttons on Window - else glfwWindowHint(GLFW_DECORATED, GL_TRUE); // Decorated window + if (configFlags & 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 (configFlags & FLAG_WINDOW_TRANSPARENT) glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); // Transparent framebuffer @@ -2284,30 +2669,41 @@ static bool InitGraphicsDevice(int width, int height) // Check selection OpenGL version if (rlGetVersion() == OPENGL_21) { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); // Choose OpenGL major version (just hint) - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); // Choose OpenGL minor version (just hint) + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); // Choose OpenGL major version (just hint) + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); // Choose OpenGL minor version (just hint) } else if (rlGetVersion() == OPENGL_33) { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // Choose OpenGL major version (just hint) - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint) + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // Choose OpenGL major version (just hint) + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint) glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Profiles Hint: Only 3.3 and above! // Values: GLFW_OPENGL_CORE_PROFILE, GLFW_OPENGL_ANY_PROFILE, GLFW_OPENGL_COMPAT_PROFILE #if defined(__APPLE__) - glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // OSX Requires fordward compatibility + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); // OSX Requires fordward compatibility #else - glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_FALSE); // Fordward Compatibility Hint: Only 3.3 and above! + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE); // Fordward Compatibility Hint: Only 3.3 and above! +#endif + //glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); // Request OpenGL DEBUG context + } + else if (rlGetVersion() == OPENGL_ES_20) // Request OpenGL ES 2.0 context + { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); +#if defined(PLATFORM_DESKTOP) + glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); +#else + glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API); #endif - //glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE); // Request OpenGL DEBUG context } if (fullscreen) { // Obtain recommended displayWidth/displayHeight from a valid videomode for the monitor - int count; + int count = 0; const GLFWvidmode *modes = glfwGetVideoModes(glfwGetPrimaryMonitor(), &count); - // Get closest videomode to desired screenWidth/screenHeight + // Get closest video mode to desired screenWidth/screenHeight for (int i = 0; i < count; i++) { if (modes[i].width >= screenWidth) @@ -2328,9 +2724,11 @@ static bool InitGraphicsDevice(int width, int height) // framebuffer is rendered correctly but once displayed on a 16:9 monitor, it gets stretched // by the sides to fit all monitor space... - // At this point we need to manage render size vs screen size - // NOTE: This function uses and modifies global module variables: - // screenWidth/screenHeight - renderWidth/renderHeight - downscaleView + // Try to setup the most appropiate fullscreen framebuffer for the requested screenWidth/screenHeight + // It considers device display resolution mode and setups a framebuffer with black bars if required (render size/offset) + // Modified global variables: screenWidth/screenHeight - renderWidth/renderHeight - renderOffsetX/renderOffsetY - screenScaling + // TODO: It is a quite cumbersome solution to display size vs requested size, it should be reviewed or removed... + // HighDPI monitors are properly considered in a following similar function: SetupViewport() SetupFramebuffer(displayWidth, displayHeight); window = glfwCreateWindow(displayWidth, displayHeight, windowTitle, glfwGetPrimaryMonitor(), NULL); @@ -2385,15 +2783,15 @@ static bool InitGraphicsDevice(int width, int height) glfwSetCharCallback(window, CharCallback); glfwSetScrollCallback(window, ScrollCallback); glfwSetWindowIconifyCallback(window, WindowIconifyCallback); -#if defined(PLATFORM_DESKTOP) glfwSetDropCallback(window, WindowDropCallback); -#endif glfwMakeContextCurrent(window); // Try to disable GPU V-Sync by default, set framerate using SetTargetFPS() // NOTE: V-Sync can be enabled by graphic driver configuration +#if !defined(PLATFORM_WEB) glfwSwapInterval(0); +#endif #if defined(PLATFORM_DESKTOP) // Load OpenGL 3.3 extensions @@ -2409,7 +2807,7 @@ static bool InitGraphicsDevice(int width, int height) glfwSwapInterval(1); TraceLog(LOG_INFO, "Trying to enable VSYNC"); } -#endif // defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) +#endif // PLATFORM_DESKTOP || PLATFORM_WEB #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_UWP) fullscreen = true; @@ -2566,8 +2964,6 @@ static bool InitGraphicsDevice(int width, int height) } } - //SetupFramebuffer(displayWidth, displayHeight); - EGLint numConfigs = 0; if ((eglChooseConfig(display, framebufferAttribs, &config, 1, &numConfigs) == EGL_FALSE) || (numConfigs == 0)) { @@ -2671,20 +3067,20 @@ static bool InitGraphicsDevice(int width, int height) eglGetConfigAttrib(display, 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: screenWidth/screenHeight and renderWidth/renderHeight and downscaleView + // NOTE: This function use and modify global module variables: screenWidth/screenHeight and renderWidth/renderHeight and screenScaling SetupFramebuffer(displayWidth, displayHeight); ANativeWindow_setBuffersGeometry(androidApp->window, renderWidth, renderHeight, displayFormat); //ANativeWindow_setBuffersGeometry(androidApp->window, 0, 0, displayFormat); // Force use of native display size surface = eglCreateWindowSurface(display, config, androidApp->window, NULL); -#endif // defined(PLATFORM_ANDROID) +#endif // PLATFORM_ANDROID #if defined(PLATFORM_RPI) graphics_get_display_size(0, &displayWidth, &displayHeight); // At this point we need to manage render size vs screen size - // NOTE: This function use and modify global module variables: screenWidth/screenHeight and renderWidth/renderHeight and downscaleView + // NOTE: This function use and modify global module variables: screenWidth/screenHeight and renderWidth/renderHeight and screenScaling SetupFramebuffer(displayWidth, displayHeight); dstRect.x = 0; @@ -2718,7 +3114,8 @@ static bool InitGraphicsDevice(int width, int height) surface = eglCreateWindowSurface(display, config, &nativeWindow, NULL); //--------------------------------------------------------------------------------- -#endif // defined(PLATFORM_RPI) +#endif // PLATFORM_RPI + // There must be at least one frame displayed before the buffers are swapped //eglSwapInterval(display, 1); @@ -2739,25 +3136,28 @@ static bool InitGraphicsDevice(int width, int height) TraceLog(LOG_INFO, "Screen size: %i x %i", screenWidth, screenHeight); TraceLog(LOG_INFO, "Viewport offsets: %i, %i", renderOffsetX, renderOffsetY); } -#endif // defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) - - renderWidth = screenWidth; - renderHeight = screenHeight; +#endif // PLATFORM_ANDROID || PLATFORM_RPI // Initialize OpenGL context (states and resources) - // NOTE: screenWidth and screenHeight not used, just stored as globals + // NOTE: screenWidth and screenHeight not used, just stored as globals in rlgl rlglInit(screenWidth, screenHeight); - // Setup default viewport - SetupViewport(); + int fbWidth = renderWidth; + int fbHeight = renderHeight; - // Initialize internal projection and modelview matrices - // NOTE: Default to orthographic projection mode with top-left corner at (0,0) - rlMatrixMode(RL_PROJECTION); // Switch to PROJECTION matrix - rlLoadIdentity(); // Reset current matrix (PROJECTION) - rlOrtho(0, renderWidth - renderOffsetX, renderHeight - renderOffsetY, 0, 0.0f, 1.0f); - rlMatrixMode(RL_MODELVIEW); // Switch back to MODELVIEW matrix - rlLoadIdentity(); // Reset current matrix (MODELVIEW) +#if defined(PLATFORM_DESKTOP) && defined(SUPPORT_HIGH_DPI) + glfwGetFramebufferSize(window, &fbWidth, &fbHeight); + + // Screen scaling matrix is required in case desired screen area is different than display area + screenScaling = MatrixScale((float)fbWidth/screenWidth, (float)fbHeight/screenHeight, 1.0f); + SetMouseScale((float)screenWidth/fbWidth, (float)screenHeight/fbHeight); +#endif // PLATFORM_DESKTOP && SUPPORT_HIGH_DPI + + // Setup default viewport + SetupViewport(fbWidth, fbHeight); + + currentWidth = screenWidth; + currentHeight = screenHeight; ClearBackground(RAYWHITE); // Default background color for raylib games :P @@ -2767,22 +3167,26 @@ static bool InitGraphicsDevice(int width, int height) return true; } -// Set viewport parameters -static void SetupViewport(void) +// Set viewport for a provided width and height +static void SetupViewport(int width, int height) { -#if defined(__APPLE__) - // Get framebuffer size of current window - // NOTE: Required to handle HighDPI display correctly on OSX because framebuffer - // is automatically reasized to adapt to new DPI. - // When OS does that, it can be detected using GLFW3 callback: glfwSetFramebufferSizeCallback() - int fbWidth, fbHeight; - glfwGetFramebufferSize(window, &fbWidth, &fbHeight); - rlViewport(renderOffsetX/2, renderOffsetY/2, fbWidth - renderOffsetX, fbHeight - renderOffsetY); -#else - // Initialize screen viewport (area of the screen that you will actually draw to) - // NOTE: Viewport must be recalculated if screen is resized + renderWidth = width; + renderHeight = height; + + // Set viewport width and height + // NOTE: We consider render size 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) rlViewport(renderOffsetX/2, renderOffsetY/2, renderWidth - renderOffsetX, renderHeight - renderOffsetY); -#endif + + 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, renderWidth, renderHeight, 0, 0.0f, 1.0f); + + rlMatrixMode(RL_MODELVIEW); // Switch back to MODELVIEW matrix + rlLoadIdentity(); // Reset current matrix (MODELVIEW) } // Compute framebuffer size relative to screen size and display size @@ -2813,10 +3217,9 @@ static void SetupFramebuffer(int width, int height) renderOffsetY = 0; } - // NOTE: downscale matrix required! + // Screen scaling required float scaleRatio = (float)renderWidth/(float)screenWidth; - - downscaleView = MatrixScale(scaleRatio, scaleRatio, scaleRatio); + screenScaling = MatrixScale(scaleRatio, scaleRatio, 1.0f); // NOTE: We render to full display resolution! // We just need to calculate above parameters for downscale matrix and offsets @@ -2884,9 +3287,10 @@ static void InitTimer(void) // NOTE: Sleep() granularity could be around 10 ms, it means, Sleep() could // take longer than expected... for that reason we use the busy wait loop // http://stackoverflow.com/questions/43057578/c-programming-win32-games-sleep-taking-longer-than-expected +// http://www.geisswerks.com/ryan/FAQS/timing.html --> All about timming on Win32! static void Wait(float ms) { -#if defined(SUPPORT_BUSY_WAIT_LOOP) +#if defined(SUPPORT_BUSY_WAIT_LOOP) && !defined(PLATFORM_UWP) double prevTime = GetTime(); double nextTime = 0.0; @@ -2919,7 +3323,7 @@ static bool GetKeyStatus(int key) // NOTE: Android supports up to 260 keys if (key < 0 || key > 260) return false; else return currentKeyState[key]; -#elif defined(PLATFORM_RPI) +#elif defined(PLATFORM_RPI) || defined(PLATFORM_UWP) // NOTE: Keys states are filled in PollInputEvents() if (key < 0 || key > 511) return false; else return currentKeyState[key]; @@ -2934,12 +3338,105 @@ static bool GetMouseButtonStatus(int button) #elif defined(PLATFORM_ANDROID) // TODO: Check for virtual mouse? return false; -#elif defined(PLATFORM_RPI) +#elif defined(PLATFORM_RPI) || defined(PLATFORM_UWP) // NOTE: Mouse buttons states are filled in PollInputEvents() return currentMouseState[button]; #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; +} + +// Get gamepad axis generic to all platforms +static int GetGamepadAxis(int axis) +{ + int axs = GAMEPAD_AXIS_UNKNOWN; +#if defined(PLATFORM_DESKTOP) + switch (axis) + { + case GLFW_GAMEPAD_AXIS_LEFT_X: axs = GAMEPAD_AXIS_LEFT_X; break; + case GLFW_GAMEPAD_AXIS_LEFT_Y: axs = GAMEPAD_AXIS_LEFT_Y; break; + case GLFW_GAMEPAD_AXIS_RIGHT_X: axs = GAMEPAD_AXIS_RIGHT_X; break; + case GLFW_GAMEPAD_AXIS_RIGHT_Y: axs = GAMEPAD_AXIS_RIGHT_Y; break; + case GLFW_GAMEPAD_AXIS_LEFT_TRIGGER: axs = GAMEPAD_AXIS_LEFT_TRIGGER; break; + case GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER: axs = GAMEPAD_AXIS_RIGHT_TRIGGER; break; + } +#endif + +#if defined(PLATFORM_UWP) + axs = axis; // UWP will provide the correct axis +#endif + +#if defined(PLATFORM_WEB) + // Gamepad axis reference:https://www.w3.org/TR/gamepad/#gamepad-interface + switch (axis) + { + case 0: axs = GAMEPAD_AXIS_LEFT_X; + case 1: axs = GAMEPAD_AXIS_LEFT_Y; + case 2: axs = GAMEPAD_AXIS_RIGHT_X; + case 3: axs = GAMEPAD_AXIS_RIGHT_X; + } +#endif + + return axs; +} + // Poll (store) all input events static void PollInputEvents(void) { @@ -2949,8 +3446,8 @@ static void PollInputEvents(void) UpdateGestures(); #endif - // Reset last key pressed registered - lastKeyPressed = -1; + // Reset key pressed registered + keyPressedQueueCount = 0; #if !defined(PLATFORM_RPI) // Reset last gamepad button/axis registered state @@ -2960,7 +3457,16 @@ static void PollInputEvents(void) #if defined(PLATFORM_RPI) // Register previous keys states - for (int i = 0; i < 512; i++) previousKeyState[i] = currentKeyState[i]; + for (int i = 0; i < 512; i++)previousKeyState[i] = currentKeyState[i]; + + // Grab a keypress from the evdev fifo if avalable + if (lastKeyPressedEvdev.Head != lastKeyPressedEvdev.Tail) + { + keyPressedQueue[keyPressedQueueCount] = lastKeyPressedEvdev.Contents[lastKeyPressedEvdev.Tail]; // Read the key from the buffer + keyPressedQueueCount++; + + lastKeyPressedEvdev.Tail = (lastKeyPressedEvdev.Tail + 1) & 0x07; // Increment the tail pointer forwards and binary wraparound after 7 (fifo is 8 elements long) + } // Register previous mouse states previousMouseWheelY = currentMouseWheelY; @@ -2972,6 +3478,160 @@ static void PollInputEvents(void) } #endif +#if defined(PLATFORM_UWP) + // Register previous keys states + for (int i = 0; i < 512; i++) previousKeyState[i] = currentKeyState[i]; + + for (int i = 0; i < MAX_GAMEPADS; i++) + { + if (gamepadReady[i]) + { + for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) previousGamepadState[i][k] = currentGamepadState[i][k]; + } + } + + // Register previous mouse states + previousMouseWheelY = currentMouseWheelY; + currentMouseWheelY = 0; + for (int i = 0; i < 3; i++) previousMouseState[i] = currentMouseState[i]; + + // Loop over pending messages + while (HasMessageFromUWP()) + { + UWPMessage *msg = GetMessageFromUWP(); + + switch (msg->type) + { + case UWP_MSG_REGISTER_KEY: + { + // Convert from virtualKey + int actualKey = -1; + + switch (msg->paramInt0) + { + case 0x08: actualKey = KEY_BACKSPACE; break; + case 0x20: actualKey = KEY_SPACE; break; + case 0x1B: actualKey = KEY_ESCAPE; break; + case 0x0D: actualKey = KEY_ENTER; break; + case 0x2E: actualKey = KEY_DELETE; break; + case 0x27: actualKey = KEY_RIGHT; break; + case 0x25: actualKey = KEY_LEFT; break; + case 0x28: actualKey = KEY_DOWN; break; + case 0x26: actualKey = KEY_UP; break; + case 0x70: actualKey = KEY_F1; break; + case 0x71: actualKey = KEY_F2; break; + case 0x72: actualKey = KEY_F3; break; + case 0x73: actualKey = KEY_F4; break; + case 0x74: actualKey = KEY_F5; break; + case 0x75: actualKey = KEY_F6; break; + case 0x76: actualKey = KEY_F7; break; + case 0x77: actualKey = KEY_F8; break; + case 0x78: actualKey = KEY_F9; break; + case 0x79: actualKey = KEY_F10; break; + case 0x7A: actualKey = KEY_F11; break; + case 0x7B: actualKey = KEY_F12; break; + case 0xA0: actualKey = KEY_LEFT_SHIFT; break; + case 0xA2: actualKey = KEY_LEFT_CONTROL; break; + case 0xA4: actualKey = KEY_LEFT_ALT; break; + case 0xA1: actualKey = KEY_RIGHT_SHIFT; break; + case 0xA3: actualKey = KEY_RIGHT_CONTROL; break; + case 0xA5: actualKey = KEY_RIGHT_ALT; break; + case 0x30: actualKey = KEY_ZERO; break; + case 0x31: actualKey = KEY_ONE; break; + case 0x32: actualKey = KEY_TWO; break; + case 0x33: actualKey = KEY_THREE; break; + case 0x34: actualKey = KEY_FOUR; break; + case 0x35: actualKey = KEY_FIVE; break; + case 0x36: actualKey = KEY_SIX; break; + case 0x37: actualKey = KEY_SEVEN; break; + case 0x38: actualKey = KEY_EIGHT; break; + case 0x39: actualKey = KEY_NINE; break; + case 0x41: actualKey = KEY_A; break; + case 0x42: actualKey = KEY_B; break; + case 0x43: actualKey = KEY_C; break; + case 0x44: actualKey = KEY_D; break; + case 0x45: actualKey = KEY_E; break; + case 0x46: actualKey = KEY_F; break; + case 0x47: actualKey = KEY_G; break; + case 0x48: actualKey = KEY_H; break; + case 0x49: actualKey = KEY_I; break; + case 0x4A: actualKey = KEY_J; break; + case 0x4B: actualKey = KEY_K; break; + case 0x4C: actualKey = KEY_L; break; + case 0x4D: actualKey = KEY_M; break; + case 0x4E: actualKey = KEY_N; break; + case 0x4F: actualKey = KEY_O; break; + case 0x50: actualKey = KEY_P; break; + case 0x51: actualKey = KEY_Q; break; + case 0x52: actualKey = KEY_R; break; + case 0x53: actualKey = KEY_S; break; + case 0x54: actualKey = KEY_T; break; + case 0x55: actualKey = KEY_U; break; + case 0x56: actualKey = KEY_V; break; + case 0x57: actualKey = KEY_W; break; + case 0x58: actualKey = KEY_X; break; + case 0x59: actualKey = KEY_Y; break; + case 0x5A: actualKey = KEY_Z; break; + default: break; + } + + if (actualKey > -1) currentKeyState[actualKey] = msg->paramChar0; + + } break; + case UWP_MSG_REGISTER_CLICK: currentMouseState[msg->paramInt0] = msg->paramChar0; break; + case UWP_MSG_SCROLL_WHEEL_UPDATE: currentMouseWheelY += msg->paramInt0; break; + case UWP_MSG_UPDATE_MOUSE_LOCATION: mousePosition = msg->paramVector0; break; + case UWP_MSG_SET_GAMEPAD_ACTIVE: if (msg->paramInt0 < MAX_GAMEPADS) gamepadReady[msg->paramInt0] = msg->paramBool0; break; + case UWP_MSG_SET_GAMEPAD_BUTTON: + { + if ((msg->paramInt0 < MAX_GAMEPADS) && (msg->paramInt1 < MAX_GAMEPAD_BUTTONS)) currentGamepadState[msg->paramInt0][msg->paramInt1] = msg->paramChar0; + } break; + case UWP_MSG_SET_GAMEPAD_AXIS: + { + if ((msg->paramInt0 < MAX_GAMEPADS) && (msg->paramInt1 < MAX_GAMEPAD_AXIS)) gamepadAxisState[msg->paramInt0][msg->paramInt1] = msg->paramFloat0; + + // Register buttons for 2nd triggers + currentGamepadState[msg->paramInt0][GAMEPAD_BUTTON_LEFT_TRIGGER_2] = (char)(gamepadAxisState[msg->paramInt0][GAMEPAD_AXIS_LEFT_TRIGGER] > 0.1); + currentGamepadState[msg->paramInt0][GAMEPAD_BUTTON_RIGHT_TRIGGER_2] = (char)(gamepadAxisState[msg->paramInt0][GAMEPAD_AXIS_RIGHT_TRIGGER] > 0.1); + } break; + case UWP_MSG_SET_DISPLAY_DIMS: + { + displayWidth = msg->paramVector0.x; + displayHeight = msg->paramVector0.y; + } break; + case UWP_MSG_HANDLE_RESIZE: + { + eglQuerySurface(display, surface, EGL_WIDTH, &screenWidth); + eglQuerySurface(display, surface, EGL_HEIGHT, &screenHeight); + + // If window is resized, viewport and projection matrix needs to be re-calculated + rlViewport(0, 0, screenWidth, screenHeight); // Set viewport width and height + rlMatrixMode(RL_PROJECTION); // Switch to PROJECTION matrix + rlLoadIdentity(); // Reset current matrix (PROJECTION) + rlOrtho(0, screenWidth, screenHeight, 0, 0.0f, 1.0f); // Orthographic projection mode with top-left corner at (0,0) + rlMatrixMode(RL_MODELVIEW); // Switch back to MODELVIEW matrix + rlLoadIdentity(); // Reset current matrix (MODELVIEW) + rlClearScreenBuffers(); // Clear screen buffers (color and depth) + + // Window size must be updated to be used on 3D mode to get new aspect ratio (BeginMode3D()) + // NOTE: Be careful! GLFW3 will choose the closest fullscreen resolution supported by current monitor, + // for example, if reescaling back to 800x450 (desired), it could set 720x480 (closest fullscreen supported) + currentWidth = screenWidth; + currentHeight = screenHeight; + + // NOTE: Postprocessing texture is not scaled to new size + + windowResized = true; + + } break; + case UWP_MSG_SET_GAME_TIME: currentTime = msg->paramDouble0; break; + default: break; + } + + DeleteUWPMessage(msg); //Delete, we are done + } +#endif // PLATFORM_UWP + #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) // Mouse input polling double mouseX; @@ -2996,7 +3656,7 @@ static void PollInputEvents(void) #if defined(PLATFORM_DESKTOP) // Check if gamepads are ready - // NOTE: We do it here in case of disconection + // NOTE: We do it here in case of disconnection for (int i = 0; i < MAX_GAMEPADS; i++) { if (glfwJoystickPresent(i)) gamepadReady[i] = true; @@ -3013,36 +3673,42 @@ static void PollInputEvents(void) // Get current gamepad state // NOTE: There is no callback available, so we get it manually - const unsigned char *buttons; - int buttonsCount; + // Get remapped buttons + GLFWgamepadstate state; + glfwGetGamepadState(i, &state); // This remapps all gamepads so they have their buttons mapped like an xbox controller + const unsigned char *buttons = state.buttons; - buttons = glfwGetJoystickButtons(i, &buttonsCount); - - for (int k = 0; (buttons != NULL) && (k < buttonsCount) && (buttonsCount < MAX_GAMEPAD_BUTTONS); k++) + for (int k = 0; (buttons != NULL) && (k < GLFW_GAMEPAD_BUTTON_DPAD_LEFT + 1) && (k < MAX_GAMEPAD_BUTTONS); k++) { + const GamepadButton button = GetGamepadButton(k); + if (buttons[k] == GLFW_PRESS) { - currentGamepadState[i][k] = 1; - lastGamepadButtonPressed = k; + currentGamepadState[i][button] = 1; + lastGamepadButtonPressed = button; } - else currentGamepadState[i][k] = 0; + else currentGamepadState[i][button] = 0; } // Get current axis state - const float *axes; - int axisCount = 0; + const float *axes = state.axes; - axes = glfwGetJoystickAxes(i, &axisCount); - - for (int k = 0; (axes != NULL) && (k < axisCount) && (k < MAX_GAMEPAD_AXIS); k++) + for (int k = 0; (axes != NULL) && (k < GLFW_GAMEPAD_AXIS_LAST + 1) && (k < MAX_GAMEPAD_AXIS); k++) { - gamepadAxisState[i][k] = axes[k]; + const int axis = GetGamepadAxis(k); + gamepadAxisState[i][axis] = axes[k]; } - gamepadAxisCount = axisCount; + // Register buttons for 2nd triggers (because GLFW doesn't count these as buttons but rather axis) + currentGamepadState[i][GAMEPAD_BUTTON_LEFT_TRIGGER_2] = (char)(gamepadAxisState[i][GAMEPAD_AXIS_LEFT_TRIGGER] > 0.1); + currentGamepadState[i][GAMEPAD_BUTTON_RIGHT_TRIGGER_2] = (char)(gamepadAxisState[i][GAMEPAD_AXIS_RIGHT_TRIGGER] > 0.1); + + gamepadAxisCount = GLFW_GAMEPAD_AXIS_LAST; } } + windowResized = false; + #if defined(SUPPORT_EVENTS_WAITING) glfwWaitEvents(); #else @@ -3054,7 +3720,8 @@ static void PollInputEvents(void) // NOTE: GLFW3 joystick functionality not available in web #if defined(PLATFORM_WEB) // Get number of gamepads connected - int numGamepads = emscripten_get_num_gamepads(); + int numGamepads = 0; + if (emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS) numGamepads = emscripten_get_num_gamepads(); for (int i = 0; (i < numGamepads) && (i < MAX_GAMEPADS); i++) { @@ -3070,20 +3737,22 @@ 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) { - currentGamepadState[i][j] = 1; - lastGamepadButtonPressed = j; + currentGamepadState[i][button] = 1; + lastGamepadButtonPressed = button; } - else currentGamepadState[i][j] = 0; + else currentGamepadState[i][button] = 0; - //printf("Gamepad %d, button %d: Digital: %d, Analog: %g\n", gamepadState.index, j, gamepadState.digitalButton[j], gamepadState.analogButton[j]); + //TraceLog(LOG_DEBUG, "Gamepad %d, button %d: Digital: %d, Analog: %g", gamepadState.index, j, gamepadState.digitalButton[j], gamepadState.analogButton[j]); } // Register axis data for every connected gamepad for (int j = 0; (j < gamepadState.numAxes) && (j < MAX_GAMEPAD_AXIS); j++) { - gamepadAxisState[i][j] = gamepadState.axis[j]; + const int axis = GetGamepadAxis(j); + gamepadAxisState[i][axis] = gamepadState.axis[j]; } gamepadAxisCount = gamepadState.numAxes; @@ -3098,7 +3767,7 @@ static void PollInputEvents(void) // Poll Events (registered events) // NOTE: Activity is paused if not enabled (appEnabled) - while ((ident = ALooper_pollAll(appEnabled ? 0 : -1, NULL, &events,(void**)&source)) >= 0) + while ((ident = ALooper_pollAll(appEnabled? 0 : -1, NULL, &events,(void**)&source)) >= 0) { // Process this event if (source != NULL) source->process(androidApp, source); @@ -3113,13 +3782,12 @@ static void PollInputEvents(void) } #endif -#if defined(PLATFORM_RPI) - // NOTE: Mouse input events polling is done asynchonously in another pthread - MouseThread() - +#if defined(PLATFORM_RPI) && defined(SUPPORT_SSH_KEYBOARD_RPI) // NOTE: Keyboard reading could be done using input_event(s) reading or just read from stdin, - // we use method 2 (stdin) but maybe in a future we should change to method 1... + // we now use both methods inside here. 2nd method is still used for legacy purposes (Allows for input trough SSH console) ProcessKeyboard(); + // NOTE: Mouse input events polling is done asynchronously in another pthread - EventThread() // NOTE: Gamepad (Joystick) input events polling is done asynchonously in another pthread - GamepadThread() #endif } @@ -3129,16 +3797,6 @@ static void SwapBuffers(void) { #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) glfwSwapBuffers(window); -#if __APPLE__ - // Workaround for missing/erroneous initial rendering on macOS - if (windowNeedsUpdating) - { - // Desugared version of Objective C: [glfwGetNSGLContext(window) update] - ((id (*)(id, SEL))objc_msgSend)(glfwGetNSGLContext(window), sel_registerName("update")); - - windowNeedsUpdating--; - } -#endif #endif #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_UWP) @@ -3164,14 +3822,13 @@ static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, i { if (key == exitKey && action == GLFW_PRESS) { - glfwSetWindowShouldClose(window, GL_TRUE); + glfwSetWindowShouldClose(window, GLFW_TRUE); // NOTE: Before closing window, while loop must be left! } -#if defined(PLATFORM_DESKTOP) else if (key == GLFW_KEY_F12 && action == GLFW_PRESS) { - #if defined(SUPPORT_GIF_RECORDING) +#if defined(SUPPORT_GIF_RECORDING) if (mods == GLFW_MOD_CONTROL) { if (gifRecording) @@ -3179,6 +3836,12 @@ static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, i GifEnd(); gifRecording = false; + #if defined(PLATFORM_WEB) + // Download file from MEMFS (emscripten memory filesystem) + // saveFileFromMEMFSToDisk() function is defined in raylib/templates/web_shel/shell.html + emscripten_run_script(TextFormat("saveFileFromMEMFSToDisk('%s','%s')", TextFormat("screenrec%03i.gif", screenshotCounter - 1), TextFormat("screenrec%03i.gif", screenshotCounter - 1))); + #endif + TraceLog(LOG_INFO, "End animated GIF recording"); } else @@ -3186,31 +3849,32 @@ 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, 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(FormatText("screenrec%03i.gif", screenshotCounter), screenWidth, screenHeight, (int)(GetFrameTime()*10.0f), 8, false); + GifBegin(path, screenWidth, screenHeight, (int)(GetFrameTime()*10.0f), 8, false); screenshotCounter++; - TraceLog(LOG_INFO, "Begin animated GIF recording: %s", FormatText("screenrec%03i.gif", screenshotCounter)); + TraceLog(LOG_INFO, "Begin animated GIF recording: %s", TextFormat("screenrec%03i.gif", screenshotCounter)); } } else - #endif // SUPPORT_GIF_RECORDING - #if defined(SUPPORT_SCREEN_CAPTURE) +#endif // SUPPORT_GIF_RECORDING +#if defined(SUPPORT_SCREEN_CAPTURE) { - TakeScreenshot(FormatText("screenshot%03i.png", screenshotCounter)); + TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); screenshotCounter++; } - #endif // SUPPORT_SCREEN_CAPTURE - } -#endif // PLATFORM_DESKTOP - else - { - currentKeyState[key] = action; - - // NOTE: lastKeyPressed already registered on CharCallback() - //if (action == GLFW_PRESS) lastKeyPressed = key; +#endif // SUPPORT_SCREEN_CAPTURE } + else currentKeyState[key] = action; } // GLFW3 Mouse Button Callback, runs on mouse button pressed @@ -3283,7 +3947,13 @@ static void CharCallback(GLFWwindow *window, unsigned int key) // https://github.com/glfw/glfw/issues/668#issuecomment-166794907 // http://www.glfw.org/docs/latest/input_guide.html#input_char - lastKeyPressed = key; + // Check if there is space available in the queue + if (keyPressedQueueCount < MAX_CHARS_QUEUE) + { + // Add character to the queue + keyPressedQueue[keyPressedQueueCount] = key; + keyPressedQueueCount++; + } } // GLFW3 CursorEnter Callback, when cursor enters the window @@ -3297,24 +3967,17 @@ static void CursorEnterCallback(GLFWwindow *window, int enter) // NOTE: Window resizing not allowed by default static void WindowSizeCallback(GLFWwindow *window, int width, int height) { - // If window is resized, viewport and projection matrix needs to be re-calculated - rlViewport(0, 0, width, height); // Set viewport width and height - rlMatrixMode(RL_PROJECTION); // Switch to PROJECTION matrix - rlLoadIdentity(); // Reset current matrix (PROJECTION) - rlOrtho(0, width, height, 0, 0.0f, 1.0f); // Orthographic projection mode with top-left corner at (0,0) - rlMatrixMode(RL_MODELVIEW); // Switch back to MODELVIEW matrix - rlLoadIdentity(); // Reset current matrix (MODELVIEW) - rlClearScreenBuffers(); // Clear screen buffers (color and depth) + SetupViewport(width, height); // Reset viewport and projection matrix for new size - // Window size must be updated to be used on 3D mode to get new aspect ratio (BeginMode3D()) - // NOTE: Be careful! GLFW3 will choose the closest fullscreen resolution supported by current monitor, - // for example, if reescaling back to 800x450 (desired), it could set 720x480 (closest fullscreen supported) + // Set current screen size screenWidth = width; screenHeight = height; - renderWidth = width; - renderHeight = height; + currentWidth = width; + currentHeight = height; // NOTE: Postprocessing texture is not scaled to new size + + windowResized = true; } // GLFW3 WindowIconify Callback, runs when window is minimized/restored @@ -3323,21 +3986,19 @@ static void WindowIconifyCallback(GLFWwindow *window, int iconified) if (iconified) windowMinimized = true; // The window was iconified else windowMinimized = false; // The window was restored } -#endif -#if defined(PLATFORM_DESKTOP) // GLFW3 Window Drop Callback, runs when drop files into window -// NOTE: Paths are stored in dinamic memory for further retrieval +// NOTE: Paths are stored in dynamic memory for further retrieval // Everytime new files are dropped, old ones are discarded static void WindowDropCallback(GLFWwindow *window, int count, const char **paths) { ClearDroppedFiles(); - dropFilesPath = (char **)malloc(sizeof(char *)*count); + dropFilesPath = (char **)RL_MALLOC(sizeof(char *)*count); for (int i = 0; i < count; i++) { - dropFilesPath[i] = (char *)malloc(sizeof(char)*MAX_FILEPATH_LENGTH); // Max path length using MAX_FILEPATH_LENGTH set to 256 char + dropFilesPath[i] = (char *)RL_MALLOC(sizeof(char)*MAX_FILEPATH_LENGTH); strcpy(dropFilesPath[i], paths[i]); } @@ -3387,11 +4048,11 @@ static void AndroidCommandCallback(struct android_app *app, int32_t cmd) // Init hi-res timer InitTimer(); - #if defined(SUPPORT_DEFAULT_FONT) + #if defined(SUPPORT_DEFAULT_FONT) // Load default font // NOTE: External function (defined in module: text) - LoadDefaultFont(); - #endif + LoadFontDefault(); + #endif // TODO: GPU assets reload in case of lost focus (lost context) // NOTE: This problem has been solved just unbinding and rebinding context from display @@ -3407,13 +4068,6 @@ static void AndroidCommandCallback(struct android_app *app, int32_t cmd) } } */ - - // raylib logo appearing animation (if enabled) - if (showLogo) - { - SetTargetFPS(60); // Not required on Android - LogoAnimation(); - } } } } break; @@ -3504,7 +4158,9 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) { currentKeyState[keycode] = 1; // Key down - lastKeyPressed = keycode; + + keyPressedQueue[keyPressedQueueCount] = keycode; + keyPressedQueueCount++; } else currentKeyState[keycode] = 0; // Key up @@ -3519,7 +4175,9 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) { currentKeyState[keycode] = 1; // Key down - lastKeyPressed = keycode; + + keyPressedQueue[keyPressedQueueCount] = keycode; + keyPressedQueueCount++; } else currentKeyState[keycode] = 0; // Key up @@ -3583,7 +4241,6 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) ProcessGestureEvent(gestureEvent); } #else - // Support only simple touch position if (flags == AMOTION_EVENT_ACTION_DOWN) { @@ -3675,18 +4332,19 @@ static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent y = touchEvent->touches[i].canvasY; } - printf("%s, numTouches: %d %s%s%s%s\n", emscripten_event_type_to_string(eventType), event->numTouches, - event->ctrlKey ? " CTRL" : "", event->shiftKey ? " SHIFT" : "", event->altKey ? " ALT" : "", event->metaKey ? " META" : ""); + TraceLog(LOG_DEBUG, "%s, numTouches: %d %s%s%s%s", emscripten_event_type_to_string(eventType), event->numTouches, + event->ctrlKey? " CTRL" : "", event->shiftKey? " SHIFT" : "", event->altKey? " ALT" : "", event->metaKey? " META" : ""); for (int i = 0; i < event->numTouches; ++i) { const EmscriptenTouchPoint *t = &event->touches[i]; - printf(" %ld: screen: (%ld,%ld), client: (%ld,%ld), page: (%ld,%ld), isChanged: %d, onTarget: %d, canvas: (%ld, %ld)\n", + TraceLog(LOG_DEBUG, " %ld: screen: (%ld,%ld), client: (%ld,%ld), page: (%ld,%ld), isChanged: %d, onTarget: %d, canvas: (%ld, %ld)", t->identifier, t->screenX, t->screenY, t->clientX, t->clientY, t->pageX, t->pageY, t->isChanged, t->onTarget, t->canvasX, t->canvasY); } */ +#if defined(SUPPORT_GESTURES_SYSTEM) GestureEvent gestureEvent; // Register touch actions @@ -3721,6 +4379,17 @@ static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent // Gesture data is sent to gestures system for processing ProcessGestureEvent(gestureEvent); +#else + // Support only simple touch position + if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) + { + // Get first touch position + touchPosition[0] = (Vector2){ touchEvent->touches[0].targetX, touchEvent->touches[0].targetY }; + + touchPosition[0].x /= (float)GetScreenWidth(); + touchPosition[0].y /= (float)GetScreenHeight(); + } +#endif return 1; } @@ -3729,12 +4398,12 @@ static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) { /* - printf("%s: timeStamp: %g, connected: %d, index: %ld, numAxes: %d, numButtons: %d, id: \"%s\", mapping: \"%s\"\n", - eventType != 0 ? emscripten_event_type_to_string(eventType) : "Gamepad state", + TraceLog(LOG_DEBUG, "%s: timeStamp: %g, connected: %d, index: %ld, numAxes: %d, numButtons: %d, id: \"%s\", mapping: \"%s\"", + eventType != 0? emscripten_event_type_to_string(eventType) : "Gamepad state", gamepadEvent->timestamp, gamepadEvent->connected, gamepadEvent->index, gamepadEvent->numAxes, gamepadEvent->numButtons, gamepadEvent->id, gamepadEvent->mapping); - for(int i = 0; i < gamepadEvent->numAxes; ++i) printf("Axis %d: %g\n", i, gamepadEvent->axis[i]); - for(int i = 0; i < gamepadEvent->numButtons; ++i) printf("Button %d: Digital: %d, Analog: %g\n", i, gamepadEvent->digitalButton[i], gamepadEvent->analogButton[i]); + for (int i = 0; i < gamepadEvent->numAxes; ++i) TraceLog(LOG_DEBUG, "Axis %d: %g", i, gamepadEvent->axis[i]); + for (int i = 0; i < gamepadEvent->numButtons; ++i) TraceLog(LOG_DEBUG, "Button %d: Digital: %d, Analog: %g", i, gamepadEvent->digitalButton[i], gamepadEvent->analogButton[i]); */ if ((gamepadEvent->connected) && (gamepadEvent->index < MAX_GAMEPADS)) gamepadReady[gamepadEvent->index] = true; @@ -3747,6 +4416,8 @@ static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadE #endif #if defined(PLATFORM_RPI) + +#if defined(SUPPORT_SSH_KEYBOARD_RPI) // Initialize Keyboard system (using standard input) static void InitKeyboard(void) { @@ -3771,7 +4442,7 @@ static void InitKeyboard(void) // Set new keyboard settings (change occurs immediately) tcsetattr(STDIN_FILENO, TCSANOW, &keyboardNewSettings); - // NOTE: Reading directly from stdin will give chars already key-mapped by kernel to ASCII or UNICODE, we change that! -> WHY??? + // NOTE: Reading directly from stdin will give chars already key-mapped by kernel to ASCII or UNICODE // Save old keyboard mode to restore it at the end if (ioctl(STDIN_FILENO, KDGKBMODE, &defaultKeyboardMode) < 0) @@ -3803,21 +4474,20 @@ static void ProcessKeyboard(void) int bufferByteCount = 0; // Bytes available on the buffer char keysBuffer[MAX_KEYBUFFER_SIZE]; // Max keys to be read at a time - // Reset pressed keys array - for (int i = 0; i < 512; i++) currentKeyState[i] = 0; - // Read availables keycodes from stdin 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++) currentKeyState[i] = 0; + + // Check keys from event input workers (This is the new keyboard reading method) + for (int i = 0; i < 512; i++) currentKeyState[i] = currentKeyStateEvdev[i]; + // Fill all read bytes (looking for keys) for (int i = 0; i < bufferByteCount; i++) { TraceLog(LOG_DEBUG, "Bytes on keysBuffer: %i", bufferByteCount); - //printf("Key(s) bytes: "); - //for (int i = 0; i < bufferByteCount; i++) printf("0x%02x ", keysBuffer[i]); - //printf("\n"); - // NOTE: If (key == 0x1b), depending on next key, it could be a special keymap code! // Up -> 1b 5b 41 / Left -> 1b 5b 44 / Right -> 1b 5b 43 / Down -> 1b 5b 42 if (keysBuffer[i] == 0x1b) @@ -3869,8 +4539,20 @@ static void ProcessKeyboard(void) } } } - else if (keysBuffer[i] == 0x0a) currentKeyState[257] = 1; // raylib KEY_ENTER (don't mix with KEY_*) - else if (keysBuffer[i] == 0x7f) currentKeyState[259] = 1; // raylib KEY_BACKSPACE + else if (keysBuffer[i] == 0x0a) // raylib KEY_ENTER (don't mix with KEY_*) + { + currentKeyState[257] = 1; + + keyPressedQueue[keyPressedQueueCount] = 257; // Add keys pressed into queue + keyPressedQueueCount++; + } + else if (keysBuffer[i] == 0x7f) // raylib KEY_BACKSPACE + { + currentKeyState[259] = 1; + + keyPressedQueue[keyPressedQueueCount] = 257; // Add keys pressed into queue + keyPressedQueueCount++; + } else { TraceLog(LOG_DEBUG, "Pressed key (ASCII): 0x%02x", keysBuffer[i]); @@ -3881,6 +4563,9 @@ static void ProcessKeyboard(void) currentKeyState[(int)keysBuffer[i] - 32] = 1; } else currentKeyState[(int)keysBuffer[i]] = 1; + + keyPressedQueue[keyPressedQueueCount] = keysBuffer[i]; // Add keys pressed into queue + keyPressedQueueCount++; } } @@ -3906,11 +4591,12 @@ static void RestoreKeyboard(void) // Reconfigure keyboard to default mode ioctl(STDIN_FILENO, KDSKBMODE, defaultKeyboardMode); } +#endif //SUPPORT_SSH_KEYBOARD_RPI -// Mouse initialization (including mouse thread) -static void InitMouse(void) +// Initialise user input from evdev(/dev/input/event) this means mouse, keyboard or gamepad devices +static void InitEvdevInput(void) { - char Path[MAX_FILEPATH_LENGTH]; + char path[MAX_FILEPATH_LENGTH]; DIR *directory; struct dirent *entity; @@ -3920,28 +4606,32 @@ static void InitMouse(void) touchPosition[i].x = -1; touchPosition[i].y = -1; } + // Reset keypress buffer + lastKeyPressedEvdev.Head = 0; + lastKeyPressedEvdev.Tail = 0; + // Reset keyboard key state + for (int i = 0; i < 512; i++) currentKeyStateEvdev[i] = 0; // Open the linux directory of "/dev/input" directory = opendir(DEFAULT_EVDEV_PATH); + if (directory) { while ((entity = readdir(directory)) != NULL) { 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 + sprintf(path, "%s%s", DEFAULT_EVDEV_PATH, entity->d_name); + EventThreadSpawn(path); // Identify the device and spawn a thread for it } } closedir(directory); } - else - { - TraceLog(LOG_WARNING, "Unable to open linux event directory %s", DEFAULT_EVDEV_PATH); - } + else TraceLog(LOG_WARNING, "Unable 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) { #define BITS_PER_LONG (sizeof(long)*8) @@ -3964,8 +4654,8 @@ static void EventThreadSpawn(char *device) InputEventWorker *worker; - /////////////////////////////////// Open the device and allocate worker ///////////////////////////////////////////// - + // Open the device and allocate worker + //------------------------------------------------------------------------------------------------------- // Find a free spot in the workers array for (int i = 0; i < sizeof(eventWorkers)/sizeof(InputEventWorker); ++i) { @@ -3984,7 +4674,7 @@ static void EventThreadSpawn(char *device) } else { - TraceLog(LOG_WARNING, "Error creating input device thread for '%s': Out of worker slots", device); + TraceLog(LOG_WARNING, "Error creating input device thread for [%s]: Out of worker slots", device); return; } @@ -3992,12 +4682,12 @@ static void EventThreadSpawn(char *device) fd = open(device, O_RDONLY | O_NONBLOCK); if (fd < 0) { - TraceLog(LOG_WARNING, "Error creating input device thread for '%s': Can't open device (Err: %d)", device, worker->fd); + TraceLog(LOG_WARNING, "Error creating input device thread for [%s]: Can't open device (Error: %d)", device, worker->fd); return; } worker->fd = fd; - //Grab number on the end of the devices name "event" + // Grab number on the end of the devices name "event" int devNum = 0; char *ptrDevName = strrchr(device, 't'); worker->eventNum = -1; @@ -4008,13 +4698,13 @@ static void EventThreadSpawn(char *device) worker->eventNum = devNum; } - // At this point we have a connection to the device, - // but we don't yet know what the device is (Could be - // many things, even as simple as a power button) + // At this point we have a connection to the device, but we don't yet know what the device is. + // It could be many things, even as simple as a power button... + //------------------------------------------------------------------------------------------------------- - /////////////////////////////////// Identify the device ///////////////////////////////////////////// - - ioctl(fd, EVIOCGBIT(0, sizeof(evBits)), evBits); // Read a bitfield of the avalable device properties + // Identify the device + //------------------------------------------------------------------------------------------------------- + ioctl(fd, EVIOCGBIT(0, sizeof(evBits)), evBits); // Read a bitfield of the avalable device properties // Check for absolute input devices if (TEST_BIT(evBits, EV_ABS)) @@ -4086,24 +4776,25 @@ static void EventThreadSpawn(char *device) if (TEST_BIT(keyBits, KEY_SPACE)) worker->isKeyboard = true; // This is a keyboard } + //------------------------------------------------------------------------------------------------------- - - /////////////////////////////////// Decide what to do with the device ///////////////////////////////////////////// - if (worker->isTouch || worker->isMouse) + // Decide what to do with the device + //------------------------------------------------------------------------------------------------------- + if (worker->isTouch || worker->isMouse || worker->isKeyboard) { // Looks like a interesting device - TraceLog(LOG_INFO, "Opening input device '%s' (%s%s%s%s%s)", device, - worker->isMouse ? "mouse " : "", - worker->isMultitouch ? "multitouch " : "", - worker->isTouch ? "touchscreen " : "", - worker->isGamepad ? "gamepad " : "", - worker->isKeyboard ? "keyboard " : ""); + TraceLog(LOG_INFO, "Opening input device [%s] (%s%s%s%s%s)", device, + worker->isMouse? "mouse " : "", + worker->isMultitouch? "multitouch " : "", + worker->isTouch? "touchscreen " : "", + worker->isGamepad? "gamepad " : "", + worker->isKeyboard? "keyboard " : ""); // Create a thread for this device int error = pthread_create(&worker->threadId, NULL, &EventThread, (void *)worker); if (error != 0) { - TraceLog(LOG_WARNING, "Error creating input device thread for '%s': Can't create thread (Err: %d)", device, error); + TraceLog(LOG_WARNING, "Error creating input device thread for [%s]: Can't create thread (Error: %d)", device, error); worker->threadId = 0; close(fd); } @@ -4124,7 +4815,7 @@ static void EventThreadSpawn(char *device) { if (eventWorkers[i].threadId != 0) { - TraceLog(LOG_WARNING, "Duplicate touchscreen found, killing toucnscreen on event%d", i); + TraceLog(LOG_WARNING, "Duplicate touchscreen found, killing touchscreen on event: %d", i); pthread_cancel(eventWorkers[i].threadId); close(eventWorkers[i].fd); } @@ -4133,83 +4824,112 @@ static void EventThreadSpawn(char *device) #endif } else close(fd); // We are not interested in this device + //------------------------------------------------------------------------------------------------------- } +// Input device events reading thread static void *EventThread(void *arg) { + // 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: + // Currently non US keyboards will have the wrong mapping for some keys + static const int keymap_US[] = + { 0,256,49,50,51,52,53,54,55,56,57,48,45,61,259,258,81,87,69,82,84, + 89,85,73,79,80,91,93,257,341,65,83,68,70,71,72,74,75,76,59,39,96, + 340,92,90,88,67,86,66,78,77,44,46,47,344,332,342,32,280,290,291, + 292,293,294,295,296,297,298,299,282,281,327,328,329,333,324,325, + 326,334,321,322,323,320,330,0,85,86,300,301,89,90,91,92,93,94,95, + 335,345,331,283,346,101,268,265,266,263,262,269,264,267,260,261, + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,347,127, + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, + 192,193,194,0,0,0,0,0,200,201,202,203,204,205,206,207,208,209,210, + 211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226, + 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, }; + struct input_event event; - GestureEvent gestureEvent; InputEventWorker *worker = (InputEventWorker *)arg; - bool GestureNeedsUpdate = false; + + int touchAction = -1; + bool gestureUpdate = false; + int keycode; while (!windowShouldClose) { + // Try to read data from the device and only continue if successful if (read(worker->fd, &event, sizeof(event)) == (int)sizeof(event)) { - /////////////////////////////// Relative movement parsing //////////////////////////////////// + // Relative movement parsing if (event.type == EV_REL) { if (event.code == REL_X) { mousePosition.x += event.value; touchPosition[0].x = mousePosition.x; - gestureEvent.touchAction = TOUCH_MOVE; - GestureNeedsUpdate = true; + + #if defined(SUPPORT_GESTURES_SYSTEM) + touchAction = TOUCH_MOVE; + gestureUpdate = true; + #endif } if (event.code == REL_Y) { mousePosition.y += event.value; touchPosition[0].y = mousePosition.y; - gestureEvent.touchAction = TOUCH_MOVE; - GestureNeedsUpdate = true; + + #if defined(SUPPORT_GESTURES_SYSTEM) + touchAction = TOUCH_MOVE; + gestureUpdate = true; + #endif } - if (event.code == REL_WHEEL) - { - currentMouseWheelY += event.value; - } + if (event.code == REL_WHEEL) currentMouseWheelY += event.value; } - /////////////////////////////// Absolute movement parsing //////////////////////////////////// + // Absolute movement parsing if (event.type == EV_ABS) { // Basic movement if (event.code == ABS_X) { mousePosition.x = (event.value - worker->absRange.x)*screenWidth/worker->absRange.width; // Scale acording to absRange - gestureEvent.touchAction = TOUCH_MOVE; - GestureNeedsUpdate = true; + + #if defined(SUPPORT_GESTURES_SYSTEM) + touchAction = TOUCH_MOVE; + gestureUpdate = true; + #endif } if (event.code == ABS_Y) { mousePosition.y = (event.value - worker->absRange.y)*screenHeight/worker->absRange.height; // Scale acording to absRange - gestureEvent.touchAction = TOUCH_MOVE; - GestureNeedsUpdate = true; + + #if defined(SUPPORT_GESTURES_SYSTEM) + touchAction = TOUCH_MOVE; + gestureUpdate = true; + #endif } // 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; // Remeber the slot number for the folowing events if (event.code == ABS_MT_POSITION_X) { - if (worker->touchSlot < MAX_TOUCH_POINTS) - touchPosition[worker->touchSlot].x = (event.value - worker->absRange.x)*screenWidth/worker->absRange.width; // Scale acording to absRange + if (worker->touchSlot < MAX_TOUCH_POINTS) touchPosition[worker->touchSlot].x = (event.value - worker->absRange.x)*screenWidth/worker->absRange.width; // Scale acording to absRange } if (event.code == ABS_MT_POSITION_Y) { - if (worker->touchSlot < MAX_TOUCH_POINTS) - touchPosition[worker->touchSlot].y = (event.value - worker->absRange.y)*screenHeight/worker->absRange.height; // Scale acording to absRange + if (worker->touchSlot < MAX_TOUCH_POINTS) touchPosition[worker->touchSlot].y = (event.value - worker->absRange.y)*screenHeight/worker->absRange.height; // Scale acording to absRange } if (event.code == ABS_MT_TRACKING_ID) { - if ( (event.value < 0) && (worker->touchSlot < MAX_TOUCH_POINTS) ) + if ((event.value < 0) && (worker->touchSlot < MAX_TOUCH_POINTS)) { // Touch has ended for this point touchPosition[worker->touchSlot].x = -1; @@ -4218,46 +4938,101 @@ static void *EventThread(void *arg) } } - /////////////////////////////// Button parsing //////////////////////////////////// + // Button parsing if (event.type == EV_KEY) { - if((event.code == BTN_TOUCH) || (event.code == BTN_LEFT)) + // Mouse button parsing + if ((event.code == BTN_TOUCH) || (event.code == BTN_LEFT)) { currentMouseStateEvdev[MOUSE_LEFT_BUTTON] = event.value; - if (event.value > 0) gestureEvent.touchAction = TOUCH_DOWN; - else gestureEvent.touchAction = TOUCH_UP; - GestureNeedsUpdate = true; + + #if defined(SUPPORT_GESTURES_SYSTEM) + if (event.value > 0) touchAction = TOUCH_DOWN; + else touchAction = TOUCH_UP; + gestureUpdate = true; + #endif } if (event.code == BTN_RIGHT) currentMouseStateEvdev[MOUSE_RIGHT_BUTTON] = event.value; if (event.code == BTN_MIDDLE) currentMouseStateEvdev[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(currentKeyState))) + { + /* Disabled buffer !! + // Store the key information for raylib to later use + currentKeyStateEvdev[keycode] = event.value; + if (event.value > 0) + { + // Add the key int the fifo + lastKeyPressedEvdev.Contents[lastKeyPressedEvdev.Head] = keycode; // Put the data at the front of the fifo snake + lastKeyPressedEvdev.Head = (lastKeyPressedEvdev.Head + 1) & 0x07; // Increment the head pointer forwards and binary wraparound after 7 (fifo is 8 elements long) + // TODO: This fifo is not fully threadsafe with multiple writers, so multiple keyboards hitting a key at the exact same time could miss a key (double write to head before it was incremented) + } + */ + + currentKeyState[keycode] = event.value; + if (event.value == 1) + { + keyPressedQueue[keyPressedQueueCount] = keycode; // Register last key pressed + keyPressedQueueCount++; + } + + #if defined(SUPPORT_SCREEN_CAPTURE) + // Check screen capture key (raylib key: KEY_F12) + if (currentKeyState[301] == 1) + { + TakeScreenshot(FormatText("screenshot%03i.png", screenshotCounter)); + screenshotCounter++; + } + #endif + + if (currentKeyState[exitKey] == 1) windowShouldClose = true; + + TraceLog(LOG_DEBUG, "KEY%s ScanCode: %4i KeyCode: %4i",event.value == 0 ? "UP":"DOWN", event.code, keycode); + } + } } - /////////////////////////////// Screen confinement //////////////////////////////////// + // Screen confinement if (mousePosition.x < 0) mousePosition.x = 0; - if (mousePosition.x > screenWidth/mouseScale) mousePosition.x = screenWidth/mouseScale; + if (mousePosition.x > screenWidth/mouseScale.x) mousePosition.x = screenWidth/mouseScale.x; if (mousePosition.y < 0) mousePosition.y = 0; - if (mousePosition.y > screenHeight/mouseScale) mousePosition.y = screenHeight/mouseScale; + if (mousePosition.y > screenHeight/mouseScale.y) mousePosition.y = screenHeight/mouseScale.y; - /////////////////////////////// Gesture update //////////////////////////////////// - if (GestureNeedsUpdate) + // Gesture update + if (gestureUpdate) { + #if defined(SUPPORT_GESTURES_SYSTEM) + GestureEvent gestureEvent = { 0 }; + gestureEvent.pointCount = 0; + gestureEvent.touchAction = touchAction; + if (touchPosition[0].x >= 0) gestureEvent.pointCount++; if (touchPosition[1].x >= 0) gestureEvent.pointCount++; if (touchPosition[2].x >= 0) gestureEvent.pointCount++; if (touchPosition[3].x >= 0) gestureEvent.pointCount++; + gestureEvent.pointerId[0] = 0; gestureEvent.pointerId[1] = 1; gestureEvent.pointerId[2] = 2; gestureEvent.pointerId[3] = 3; + gestureEvent.position[0] = touchPosition[0]; gestureEvent.position[1] = touchPosition[1]; gestureEvent.position[2] = touchPosition[2]; gestureEvent.position[3] = touchPosition[3]; + ProcessGestureEvent(gestureEvent); + #endif } } else @@ -4361,117 +5136,3 @@ static void *GamepadThread(void *arg) return NULL; } #endif // PLATFORM_RPI - -// Plays raylib logo appearing animation -static void LogoAnimation(void) -{ -#if !defined(PLATFORM_WEB) - int logoPositionX = screenWidth/2 - 128; - int logoPositionY = screenHeight/2 - 128; - - int framesCounter = 0; - int lettersCount = 0; - - int topSideRecWidth = 16; - int leftSideRecHeight = 16; - - int bottomSideRecWidth = 16; - int rightSideRecHeight = 16; - - int state = 0; // Tracking animation states (State Machine) - float alpha = 1.0f; // Useful for fading - - while (!WindowShouldClose() && (state != 4)) // Detect window close button or ESC key - { - // Update - //---------------------------------------------------------------------------------- - if (state == 0) // State 0: Small box blinking - { - framesCounter++; - - if (framesCounter == 84) - { - state = 1; - framesCounter = 0; // Reset counter... will be used later... - } - } - else if (state == 1) // State 1: Top and left bars growing - { - topSideRecWidth += 4; - leftSideRecHeight += 4; - - if (topSideRecWidth == 256) state = 2; - } - else if (state == 2) // State 2: Bottom and right bars growing - { - bottomSideRecWidth += 4; - rightSideRecHeight += 4; - - if (bottomSideRecWidth == 256) state = 3; - } - else if (state == 3) // State 3: Letters appearing (one by one) - { - framesCounter++; - - if (framesCounter/12) // Every 12 frames, one more letter! - { - lettersCount++; - framesCounter = 0; - } - - if (lettersCount >= 10) // When all letters have appeared, just fade out everything - { - alpha -= 0.02f; - - if (alpha <= 0.0f) - { - alpha = 0.0f; - state = 4; - } - } - } - //---------------------------------------------------------------------------------- - - // Draw - //---------------------------------------------------------------------------------- - BeginDrawing(); - - ClearBackground(RAYWHITE); - - if (state == 0) - { - if ((framesCounter/12)%2) DrawRectangle(logoPositionX, logoPositionY, 16, 16, BLACK); - } - else if (state == 1) - { - DrawRectangle(logoPositionX, logoPositionY, topSideRecWidth, 16, BLACK); - DrawRectangle(logoPositionX, logoPositionY, 16, leftSideRecHeight, BLACK); - } - else if (state == 2) - { - DrawRectangle(logoPositionX, logoPositionY, topSideRecWidth, 16, BLACK); - DrawRectangle(logoPositionX, logoPositionY, 16, leftSideRecHeight, BLACK); - - DrawRectangle(logoPositionX + 240, logoPositionY, 16, rightSideRecHeight, BLACK); - DrawRectangle(logoPositionX, logoPositionY + 240, bottomSideRecWidth, 16, BLACK); - } - else if (state == 3) - { - DrawRectangle(logoPositionX, logoPositionY, topSideRecWidth, 16, Fade(BLACK, alpha)); - DrawRectangle(logoPositionX, logoPositionY + 16, 16, leftSideRecHeight - 32, Fade(BLACK, alpha)); - - DrawRectangle(logoPositionX + 240, logoPositionY + 16, 16, rightSideRecHeight - 32, Fade(BLACK, alpha)); - DrawRectangle(logoPositionX, logoPositionY + 240, bottomSideRecWidth, 16, Fade(BLACK, alpha)); - - DrawRectangle(screenWidth/2 - 112, screenHeight/2 - 112, 224, 224, Fade(RAYWHITE, alpha)); - - DrawText(SubText("raylib", 0, lettersCount), screenWidth/2 - 44, screenHeight/2 + 48, 50, Fade(BLACK, alpha)); - } - - EndDrawing(); - //---------------------------------------------------------------------------------- - } -#endif - - showLogo = false; // Prevent for repeating when reloading window (Android) -} diff --git a/raylib/core.go b/raylib/core.go index 5255658..783a45a 100644 --- a/raylib/core.go +++ b/raylib/core.go @@ -372,14 +372,9 @@ func Fade(color Color, alpha float32) Color { return v } -// ShowLogo - Activates raylib logo at startup (can be done with flags) -func ShowLogo() { - C.ShowLogo() -} - // SetConfigFlags - Setup some window configuration flags func SetConfigFlags(flags byte) { - cflags := (C.uchar)(flags) + cflags := (C.uint)(flags) C.SetConfigFlags(cflags) } @@ -590,15 +585,17 @@ func GetMousePosition() Vector2 { } // SetMousePosition - Set mouse position XY -func SetMousePosition(position Vector2) { - cposition := position.cptr() - C.SetMousePosition(*cposition) +func SetMousePosition(x, y int) { + cx := (C.int)(x) + cy := (C.int)(y) + C.SetMousePosition(cx, cy) } // SetMouseScale - Set mouse scaling -func SetMouseScale(scale float32) { - cscale := (C.float)(scale) - C.SetMouseScale(cscale) +func SetMouseScale(scaleX, scaleY float32) { + cscaleX := (C.float)(scaleX) + cscaleY := (C.float)(scaleY) + C.SetMouseScale(cscaleX, cscaleY) } // GetMouseWheelMove - Returns mouse wheel movement Y diff --git a/raylib/external/cgltf.h b/raylib/external/cgltf.h index ed04d54..7f3f77f 100644 --- a/raylib/external/cgltf.h +++ b/raylib/external/cgltf.h @@ -1,13 +1,92 @@ +/** + * cgltf - a single-file glTF 2.0 parser written in C99. + * + * Version: 1.2 + * + * Website: https://github.com/jkuhlmann/cgltf + * + * Distributed under the MIT License, see notice at the end of this file. + * + * Building: + * Include this file where you need the struct and function + * declarations. Have exactly one source file where you define + * `CGLTF_IMPLEMENTATION` before including this file to get the + * function definitions. + * + * Reference: + * `cgltf_result cgltf_parse(const cgltf_options*, const void*, + * cgltf_size, cgltf_data**)` parses both glTF and GLB data. If + * this function returns `cgltf_result_success`, you have to call + * `cgltf_free()` on the created `cgltf_data*` variable. + * Note that contents of external files for buffers and images are not + * automatically loaded. You'll need to read these files yourself using + * URIs in the `cgltf_data` structure. + * + * `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 callbacks. Should be zero-initialized + * to trigger default behavior. + * + * `cgltf_data` is the struct allocated and filled by `cgltf_parse()`. + * It generally mirrors the glTF format as described by the spec (see + * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0). + * + * `void cgltf_free(cgltf_data*)` frees the allocated `cgltf_data` + * variable. + * + * `cgltf_result cgltf_load_buffers(const cgltf_options*, cgltf_data*, + * const char* gltf_path)` can be optionally called to open and read buffer + * files using the `FILE*` APIs. The `gltf_path` argument is the path to + * the original glTF file, which allows the parser to resolve the path to + * buffer files. + * + * `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. + * + * `cgltf_result cgltf_parse_file(const cgltf_options* options, const + * char* path, cgltf_data** out_data)` can be used to open the given + * file using `FILE*` APIs and parse the data using `cgltf_parse()`. + * + * `cgltf_result cgltf_validate(cgltf_data*)` can be used to do additional + * checks to make sure the parsed glTF data is valid. + * + * `cgltf_node_transform_local` converts the translation / rotation / scale properties of a node + * into a mat4. + * + * `cgltf_node_transform_world` calls `cgltf_node_transform_local` on every ancestor in order + * to compute the root-to-node transformation. + * + * `cgltf_accessor_read_float` reads a certain element from an accessor and converts it to + * floating point, assuming that `cgltf_load_buffers` has already been called. The passed-in element + * size is the number of floats in the output buffer, which should be in the range [1, 16]. Returns + * false if the passed-in element_size is too small, or if the accessor is sparse. + * + * `cgltf_accessor_read_index` is similar to its floating-point counterpart, but it returns size_t + * and only works with single-component data types. + * + * `cgltf_result cgltf_copy_extras_json(const cgltf_data*, const cgltf_extras*, + * char* dest, cgltf_size* dest_size)` allows to retrieve the "extras" data that + * can be attached to many glTF objects (which can be arbitrary JSON data). The + * `cgltf_extras` struct stores the offsets of the start and end of the extras JSON data + * as it appears in the complete glTF JSON data. This function copies the extras data + * into the provided buffer. If `dest` is NULL, the length of the data is written into + * `dest_size`. You can then parse this data using your own JSON parser + * or, if you've included the cgltf implementation using the integrated JSMN JSON parser. + */ #ifndef CGLTF_H_INCLUDED__ #define CGLTF_H_INCLUDED__ +#include + #ifdef __cplusplus extern "C" { #endif - -typedef unsigned long cgltf_size; +typedef size_t cgltf_size; typedef float cgltf_float; +typedef int cgltf_int; typedef int cgltf_bool; typedef enum cgltf_file_type @@ -19,7 +98,7 @@ typedef enum cgltf_file_type typedef struct cgltf_options { - cgltf_file_type type; + cgltf_file_type type; /* invalid == auto detect */ cgltf_size json_token_count; /* 0 == auto */ void* (*memory_alloc)(void* user, cgltf_size size); void (*memory_free) (void* user, void* ptr); @@ -32,7 +111,11 @@ typedef enum cgltf_result cgltf_result_data_too_short, cgltf_result_unknown_format, cgltf_result_invalid_json, + cgltf_result_invalid_gltf, cgltf_result_invalid_options, + cgltf_result_file_not_found, + cgltf_result_io_error, + cgltf_result_out_of_memory, } cgltf_result; typedef enum cgltf_buffer_view_type @@ -48,29 +131,21 @@ typedef enum cgltf_attribute_type cgltf_attribute_type_position, cgltf_attribute_type_normal, cgltf_attribute_type_tangent, - cgltf_attribute_type_texcoord_0, - cgltf_attribute_type_texcoord_1, - cgltf_attribute_type_color_0, - cgltf_attribute_type_joints_0, - cgltf_attribute_type_weights_0, + cgltf_attribute_type_texcoord, + cgltf_attribute_type_color, + cgltf_attribute_type_joints, + cgltf_attribute_type_weights, } cgltf_attribute_type; typedef enum cgltf_component_type { cgltf_component_type_invalid, - cgltf_component_type_rgb_32f, - cgltf_component_type_rgba_32f, - cgltf_component_type_rg_32f, - cgltf_component_type_rg_8, - cgltf_component_type_rg_16, - cgltf_component_type_rgba_8, - cgltf_component_type_rgba_16, - cgltf_component_type_r_8, - cgltf_component_type_r_8u, - cgltf_component_type_r_16, - cgltf_component_type_r_16u, - cgltf_component_type_r_32u, - cgltf_component_type_r_32f, + cgltf_component_type_r_8, /* BYTE */ + cgltf_component_type_r_8u, /* UNSIGNED_BYTE */ + cgltf_component_type_r_16, /* SHORT */ + cgltf_component_type_r_16u, /* UNSIGNED_SHORT */ + cgltf_component_type_r_32u, /* UNSIGNED_INT */ + cgltf_component_type_r_32f, /* FLOAT */ } cgltf_component_type; typedef enum cgltf_type @@ -87,19 +162,60 @@ typedef enum cgltf_type typedef enum cgltf_primitive_type { - cgltf_type_points, - cgltf_type_lines, - cgltf_type_line_loop, - cgltf_type_line_strip, - cgltf_type_triangles, - cgltf_type_triangle_strip, - cgltf_type_triangle_fan, + cgltf_primitive_type_points, + cgltf_primitive_type_lines, + cgltf_primitive_type_line_loop, + cgltf_primitive_type_line_strip, + cgltf_primitive_type_triangles, + cgltf_primitive_type_triangle_strip, + cgltf_primitive_type_triangle_fan, } cgltf_primitive_type; +typedef enum cgltf_alpha_mode +{ + cgltf_alpha_mode_opaque, + cgltf_alpha_mode_mask, + cgltf_alpha_mode_blend, +} cgltf_alpha_mode; + +typedef enum cgltf_animation_path_type { + cgltf_animation_path_type_invalid, + cgltf_animation_path_type_translation, + cgltf_animation_path_type_rotation, + cgltf_animation_path_type_scale, + cgltf_animation_path_type_weights, +} cgltf_animation_path_type; + +typedef enum cgltf_interpolation_type { + cgltf_interpolation_type_linear, + cgltf_interpolation_type_step, + cgltf_interpolation_type_cubic_spline, +} cgltf_interpolation_type; + +typedef enum cgltf_camera_type { + cgltf_camera_type_invalid, + cgltf_camera_type_perspective, + cgltf_camera_type_orthographic, +} cgltf_camera_type; + +typedef enum cgltf_light_type { + cgltf_light_type_invalid, + cgltf_light_type_directional, + cgltf_light_type_point, + cgltf_light_type_spot, +} cgltf_light_type; + +typedef struct cgltf_extras { + cgltf_size start_offset; + cgltf_size end_offset; +} cgltf_extras; + typedef struct cgltf_buffer { cgltf_size size; char* uri; + void* data; /* loaded by cgltf_load_buffers */ + cgltf_extras extras; } cgltf_buffer; typedef struct cgltf_buffer_view @@ -109,100 +225,270 @@ typedef struct cgltf_buffer_view cgltf_size size; cgltf_size stride; /* 0 == automatically determined by accessor */ cgltf_buffer_view_type type; + cgltf_extras extras; } cgltf_buffer_view; +typedef struct cgltf_accessor_sparse +{ + cgltf_size count; + cgltf_buffer_view* indices_buffer_view; + cgltf_size indices_byte_offset; + cgltf_component_type indices_component_type; + cgltf_buffer_view* values_buffer_view; + cgltf_size values_byte_offset; + cgltf_extras extras; + cgltf_extras indices_extras; + cgltf_extras values_extras; +} cgltf_accessor_sparse; + typedef struct cgltf_accessor { cgltf_component_type component_type; + cgltf_bool normalized; cgltf_type type; cgltf_size offset; cgltf_size count; cgltf_size stride; cgltf_buffer_view* buffer_view; + cgltf_bool has_min; + cgltf_float min[16]; + cgltf_bool has_max; + cgltf_float max[16]; + cgltf_bool is_sparse; + cgltf_accessor_sparse sparse; + cgltf_extras extras; } cgltf_accessor; typedef struct cgltf_attribute { - cgltf_attribute_type name; + char* name; + cgltf_attribute_type type; + cgltf_int index; cgltf_accessor* data; } cgltf_attribute; - -typedef struct cgltf_rgba -{ - cgltf_float r; - cgltf_float g; - cgltf_float b; - cgltf_float a; -} cgltf_rgba; - typedef struct cgltf_image { + char* name; char* uri; cgltf_buffer_view* buffer_view; char* mime_type; + cgltf_extras extras; } cgltf_image; typedef struct cgltf_sampler { - cgltf_float mag_filter; - cgltf_float min_filter; - cgltf_float wrap_s; - cgltf_float wrap_t; + cgltf_int mag_filter; + cgltf_int min_filter; + cgltf_int wrap_s; + cgltf_int wrap_t; + cgltf_extras extras; } cgltf_sampler; typedef struct cgltf_texture { + char* name; cgltf_image* image; cgltf_sampler* sampler; + cgltf_extras extras; } cgltf_texture; +typedef struct cgltf_texture_transform +{ + cgltf_float offset[2]; + cgltf_float rotation; + cgltf_float scale[2]; + cgltf_int texcoord; +} cgltf_texture_transform; + typedef struct cgltf_texture_view { cgltf_texture* texture; - cgltf_size texcoord; - cgltf_float scale; + cgltf_int texcoord; + cgltf_float scale; /* equivalent to strength for occlusion_texture */ + cgltf_bool has_transform; + cgltf_texture_transform transform; + cgltf_extras extras; } cgltf_texture_view; -typedef struct cgltf_pbr +typedef struct cgltf_pbr_metallic_roughness { cgltf_texture_view base_color_texture; cgltf_texture_view metallic_roughness_texture; - cgltf_rgba base_color; + cgltf_float base_color_factor[4]; cgltf_float metallic_factor; cgltf_float roughness_factor; -} cgltf_pbr; + + cgltf_extras extras; +} cgltf_pbr_metallic_roughness; + +typedef struct cgltf_pbr_specular_glossiness +{ + cgltf_texture_view diffuse_texture; + cgltf_texture_view specular_glossiness_texture; + + cgltf_float diffuse_factor[4]; + cgltf_float specular_factor[3]; + cgltf_float glossiness_factor; +} cgltf_pbr_specular_glossiness; typedef struct cgltf_material { char* name; - cgltf_pbr pbr; - cgltf_rgba emissive_color; + cgltf_bool has_pbr_metallic_roughness; + cgltf_bool has_pbr_specular_glossiness; + cgltf_pbr_metallic_roughness pbr_metallic_roughness; + cgltf_pbr_specular_glossiness pbr_specular_glossiness; cgltf_texture_view normal_texture; - cgltf_texture_view emissive_texture; cgltf_texture_view occlusion_texture; + cgltf_texture_view emissive_texture; + cgltf_float emissive_factor[3]; + cgltf_alpha_mode alpha_mode; + cgltf_float alpha_cutoff; cgltf_bool double_sided; + cgltf_bool unlit; + cgltf_extras extras; } cgltf_material; +typedef struct cgltf_morph_target { + cgltf_attribute* attributes; + cgltf_size attributes_count; +} cgltf_morph_target; + typedef struct cgltf_primitive { cgltf_primitive_type type; cgltf_accessor* indices; cgltf_material* material; cgltf_attribute* attributes; cgltf_size attributes_count; + cgltf_morph_target* targets; + cgltf_size targets_count; + cgltf_extras extras; } cgltf_primitive; typedef struct cgltf_mesh { char* name; cgltf_primitive* primitives; cgltf_size primitives_count; + cgltf_float* weights; + cgltf_size weights_count; + cgltf_extras extras; } cgltf_mesh; +typedef struct cgltf_node cgltf_node; + +typedef struct cgltf_skin { + char* name; + cgltf_node** joints; + cgltf_size joints_count; + cgltf_node* skeleton; + cgltf_accessor* inverse_bind_matrices; + cgltf_extras extras; +} cgltf_skin; + +typedef struct cgltf_camera_perspective { + cgltf_float aspect_ratio; + cgltf_float yfov; + cgltf_float zfar; + cgltf_float znear; + cgltf_extras extras; +} cgltf_camera_perspective; + +typedef struct cgltf_camera_orthographic { + cgltf_float xmag; + cgltf_float ymag; + cgltf_float zfar; + cgltf_float znear; + cgltf_extras extras; +} cgltf_camera_orthographic; + +typedef struct cgltf_camera { + char* name; + cgltf_camera_type type; + union { + cgltf_camera_perspective perspective; + cgltf_camera_orthographic orthographic; + }; + cgltf_extras extras; +} cgltf_camera; + +typedef struct cgltf_light { + char* name; + cgltf_float color[3]; + cgltf_float intensity; + cgltf_light_type type; + cgltf_float range; + cgltf_float spot_inner_cone_angle; + cgltf_float spot_outer_cone_angle; +} cgltf_light; + +struct cgltf_node { + char* name; + cgltf_node* parent; + cgltf_node** children; + cgltf_size children_count; + cgltf_skin* skin; + cgltf_mesh* mesh; + cgltf_camera* camera; + cgltf_light* light; + cgltf_float* weights; + cgltf_size weights_count; + cgltf_bool has_translation; + cgltf_bool has_rotation; + cgltf_bool has_scale; + cgltf_bool has_matrix; + cgltf_float translation[3]; + cgltf_float rotation[4]; + cgltf_float scale[3]; + cgltf_float matrix[16]; + cgltf_extras extras; +}; + +typedef struct cgltf_scene { + char* name; + cgltf_node** nodes; + cgltf_size nodes_count; + cgltf_extras extras; +} cgltf_scene; + +typedef struct cgltf_animation_sampler { + cgltf_accessor* input; + cgltf_accessor* output; + cgltf_interpolation_type interpolation; + cgltf_extras extras; +} cgltf_animation_sampler; + +typedef struct cgltf_animation_channel { + cgltf_animation_sampler* sampler; + cgltf_node* target_node; + cgltf_animation_path_type target_path; + cgltf_extras extras; +} cgltf_animation_channel; + +typedef struct cgltf_animation { + char* name; + cgltf_animation_sampler* samplers; + cgltf_size samplers_count; + cgltf_animation_channel* channels; + cgltf_size channels_count; + cgltf_extras extras; +} cgltf_animation; + +typedef struct cgltf_asset { + char* copyright; + char* generator; + char* version; + char* min_version; + cgltf_extras extras; +} cgltf_asset; + typedef struct cgltf_data { - unsigned version; cgltf_file_type file_type; + void* file_data; + + cgltf_asset asset; cgltf_mesh* meshes; cgltf_size meshes_count; @@ -228,6 +514,37 @@ typedef struct cgltf_data cgltf_sampler* samplers; cgltf_size samplers_count; + cgltf_skin* skins; + cgltf_size skins_count; + + cgltf_camera* cameras; + cgltf_size cameras_count; + + cgltf_light* lights; + cgltf_size lights_count; + + cgltf_node* nodes; + cgltf_size nodes_count; + + cgltf_scene* scenes; + cgltf_size scenes_count; + + cgltf_scene* scene; + + cgltf_animation* animations; + cgltf_size animations_count; + + cgltf_extras extras; + + char** extensions_used; + cgltf_size extensions_used_count; + + char** extensions_required; + cgltf_size extensions_required_count; + + const char* json; + cgltf_size json_size; + const void* bin; cgltf_size bin_size; @@ -239,10 +556,38 @@ cgltf_result cgltf_parse( const cgltf_options* options, const void* data, cgltf_size size, - cgltf_data* out_data); + cgltf_data** out_data); + +cgltf_result cgltf_parse_file( + const cgltf_options* options, + const char* path, + cgltf_data** out_data); + +cgltf_result cgltf_load_buffers( + const cgltf_options* options, + 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); + +cgltf_result cgltf_validate( + cgltf_data* data); void cgltf_free(cgltf_data* data); +void cgltf_node_transform_local(const cgltf_node* node, cgltf_float* out_matrix); +void cgltf_node_transform_world(const cgltf_node* node, cgltf_float* out_matrix); + +cgltf_bool cgltf_accessor_read_float(const cgltf_accessor* accessor, cgltf_size index, cgltf_float* out, cgltf_size element_size); +cgltf_size cgltf_accessor_read_index(const cgltf_accessor* accessor, cgltf_size index); + +cgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras* extras, char* dest, cgltf_size* dest_size); + +#ifdef __cplusplus +} +#endif + #endif /* #ifndef CGLTF_H_INCLUDED__ */ /* @@ -262,7 +607,14 @@ void cgltf_free(cgltf_data* data); #include /* For uint8_t, uint32_t */ #include /* For strncpy */ #include /* For malloc, free */ +#include /* For fopen */ +#include /* For UINT_MAX etc */ +/* JSMN_PARENT_LINKS is necessary to make parsing large structures linear in input size */ +#define JSMN_PARENT_LINKS + +/* JSMN_STRICT is necessary to reject invalid JSON documents */ +#define JSMN_STRICT /* * -- jsmn.h start -- @@ -298,34 +650,52 @@ typedef struct { unsigned int toknext; /* next token to allocate */ int toksuper; /* superior token node, e.g parent object or array */ } jsmn_parser; -void jsmn_init(jsmn_parser *parser); -int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, unsigned int num_tokens); +static void jsmn_init(jsmn_parser *parser); +static int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, size_t num_tokens); /* * -- jsmn.h end -- */ -static const cgltf_size GltfHeaderSize = 12; -static const cgltf_size GltfChunkHeaderSize = 8; -static const uint32_t GltfMagic = 0x46546C67; -static const uint32_t GltfMagicJsonChunk = 0x4E4F534A; -static const uint32_t GltfMagicBinChunk = 0x004E4942; +static const cgltf_size GlbHeaderSize = 12; +static const cgltf_size GlbChunkHeaderSize = 8; +static const uint32_t GlbVersion = 2; +static const uint32_t GlbMagic = 0x46546C67; +static const uint32_t GlbMagicJsonChunk = 0x4E4F534A; +static const uint32_t GlbMagicBinChunk = 0x004E4942; -static void* cgltf_mem_alloc(void* user, cgltf_size size) +static void* cgltf_default_alloc(void* user, cgltf_size size) { + (void)user; return malloc(size); } -static void cgltf_mem_free(void* user, void* ptr) +static void cgltf_default_free(void* user, void* ptr) { + (void)user; free(ptr); } -static cgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* json_chunk, cgltf_size size, cgltf_data* out_data); - -cgltf_result cgltf_parse(const cgltf_options* options, const void* data, cgltf_size size, cgltf_data* out_data) +static void* cgltf_calloc(cgltf_options* options, size_t element_size, cgltf_size count) { - if (size < GltfHeaderSize) + if (SIZE_MAX / element_size < count) + { + return NULL; + } + void* result = options->memory_alloc(options->memory_user_data, element_size * count); + if (!result) + { + return NULL; + } + memset(result, 0, element_size * count); + return result; +} + +static cgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* json_chunk, cgltf_size size, cgltf_data** out_data); + +cgltf_result cgltf_parse(const cgltf_options* options, const void* data, cgltf_size size, cgltf_data** out_data) +{ + if (size < GlbHeaderSize) { return cgltf_result_data_too_short; } @@ -338,42 +708,49 @@ cgltf_result cgltf_parse(const cgltf_options* options, const void* data, cgltf_s cgltf_options fixed_options = *options; if (fixed_options.memory_alloc == NULL) { - fixed_options.memory_alloc = &cgltf_mem_alloc; + fixed_options.memory_alloc = &cgltf_default_alloc; } if (fixed_options.memory_free == NULL) { - fixed_options.memory_free = &cgltf_mem_free; + fixed_options.memory_free = &cgltf_default_free; } uint32_t tmp; // Magic memcpy(&tmp, data, 4); - if (tmp != GltfMagic) + if (tmp != GlbMagic) { if (fixed_options.type == cgltf_file_type_invalid) { fixed_options.type = cgltf_file_type_gltf; } - else + else if (fixed_options.type == cgltf_file_type_glb) { return cgltf_result_unknown_format; } } - memset(out_data, 0, sizeof(cgltf_data)); - out_data->memory_free = fixed_options.memory_free; - out_data->memory_user_data = fixed_options.memory_user_data; - if (fixed_options.type == cgltf_file_type_gltf) { - out_data->file_type = cgltf_file_type_gltf; - return cgltf_parse_json(&fixed_options, data, size, out_data); + cgltf_result json_result = cgltf_parse_json(&fixed_options, (const uint8_t*)data, size, out_data); + if (json_result != cgltf_result_success) + { + return json_result; + } + + (*out_data)->file_type = cgltf_file_type_gltf; + + return cgltf_result_success; } const uint8_t* ptr = (const uint8_t*)data; // Version memcpy(&tmp, ptr + 4, 4); - out_data->version = tmp; + uint32_t version = tmp; + if (version != GlbVersion) + { + return cgltf_result_unknown_format; + } // Total length memcpy(&tmp, ptr + 8, 4); @@ -382,32 +759,34 @@ cgltf_result cgltf_parse(const cgltf_options* options, const void* data, cgltf_s return cgltf_result_data_too_short; } - const uint8_t* json_chunk = ptr + GltfHeaderSize; + const uint8_t* json_chunk = ptr + GlbHeaderSize; + + if (GlbHeaderSize + GlbChunkHeaderSize > size) + { + return cgltf_result_data_too_short; + } // JSON chunk: length uint32_t json_length; memcpy(&json_length, json_chunk, 4); - if (GltfHeaderSize + GltfChunkHeaderSize + json_length > size) + if (GlbHeaderSize + GlbChunkHeaderSize + json_length > size) { return cgltf_result_data_too_short; } // JSON chunk: magic memcpy(&tmp, json_chunk + 4, 4); - if (tmp != GltfMagicJsonChunk) + if (tmp != GlbMagicJsonChunk) { return cgltf_result_unknown_format; } - json_chunk += GltfChunkHeaderSize; - cgltf_result json_result = cgltf_parse_json(&fixed_options, json_chunk, json_length, out_data); - if (json_result != cgltf_result_success) - { - return json_result; - } + json_chunk += GlbChunkHeaderSize; - out_data->file_type = cgltf_file_type_invalid; - if (GltfHeaderSize + GltfChunkHeaderSize + json_length + GltfChunkHeaderSize <= size) + const void* bin = 0; + cgltf_size bin_size = 0; + + if (GlbHeaderSize + GlbChunkHeaderSize + json_length + GlbChunkHeaderSize <= size) { // We can read another chunk const uint8_t* bin_chunk = json_chunk + json_length; @@ -415,23 +794,473 @@ cgltf_result cgltf_parse(const cgltf_options* options, const void* data, cgltf_s // Bin chunk: length uint32_t bin_length; memcpy(&bin_length, bin_chunk, 4); - if (GltfHeaderSize + GltfChunkHeaderSize + json_length + GltfChunkHeaderSize + bin_length > size) + if (GlbHeaderSize + GlbChunkHeaderSize + json_length + GlbChunkHeaderSize + bin_length > size) { return cgltf_result_data_too_short; } // Bin chunk: magic memcpy(&tmp, bin_chunk + 4, 4); - if (tmp != GltfMagicBinChunk) + if (tmp != GlbMagicBinChunk) { return cgltf_result_unknown_format; } - bin_chunk += GltfChunkHeaderSize; + bin_chunk += GlbChunkHeaderSize; - out_data->file_type = cgltf_file_type_glb; - out_data->bin = bin_chunk; - out_data->bin_size = bin_length; + bin = bin_chunk; + bin_size = bin_length; + } + + cgltf_result json_result = cgltf_parse_json(&fixed_options, json_chunk, json_length, out_data); + if (json_result != cgltf_result_success) + { + return json_result; + } + + (*out_data)->file_type = cgltf_file_type_glb; + (*out_data)->bin = bin; + (*out_data)->bin_size = bin_size; + + return cgltf_result_success; +} + +cgltf_result cgltf_parse_file(const cgltf_options* options, const char* path, cgltf_data** out_data) +{ + if (options == NULL) + { + 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; + + FILE* file = fopen(path, "rb"); + if (!file) + { + return cgltf_result_file_not_found; + } + + fseek(file, 0, SEEK_END); + + long length = ftell(file); + if (length < 0) + { + fclose(file); + return cgltf_result_io_error; + } + + fseek(file, 0, SEEK_SET); + + char* file_data = (char*)memory_alloc(options->memory_user_data, length); + if (!file_data) + { + fclose(file); + return cgltf_result_out_of_memory; + } + + cgltf_size file_size = (cgltf_size)length; + cgltf_size read_size = fread(file_data, 1, file_size, file); + + fclose(file); + + if (read_size != file_size) + { + memory_free(options->memory_user_data, file_data); + return cgltf_result_io_error; + } + + cgltf_result result = cgltf_parse(options, file_data, file_size, out_data); + + if (result != cgltf_result_success) + { + memory_free(options->memory_user_data, file_data); + return result; + } + + (*out_data)->file_data = file_data; + + return cgltf_result_success; +} + +static void cgltf_combine_paths(char* path, const char* base, const char* uri) +{ + const char* s0 = strrchr(base, '/'); + const char* s1 = strrchr(base, '\\'); + const char* slash = s0 ? (s1 && s1 > s0 ? s1 : s0) : s1; + + if (slash) + { + size_t prefix = slash - base + 1; + + strncpy(path, base, prefix); + strcpy(path + prefix, uri); + } + else + { + strcpy(path, uri); + } +} + +static cgltf_result cgltf_load_buffer_file(const cgltf_options* options, cgltf_size size, const char* uri, const char* gltf_path, void** out_data) +{ + 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; + + char* path = (char*)memory_alloc(options->memory_user_data, strlen(uri) + strlen(gltf_path) + 1); + if (!path) + { + return cgltf_result_out_of_memory; + } + + cgltf_combine_paths(path, gltf_path, uri); + + FILE* file = fopen(path, "rb"); + + memory_free(options->memory_user_data, path); + + if (!file) + { + return cgltf_result_file_not_found; + } + + char* file_data = (char*)memory_alloc(options->memory_user_data, size); + if (!file_data) + { + fclose(file); + return cgltf_result_out_of_memory; + } + + cgltf_size read_size = fread(file_data, 1, size, file); + + fclose(file); + + if (read_size != size) + { + memory_free(options->memory_user_data, file_data); + return cgltf_result_io_error; + } + + *out_data = file_data; + + return cgltf_result_success; +} + +cgltf_result cgltf_load_buffer_base64(const cgltf_options* options, cgltf_size size, const char* base64, void** out_data) +{ + 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; + + unsigned char* data = (unsigned char*)memory_alloc(options->memory_user_data, size); + if (!data) + { + return cgltf_result_out_of_memory; + } + + unsigned int buffer = 0; + unsigned int buffer_bits = 0; + + for (cgltf_size i = 0; i < size; ++i) + { + while (buffer_bits < 8) + { + char ch = *base64++; + + int index = + (unsigned)(ch - 'A') < 26 ? (ch - 'A') : + (unsigned)(ch - 'a') < 26 ? (ch - 'a') + 26 : + (unsigned)(ch - '0') < 10 ? (ch - '0') + 52 : + ch == '+' ? 62 : + ch == '/' ? 63 : + -1; + + if (index < 0) + { + memory_free(options->memory_user_data, data); + return cgltf_result_io_error; + } + + buffer = (buffer << 6) | index; + buffer_bits += 6; + } + + data[i] = (unsigned char)(buffer >> (buffer_bits - 8)); + buffer_bits -= 8; + } + + *out_data = data; + + return cgltf_result_success; +} + +cgltf_result cgltf_load_buffers(const cgltf_options* options, cgltf_data* data, const char* gltf_path) +{ + if (options == NULL) + { + return cgltf_result_invalid_options; + } + + if (data->buffers_count && data->buffers[0].data == NULL && data->buffers[0].uri == NULL && data->bin) + { + if (data->bin_size < data->buffers[0].size) + { + return cgltf_result_data_too_short; + } + + data->buffers[0].data = (void*)data->bin; + } + + for (cgltf_size i = 0; i < data->buffers_count; ++i) + { + if (data->buffers[i].data) + { + continue; + } + + const char* uri = data->buffers[i].uri; + + if (uri == NULL) + { + continue; + } + + if (strncmp(uri, "data:", 5) == 0) + { + const char* comma = strchr(uri, ','); + + if (comma && comma - uri >= 7 && strncmp(comma - 7, ";base64", 7) == 0) + { + cgltf_result res = cgltf_load_buffer_base64(options, data->buffers[i].size, comma + 1, &data->buffers[i].data); + + if (res != cgltf_result_success) + { + return res; + } + } + else + { + return cgltf_result_unknown_format; + } + } + else if (strstr(uri, "://") == NULL && gltf_path) + { + cgltf_result res = cgltf_load_buffer_file(options, data->buffers[i].size, uri, gltf_path, &data->buffers[i].data); + + if (res != cgltf_result_success) + { + return res; + } + } + else + { + return cgltf_result_unknown_format; + } + } + + return cgltf_result_success; +} + +static cgltf_size cgltf_calc_size(cgltf_type type, cgltf_component_type component_type); + +static cgltf_size cgltf_calc_index_bound(cgltf_buffer_view* buffer_view, cgltf_size offset, cgltf_component_type component_type, cgltf_size count) +{ + char* data = (char*)buffer_view->buffer->data + offset + buffer_view->offset; + cgltf_size bound = 0; + + switch (component_type) + { + case cgltf_component_type_r_8u: + for (size_t i = 0; i < count; ++i) + { + cgltf_size v = ((unsigned char*)data)[i]; + bound = bound > v ? bound : v; + } + break; + + case cgltf_component_type_r_16u: + for (size_t i = 0; i < count; ++i) + { + cgltf_size v = ((unsigned short*)data)[i]; + bound = bound > v ? bound : v; + } + break; + + case cgltf_component_type_r_32u: + for (size_t i = 0; i < count; ++i) + { + cgltf_size v = ((unsigned int*)data)[i]; + bound = bound > v ? bound : v; + } + break; + + default: + ; + } + + return bound; +} + +cgltf_result cgltf_validate(cgltf_data* data) +{ + for (cgltf_size i = 0; i < data->accessors_count; ++i) + { + cgltf_accessor* accessor = &data->accessors[i]; + + cgltf_size element_size = cgltf_calc_size(accessor->type, accessor->component_type); + + if (accessor->buffer_view) + { + 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; + } + } + + if (accessor->is_sparse) + { + cgltf_accessor_sparse* sparse = &accessor->sparse; + + cgltf_size indices_component_size = cgltf_calc_size(cgltf_type_scalar, sparse->indices_component_type); + 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; + } + + 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; + } + + 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; + } + } + } + } + + for (cgltf_size i = 0; i < data->buffer_views_count; ++i) + { + 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) + { + return cgltf_result_data_too_short; + } + } + + for (cgltf_size i = 0; i < data->meshes_count; ++i) + { + 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; + } + } + + 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; + } + + if (data->meshes[i].primitives[j].attributes_count) + { + cgltf_accessor* first = data->meshes[i].primitives[j].attributes[0].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; + } + } + + 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_accessor* indices = data->meshes[i].primitives[j].indices; + + 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; + } + + 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; + } + } + } + } + } + + for (cgltf_size i = 0; i < data->nodes_count; ++i) + { + 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; + } + } + } + + return cgltf_result_success; +} + +cgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras* extras, char* dest, cgltf_size* dest_size) +{ + cgltf_size json_size = extras->end_offset - extras->start_offset; + + if (!dest) + { + if (dest_size) + { + *dest_size = json_size + 1; + return cgltf_result_success; + } + return cgltf_result_invalid_options; + } + + if (*dest_size + 1 < json_size) + { + strncpy(dest, data->json + extras->start_offset, *dest_size - 1); + dest[*dest_size - 1] = 0; + } + else + { + strncpy(dest, data->json + extras->start_offset, json_size); + dest[json_size] = 0; } return cgltf_result_success; @@ -439,25 +1268,61 @@ cgltf_result cgltf_parse(const cgltf_options* options, const void* data, cgltf_s void cgltf_free(cgltf_data* data) { + if (!data) + { + return; + } + + data->memory_free(data->memory_user_data, data->asset.copyright); + data->memory_free(data->memory_user_data, data->asset.generator); + data->memory_free(data->memory_user_data, data->asset.version); + data->memory_free(data->memory_user_data, data->asset.min_version); + data->memory_free(data->memory_user_data, data->accessors); data->memory_free(data->memory_user_data, data->buffer_views); - for (cgltf_size i = 0; i < data->buffers_count; ++i) { + if (data->buffers[i].data != data->bin) + { + data->memory_free(data->memory_user_data, data->buffers[i].data); + } + data->memory_free(data->memory_user_data, data->buffers[i].uri); } + data->memory_free(data->memory_user_data, data->buffers); for (cgltf_size i = 0; i < data->meshes_count; ++i) { data->memory_free(data->memory_user_data, data->meshes[i].name); + for (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j) { + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k) + { + data->memory_free(data->memory_user_data, data->meshes[i].primitives[j].attributes[k].name); + } + data->memory_free(data->memory_user_data, data->meshes[i].primitives[j].attributes); + + 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) + { + data->memory_free(data->memory_user_data, data->meshes[i].primitives[j].targets[k].attributes[m].name); + } + + data->memory_free(data->memory_user_data, data->meshes[i].primitives[j].targets[k].attributes); + } + + data->memory_free(data->memory_user_data, data->meshes[i].primitives[j].targets); } + data->memory_free(data->memory_user_data, data->meshes[i].primitives); + data->memory_free(data->memory_user_data, data->meshes[i].weights); } + data->memory_free(data->memory_user_data, data->meshes); for (cgltf_size i = 0; i < data->materials_count; ++i) @@ -469,126 +1334,565 @@ void cgltf_free(cgltf_data* data) for (cgltf_size i = 0; i < data->images_count; ++i) { + 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); } data->memory_free(data->memory_user_data, data->images); - data->memory_free(data->memory_user_data, data->textures); - data->memory_free(data->memory_user_data, data->samplers); -} -#define CGLTF_CHECK_TOKTYPE(tok_, type_) if ((tok_).type != (type_)) { return -128; } - -static char cgltf_to_lower(char c) -{ - if (c >= 'A' && c <= 'Z') + for (cgltf_size i = 0; i < data->textures_count; ++i) { - c = 'a' + (c - 'A'); + data->memory_free(data->memory_user_data, data->textures[i].name); } - return c; + + data->memory_free(data->memory_user_data, data->textures); + + 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); + } + + data->memory_free(data->memory_user_data, data->skins); + + for (cgltf_size i = 0; i < data->cameras_count; ++i) + { + data->memory_free(data->memory_user_data, data->cameras[i].name); + } + + data->memory_free(data->memory_user_data, data->cameras); + + for (cgltf_size i = 0; i < data->lights_count; ++i) + { + data->memory_free(data->memory_user_data, data->lights[i].name); + } + + data->memory_free(data->memory_user_data, data->lights); + + for (cgltf_size i = 0; i < data->nodes_count; ++i) + { + 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); + } + + data->memory_free(data->memory_user_data, data->nodes); + + for (cgltf_size i = 0; i < data->scenes_count; ++i) + { + data->memory_free(data->memory_user_data, data->scenes[i].name); + data->memory_free(data->memory_user_data, data->scenes[i].nodes); + } + + data->memory_free(data->memory_user_data, data->scenes); + + for (cgltf_size i = 0; i < data->animations_count; ++i) + { + data->memory_free(data->memory_user_data, data->animations[i].name); + data->memory_free(data->memory_user_data, data->animations[i].samplers); + data->memory_free(data->memory_user_data, data->animations[i].channels); + } + + data->memory_free(data->memory_user_data, data->animations); + + for (cgltf_size i = 0; i < data->extensions_used_count; ++i) + { + data->memory_free(data->memory_user_data, data->extensions_used[i]); + } + + data->memory_free(data->memory_user_data, data->extensions_used); + + for (cgltf_size i = 0; i < data->extensions_required_count; ++i) + { + data->memory_free(data->memory_user_data, data->extensions_required[i]); + } + + data->memory_free(data->memory_user_data, data->extensions_required); + + data->memory_free(data->memory_user_data, data->file_data); + + data->memory_free(data->memory_user_data, data); } -static int cgltf_json_strcmp(jsmntok_t const* tok, const uint8_t* json_chunk, const char* str) +void cgltf_node_transform_local(const cgltf_node* node, cgltf_float* out_matrix) { - CGLTF_CHECK_TOKTYPE(*tok, JSMN_STRING); - int const str_len = strlen(str); - int const name_length = tok->end - tok->start; - if (name_length == str_len) + cgltf_float* lm = out_matrix; + + if (node->has_matrix) { - for (int i = 0; i < str_len; ++i) + memcpy(lm, node->matrix, sizeof(float) * 16); + } + else + { + float tx = node->translation[0]; + float ty = node->translation[1]; + float tz = node->translation[2]; + + float qx = node->rotation[0]; + float qy = node->rotation[1]; + float qz = node->rotation[2]; + float qw = node->rotation[3]; + + float sx = node->scale[0]; + float sy = node->scale[1]; + float sz = node->scale[2]; + + lm[0] = (1 - 2 * qy*qy - 2 * qz*qz) * sx; + lm[1] = (2 * qx*qy + 2 * qz*qw) * sy; + lm[2] = (2 * qx*qz - 2 * qy*qw) * sz; + lm[3] = 0.f; + + lm[4] = (2 * qx*qy - 2 * qz*qw) * sx; + lm[5] = (1 - 2 * qx*qx - 2 * qz*qz) * sy; + lm[6] = (2 * qy*qz + 2 * qx*qw) * sz; + lm[7] = 0.f; + + lm[8] = (2 * qx*qz + 2 * qy*qw) * sx; + lm[9] = (2 * qy*qz - 2 * qx*qw) * sy; + lm[10] = (1 - 2 * qx*qx - 2 * qy*qy) * sz; + lm[11] = 0.f; + + lm[12] = tx; + lm[13] = ty; + lm[14] = tz; + lm[15] = 1.f; + } +} + +void cgltf_node_transform_world(const cgltf_node* node, cgltf_float* out_matrix) +{ + cgltf_float* lm = out_matrix; + cgltf_node_transform_local(node, lm); + + const cgltf_node* parent = node->parent; + + while (parent) + { + float pm[16]; + cgltf_node_transform_local(parent, pm); + + for (int i = 0; i < 4; ++i) { - char const a = cgltf_to_lower(*((const char*)json_chunk + tok->start + i)); - char const b = cgltf_to_lower(*(str + i)); - if (a < b) - { - return -1; - } - else if (a > b) - { - return 1; - } + float l0 = lm[i * 4 + 0]; + float l1 = lm[i * 4 + 1]; + float l2 = lm[i * 4 + 2]; + + float r0 = l0 * pm[0] + l1 * pm[4] + l2 * pm[8]; + float r1 = l0 * pm[1] + l1 * pm[5] + l2 * pm[9]; + float r2 = l0 * pm[2] + l1 * pm[6] + l2 * pm[10]; + + lm[i * 4 + 0] = r0; + lm[i * 4 + 1] = r1; + lm[i * 4 + 2] = r2; } + + lm[12] += pm[12]; + lm[13] += pm[13]; + lm[14] += pm[14]; + + parent = parent->parent; + } +} + +static cgltf_size cgltf_component_read_index(const void* in, cgltf_component_type component_type) +{ + switch (component_type) + { + case cgltf_component_type_r_16: + return *((const int16_t*) in); + case cgltf_component_type_r_16u: + return *((const uint16_t*) in); + case cgltf_component_type_r_32u: + return *((const uint32_t*) in); + case cgltf_component_type_r_32f: + return (cgltf_size)*((const float*) in); + case cgltf_component_type_r_8: + return *((const int8_t*) in); + case cgltf_component_type_r_8u: + case cgltf_component_type_invalid: + default: + return *((const uint8_t*) in); + } +} + +static cgltf_float cgltf_component_read_float(const void* in, cgltf_component_type component_type, cgltf_bool normalized) +{ + if (component_type == cgltf_component_type_r_32f) + { + return *((const float*) in); + } + + if (normalized) + { + switch (component_type) + { + case cgltf_component_type_r_32u: + return *((const uint32_t*) in) / (float) UINT_MAX; + case cgltf_component_type_r_16: + return *((const int16_t*) in) / (float) SHRT_MAX; + case cgltf_component_type_r_16u: + return *((const uint16_t*) in) / (float) USHRT_MAX; + case cgltf_component_type_r_8: + return *((const int8_t*) in) / (float) SCHAR_MAX; + case cgltf_component_type_r_8u: + case cgltf_component_type_invalid: + default: + return *((const uint8_t*) in) / (float) CHAR_MAX; + } + } + + return (cgltf_float)cgltf_component_read_index(in, component_type); +} + +static cgltf_size cgltf_num_components(cgltf_type type); +static cgltf_size cgltf_component_size(cgltf_component_type component_type); + +static cgltf_bool cgltf_element_read_float(const uint8_t* element, cgltf_type type, cgltf_component_type component_type, cgltf_bool normalized, cgltf_float* out, cgltf_size element_size) +{ + cgltf_size num_components = cgltf_num_components(type); + + if (element_size < num_components) { return 0; } - return 128; -} -static int cgltf_json_to_int(jsmntok_t const* tok, const uint8_t* json_chunk) -{ - char tmp[128]; - CGLTF_CHECK_TOKTYPE(*tok, JSMN_PRIMITIVE); - int size = tok->end - tok->start; - strncpy(tmp, - (const char*)json_chunk + tok->start, - size); - tmp[size] = 0; - return atoi(tmp); -} + // There are three special cases for component extraction, see #data-alignment in the 2.0 spec. -static cgltf_float cgltf_json_to_float(jsmntok_t const* tok, const uint8_t* json_chunk) { - char tmp[128]; - CGLTF_CHECK_TOKTYPE(*tok, JSMN_PRIMITIVE); - int size = tok->end - tok->start; - strncpy(tmp, - (const char*)json_chunk + tok->start, - size); - tmp[size] = 0; - return atof(tmp); -} + cgltf_size component_size = cgltf_component_size(component_type); -static cgltf_bool cgltf_json_to_bool(jsmntok_t const* tok, const uint8_t* json_chunk) { - //TODO: error handling? - if (memcmp(json_chunk + tok->start, "true", 4) == 0) + if (type == cgltf_type_mat2 && component_size == 1) + { + out[0] = cgltf_component_read_float(element, component_type, normalized); + out[1] = cgltf_component_read_float(element + 1, component_type, normalized); + out[2] = cgltf_component_read_float(element + 4, component_type, normalized); + out[3] = cgltf_component_read_float(element + 5, component_type, normalized); return 1; + } + + if (type == cgltf_type_mat3 && component_size == 1) + { + out[0] = cgltf_component_read_float(element, component_type, normalized); + out[1] = cgltf_component_read_float(element + 1, component_type, normalized); + out[2] = cgltf_component_read_float(element + 2, component_type, normalized); + out[3] = cgltf_component_read_float(element + 4, component_type, normalized); + out[4] = cgltf_component_read_float(element + 5, component_type, normalized); + out[5] = cgltf_component_read_float(element + 6, component_type, normalized); + out[6] = cgltf_component_read_float(element + 8, component_type, normalized); + out[7] = cgltf_component_read_float(element + 9, component_type, normalized); + out[8] = cgltf_component_read_float(element + 10, component_type, normalized); + return 1; + } + + if (type == cgltf_type_mat3 && component_size == 2) + { + out[0] = cgltf_component_read_float(element, component_type, normalized); + out[1] = cgltf_component_read_float(element + 2, component_type, normalized); + out[2] = cgltf_component_read_float(element + 4, component_type, normalized); + out[3] = cgltf_component_read_float(element + 8, component_type, normalized); + out[4] = cgltf_component_read_float(element + 10, component_type, normalized); + out[5] = cgltf_component_read_float(element + 12, component_type, normalized); + out[6] = cgltf_component_read_float(element + 16, component_type, normalized); + out[7] = cgltf_component_read_float(element + 18, component_type, normalized); + out[8] = cgltf_component_read_float(element + 20, component_type, normalized); + return 1; + } + + for (cgltf_size i = 0; i < num_components; ++i) + { + out[i] = cgltf_component_read_float(element + component_size * i, component_type, normalized); + } + return 1; +} + + +cgltf_bool cgltf_accessor_read_float(const cgltf_accessor* accessor, cgltf_size index, cgltf_float* out, cgltf_size element_size) +{ + if (accessor->is_sparse || accessor->buffer_view == 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_float(element, accessor->type, accessor->component_type, accessor->normalized, out, element_size); +} + +cgltf_size cgltf_accessor_read_index(const cgltf_accessor* accessor, cgltf_size index) +{ + if (accessor->buffer_view) + { + 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_component_read_index(element, accessor->component_type); + } return 0; } +#define CGLTF_ERROR_JSON -1 +#define CGLTF_ERROR_NOMEM -2 + +#define CGLTF_CHECK_TOKTYPE(tok_, type_) if ((tok_).type != (type_)) { return CGLTF_ERROR_JSON; } +#define CGLTF_CHECK_KEY(tok_) if ((tok_).type != JSMN_STRING || (tok_).size == 0) { return CGLTF_ERROR_JSON; } /* checking size for 0 verifies that a value follows the key */ + +#define CGLTF_PTRINDEX(type, idx) (type*)(cgltf_size)(idx + 1) +#define CGLTF_PTRFIXUP(var, data, size) if (var) { if ((cgltf_size)var > size) { return CGLTF_ERROR_JSON; } var = &data[(cgltf_size)var-1]; } +#define CGLTF_PTRFIXUP_REQ(var, data, size) if (!var || (cgltf_size)var > size) { return CGLTF_ERROR_JSON; } var = &data[(cgltf_size)var-1]; + +static int cgltf_json_strcmp(jsmntok_t const* tok, const uint8_t* json_chunk, const char* str) +{ + CGLTF_CHECK_TOKTYPE(*tok, JSMN_STRING); + size_t const str_len = strlen(str); + size_t const name_length = tok->end - tok->start; + return (str_len == name_length) ? strncmp((const char*)json_chunk + tok->start, str, str_len) : 128; +} + +static int cgltf_json_to_int(jsmntok_t const* tok, const uint8_t* json_chunk) +{ + CGLTF_CHECK_TOKTYPE(*tok, JSMN_PRIMITIVE); + char tmp[128]; + int size = (cgltf_size)(tok->end - tok->start) < sizeof(tmp) ? tok->end - tok->start : sizeof(tmp) - 1; + strncpy(tmp, (const char*)json_chunk + tok->start, size); + tmp[size] = 0; + return atoi(tmp); +} + +static cgltf_float cgltf_json_to_float(jsmntok_t const* tok, const uint8_t* json_chunk) +{ + CGLTF_CHECK_TOKTYPE(*tok, JSMN_PRIMITIVE); + char tmp[128]; + int size = (cgltf_size)(tok->end - tok->start) < sizeof(tmp) ? tok->end - tok->start : sizeof(tmp) - 1; + strncpy(tmp, (const char*)json_chunk + tok->start, size); + tmp[size] = 0; + return (cgltf_float)atof(tmp); +} + +static cgltf_bool cgltf_json_to_bool(jsmntok_t const* tok, const uint8_t* json_chunk) +{ + int size = tok->end - tok->start; + return size == 4 && memcmp(json_chunk + tok->start, "true", 4) == 0; +} + static int cgltf_skip_json(jsmntok_t const* tokens, int i) { - if (tokens[i].type == JSMN_ARRAY) + int end = i + 1; + + while (i < end) { - int size = tokens[i].size; - ++i; - for (int j = 0; j < size; ++j) + switch (tokens[i].type) { - i = cgltf_skip_json(tokens, i); + case JSMN_OBJECT: + end += tokens[i].size * 2; + break; + + case JSMN_ARRAY: + end += tokens[i].size; + break; + + case JSMN_PRIMITIVE: + case JSMN_STRING: + break; + + default: + return -1; } + + i++; } - else if (tokens[i].type == JSMN_OBJECT) + + return i; +} + +static void cgltf_fill_float_array(float* out_array, int size, float value) +{ + for (int j = 0; j < size; ++j) { - int size = tokens[i].size; + out_array[j] = value; + } +} + +static int cgltf_parse_json_float_array(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, float* out_array, int size) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); + if (tokens[i].size != size) + { + return CGLTF_ERROR_JSON; + } + ++i; + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_array[j] = cgltf_json_to_float(tokens + i, json_chunk); ++i; - for (int j = 0; j < size; ++j) - { - i = cgltf_skip_json(tokens, i); - i = cgltf_skip_json(tokens, i); - } - } - else if (tokens[i].type == JSMN_PRIMITIVE - || tokens[i].type == JSMN_STRING) - { - return i + 1; } return i; } +static int cgltf_parse_json_string(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, char** out_string) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_STRING); + if (*out_string) + { + return CGLTF_ERROR_JSON; + } + int size = tokens[i].end - tokens[i].start; + char* result = (char*)options->memory_alloc(options->memory_user_data, size + 1); + if (!result) + { + return CGLTF_ERROR_NOMEM; + } + strncpy(result, (const char*)json_chunk + tokens[i].start, size); + result[size] = 0; + *out_string = result; + return i + 1; +} -static int cgltf_parse_json_primitive(cgltf_options* options, jsmntok_t const* tokens, int i, - const uint8_t* json_chunk, - cgltf_primitive* out_prim) +static int cgltf_parse_json_array(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, size_t element_size, void** out_array, cgltf_size* out_size) +{ + (void)json_chunk; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); + if (*out_array) + { + return CGLTF_ERROR_JSON; + } + int size = tokens[i].size; + void* result = cgltf_calloc(options, element_size, size); + if (!result) + { + return CGLTF_ERROR_NOMEM; + } + *out_array = result; + *out_size = size; + return i + 1; +} + +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; + } + + 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) +{ + const char* us = strchr(name, '_'); + size_t len = us ? us - name : strlen(name); + + if (len == 8 && strncmp(name, "POSITION", 8) == 0) + { + *out_type = cgltf_attribute_type_position; + } + else if (len == 6 && strncmp(name, "NORMAL", 6) == 0) + { + *out_type = cgltf_attribute_type_normal; + } + else if (len == 7 && strncmp(name, "TANGENT", 7) == 0) + { + *out_type = cgltf_attribute_type_tangent; + } + else if (len == 8 && strncmp(name, "TEXCOORD", 8) == 0) + { + *out_type = cgltf_attribute_type_texcoord; + } + else if (len == 5 && strncmp(name, "COLOR", 5) == 0) + { + *out_type = cgltf_attribute_type_color; + } + else if (len == 6 && strncmp(name, "JOINTS", 6) == 0) + { + *out_type = cgltf_attribute_type_joints; + } + else if (len == 7 && strncmp(name, "WEIGHTS", 7) == 0) + { + *out_type = cgltf_attribute_type_weights; + } + else + { + *out_type = cgltf_attribute_type_invalid; + } + + if (us && *out_type != cgltf_attribute_type_invalid) + { + *out_index = atoi(us + 1); + } +} + +static int cgltf_parse_json_attribute_list(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_attribute** out_attributes, cgltf_size* out_attributes_count) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + if (*out_attributes) + { + return CGLTF_ERROR_JSON; + } + + *out_attributes_count = tokens[i].size; + *out_attributes = (cgltf_attribute*)cgltf_calloc(options, sizeof(cgltf_attribute), *out_attributes_count); + ++i; + + if (!*out_attributes) + { + return CGLTF_ERROR_NOMEM; + } + + for (cgltf_size j = 0; j < *out_attributes_count; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + i = cgltf_parse_json_string(options, tokens, i, json_chunk, &(*out_attributes)[j].name); + if (i < 0) + { + return CGLTF_ERROR_JSON; + } + + cgltf_parse_attribute_type((*out_attributes)[j].name, &(*out_attributes)[j].type, &(*out_attributes)[j].index); + + (*out_attributes)[j].data = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + + return i; +} + +static int cgltf_parse_json_extras(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_extras* out_extras) +{ + (void)json_chunk; + out_extras->start_offset = tokens[i].start; + out_extras->end_offset = tokens[i].end; + i = cgltf_skip_json(tokens, 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); + + out_prim->type = cgltf_primitive_type_triangles; + int size = tokens[i].size; ++i; - out_prim->indices = (void* )-1; - out_prim->material = NULL; - for (int j = 0; j < size; ++j) { + CGLTF_CHECK_KEY(tokens[i]); + if (cgltf_json_strcmp(tokens+i, json_chunk, "mode") == 0) { ++i; @@ -600,120 +1904,888 @@ static int cgltf_parse_json_primitive(cgltf_options* options, jsmntok_t const* t else if (cgltf_json_strcmp(tokens+i, json_chunk, "indices") == 0) { ++i; - out_prim->indices = - (void*)(size_t)cgltf_json_to_int(tokens+i, json_chunk); + out_prim->indices = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "material") == 0) { ++i; - out_prim->material = - (void*)(size_t)cgltf_json_to_int(tokens+i, json_chunk); + out_prim->material = CGLTF_PTRINDEX(cgltf_material, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "attributes") == 0) { - ++i; - if (tokens[i].type != JSMN_OBJECT) + i = cgltf_parse_json_attribute_list(options, tokens, i + 1, json_chunk, &out_prim->attributes, &out_prim->attributes_count); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "targets") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_morph_target), (void**)&out_prim->targets, &out_prim->targets_count); + if (i < 0) { - return -1; + return i; } - out_prim->attributes_count = tokens[i].size; - out_prim->attributes - = options->memory_alloc(options->memory_user_data, sizeof(cgltf_attribute) * tokens[i].size); - ++i; - for (cgltf_size iattr = 0; iattr < out_prim->attributes_count; ++iattr) + + for (cgltf_size k = 0; k < out_prim->targets_count; ++k) { - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_STRING); - out_prim->attributes[iattr].name = cgltf_attribute_type_invalid; - if (cgltf_json_strcmp(tokens+i, json_chunk, "POSITION") == 0) + i = cgltf_parse_json_attribute_list(options, tokens, i, json_chunk, &out_prim->targets[k].attributes, &out_prim->targets[k].attributes_count); + if (i < 0) { - out_prim->attributes[iattr].name = cgltf_attribute_type_position; + return i; } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "NORMAL") == 0) - { - out_prim->attributes[iattr].name = cgltf_attribute_type_normal; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "TANGENT") == 0) - { - out_prim->attributes[iattr].name = cgltf_attribute_type_tangent; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "TEXCOORD_0") == 0) - { - out_prim->attributes[iattr].name = cgltf_attribute_type_texcoord_0; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "TEXCOORD_1") == 0) - { - out_prim->attributes[iattr].name = cgltf_attribute_type_texcoord_1; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "COLOR_0") == 0) - { - out_prim->attributes[iattr].name = cgltf_attribute_type_color_0; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "JOINTS_0") == 0) - { - out_prim->attributes[iattr].name = cgltf_attribute_type_joints_0; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "WEIGHTS_0") == 0) - { - out_prim->attributes[iattr].name = cgltf_attribute_type_weights_0; - } - ++i; - out_prim->attributes[iattr].data = - (void*)(size_t)cgltf_json_to_int(tokens+i, json_chunk); - ++i; } } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_prim->extras); + } else { i = cgltf_skip_json(tokens, i+1); } + + if (i < 0) + { + return i; + } } return i; } -static int cgltf_parse_json_mesh(cgltf_options* options, jsmntok_t const* tokens, int i, - const uint8_t* json_chunk, cgltf_size mesh_index, - cgltf_data* out_data) +static int cgltf_parse_json_mesh(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_mesh* out_mesh) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - out_data->meshes[mesh_index].name = NULL; - 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; - int strsize = tokens[i].end - tokens[i].start; - out_data->meshes[mesh_index].name = options->memory_alloc(options->memory_user_data, strsize + 1); - strncpy(out_data->meshes[mesh_index].name, - (const char*)json_chunk + tokens[i].start, - strsize); - out_data->meshes[mesh_index].name[strsize] = 0; - ++i; + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_mesh->name); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "primitives") == 0) { - ++i; - if (tokens[i].type != JSMN_ARRAY) + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_primitive), (void**)&out_mesh->primitives, &out_mesh->primitives_count); + if (i < 0) { - return -1; + return i; } - out_data->meshes[mesh_index].primitives_count = tokens[i].size; - out_data->meshes[mesh_index].primitives = options->memory_alloc(options->memory_user_data, sizeof(cgltf_primitive) * tokens[i].size); + + for (cgltf_size prim_index = 0; prim_index < out_mesh->primitives_count; ++prim_index) + { + i = cgltf_parse_json_primitive(options, tokens, i, json_chunk, &out_mesh->primitives[prim_index]); + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "weights") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_float), (void**)&out_mesh->weights, &out_mesh->weights_count); + if (i < 0) + { + return i; + } + + i = cgltf_parse_json_float_array(tokens, i - 1, json_chunk, out_mesh->weights, (int)out_mesh->weights_count); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_mesh->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_meshes(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_mesh), (void**)&out_data->meshes, &out_data->meshes_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->meshes_count; ++j) + { + i = cgltf_parse_json_mesh(options, tokens, i, json_chunk, &out_data->meshes[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static cgltf_component_type cgltf_json_to_component_type(jsmntok_t const* tok, const uint8_t* json_chunk) +{ + int type = cgltf_json_to_int(tok, json_chunk); + + switch (type) + { + case 5120: + return cgltf_component_type_r_8; + case 5121: + return cgltf_component_type_r_8u; + case 5122: + return cgltf_component_type_r_16; + case 5123: + return cgltf_component_type_r_16u; + case 5125: + return cgltf_component_type_r_32u; + case 5126: + return cgltf_component_type_r_32f; + default: + return cgltf_component_type_invalid; + } +} + +static int cgltf_parse_json_accessor_sparse(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_accessor_sparse* out_sparse) +{ + 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, "count") == 0) + { + ++i; + out_sparse->count = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "indices") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int indices_size = tokens[i].size; ++i; - for (cgltf_size prim_index = 0; - prim_index < out_data->meshes[mesh_index].primitives_count; - ++prim_index) + for (int k = 0; k < indices_size; ++k) { - i = cgltf_parse_json_primitive(options, tokens, i, json_chunk, - &out_data->meshes[mesh_index].primitives[prim_index]); + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) + { + ++i; + out_sparse->indices_buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) + { + ++i; + out_sparse->indices_byte_offset = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "componentType") == 0) + { + ++i; + out_sparse->indices_component_type = cgltf_json_to_component_type(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sparse->indices_extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "values") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int values_size = tokens[i].size; + ++i; + + for (int k = 0; k < values_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) + { + ++i; + out_sparse->values_buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) + { + ++i; + out_sparse->values_byte_offset = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sparse->values_extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sparse->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_accessor(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_accessor* out_accessor) +{ + 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, "bufferView") == 0) + { + ++i; + out_accessor->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) + { + ++i; + out_accessor->offset = + cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "componentType") == 0) + { + ++i; + out_accessor->component_type = cgltf_json_to_component_type(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "normalized") == 0) + { + ++i; + out_accessor->normalized = cgltf_json_to_bool(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "count") == 0) + { + ++i; + out_accessor->count = + cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "type") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens+i, json_chunk, "SCALAR") == 0) + { + out_accessor->type = cgltf_type_scalar; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC2") == 0) + { + out_accessor->type = cgltf_type_vec2; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC3") == 0) + { + out_accessor->type = cgltf_type_vec3; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC4") == 0) + { + out_accessor->type = cgltf_type_vec4; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT2") == 0) + { + out_accessor->type = cgltf_type_mat2; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT3") == 0) + { + out_accessor->type = cgltf_type_mat3; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT4") == 0) + { + out_accessor->type = cgltf_type_mat4; + } + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "min") == 0) + { + ++i; + out_accessor->has_min = 1; + // note: we can't parse the precise number of elements since type may not have been computed yet + int min_size = tokens[i].size > 16 ? 16 : tokens[i].size; + i = cgltf_parse_json_float_array(tokens, i, json_chunk, out_accessor->min, min_size); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "max") == 0) + { + ++i; + out_accessor->has_max = 1; + // note: we can't parse the precise number of elements since type may not have been computed yet + int max_size = tokens[i].size > 16 ? 16 : tokens[i].size; + i = cgltf_parse_json_float_array(tokens, i, json_chunk, out_accessor->max, max_size); + } + 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); + } + 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 + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_texture_transform(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture_transform* out_texture_transform) +{ + 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, "offset") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_texture_transform->offset, 2); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "rotation") == 0) + { + ++i; + out_texture_transform->rotation = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "scale") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_texture_transform->scale, 2); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "texCoord") == 0) + { + ++i; + out_texture_transform->texcoord = cgltf_json_to_int(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_texture_view(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture_view* out_texture_view) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + out_texture_view->scale = 1.0f; + cgltf_fill_float_array(out_texture_view->transform.scale, 2, 1.0f); + + 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, "index") == 0) + { + ++i; + out_texture_view->texture = CGLTF_PTRINDEX(cgltf_texture, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "texCoord") == 0) + { + ++i; + out_texture_view->texcoord = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "scale") == 0) + { + ++i; + out_texture_view->scale = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "strength") == 0) + { + ++i; + out_texture_view->scale = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_texture_view->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int extensions_size = tokens[i].size; + ++i; + + for (int k = 0; k < extensions_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_texture_transform") == 0) + { + out_texture_view->has_transform = 1; + i = cgltf_parse_json_texture_transform(tokens, i + 1, json_chunk, &out_texture_view->transform); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + 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) +{ + 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, "metallicFactor") == 0) + { + ++i; + out_pbr->metallic_factor = + cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "roughnessFactor") == 0) + { + ++i; + out_pbr->roughness_factor = + cgltf_json_to_float(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "baseColorFactor") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->base_color_factor, 4); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "baseColorTexture") == 0) + { + i = cgltf_parse_json_texture_view(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, + &out_pbr->metallic_roughness_texture); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_pbr->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + 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) +{ + 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, "diffuseFactor") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->diffuse_factor, 4); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "specularFactor") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->specular_factor, 3); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "glossinessFactor") == 0) + { + ++i; + out_pbr->glossiness_factor = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + 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); + } + 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); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_image(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_image* out_image) +{ + 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, "uri") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->uri); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) + { + ++i; + out_image->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "mimeType") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->mime_type); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->name); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_image->extras); + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_sampler(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_sampler* out_sampler) +{ + (void)options; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + out_sampler->wrap_s = 10497; + out_sampler->wrap_t = 10497; + + 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, "magFilter") == 0) + { + ++i; + out_sampler->mag_filter + = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "minFilter") == 0) + { + ++i; + out_sampler->min_filter + = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "wrapS") == 0) + { + ++i; + out_sampler->wrap_s + = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "wrapT") == 0) + { + ++i; + out_sampler->wrap_t + = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sampler->extras); + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + 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); + + 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_texture->name); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "sampler") == 0) + { + ++i; + out_texture->sampler = CGLTF_PTRINDEX(cgltf_sampler, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "source") == 0) + { + ++i; + out_texture->image = CGLTF_PTRINDEX(cgltf_image, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_texture->extras); + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_material(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_material* out_material) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + cgltf_fill_float_array(out_material->pbr_metallic_roughness.base_color_factor, 4, 1.0f); + out_material->pbr_metallic_roughness.metallic_factor = 1.0f; + out_material->pbr_metallic_roughness.roughness_factor = 1.0f; + + cgltf_fill_float_array(out_material->pbr_specular_glossiness.diffuse_factor, 4, 1.0f); + cgltf_fill_float_array(out_material->pbr_specular_glossiness.specular_factor, 3, 1.0f); + out_material->pbr_specular_glossiness.glossiness_factor = 1.0f; + + out_material->alpha_cutoff = 0.5f; + + 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_material->name); + } + 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); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "emissiveFactor") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_material->emissive_factor, 3); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "normalTexture") == 0) + { + i = cgltf_parse_json_texture_view(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, + &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, + &out_material->emissive_texture); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "alphaMode") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens + i, json_chunk, "OPAQUE") == 0) + { + out_material->alpha_mode = cgltf_alpha_mode_opaque; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "MASK") == 0) + { + out_material->alpha_mode = cgltf_alpha_mode_mask; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "BLEND") == 0) + { + out_material->alpha_mode = cgltf_alpha_mode_blend; + } + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "alphaCutoff") == 0) + { + ++i; + out_material->alpha_cutoff = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "doubleSided") == 0) + { + ++i; + out_material->double_sided = + cgltf_json_to_bool(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_material->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int extensions_size = tokens[i].size; + ++i; + + for (int k = 0; k < extensions_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + 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); + } + 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 + { + i = cgltf_skip_json(tokens, i+1); + } + if (i < 0) { return i; @@ -724,463 +2796,27 @@ static int cgltf_parse_json_mesh(cgltf_options* options, jsmntok_t const* tokens { i = cgltf_skip_json(tokens, i+1); } - } - return i; -} - -static int cgltf_parse_json_meshes(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); - out_data->meshes_count = tokens[i].size; - out_data->meshes = options->memory_alloc(options->memory_user_data, sizeof(cgltf_mesh) * out_data->meshes_count); - ++i; - for (cgltf_size j = 0 ; j < out_data->meshes_count; ++j) - { - i = cgltf_parse_json_mesh(options, tokens, i, json_chunk, j, out_data); if (i < 0) { return i; } } - return i; -} - -static int cgltf_parse_json_accessor(jsmntok_t const* tokens, int i, - const uint8_t* json_chunk, cgltf_size accessor_index, - cgltf_data* out_data) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - memset(&out_data->accessors[accessor_index], 0, sizeof(cgltf_accessor)); - out_data->accessors[accessor_index].buffer_view = (void*)-1; - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) - { - ++i; - out_data->accessors[accessor_index].buffer_view = - (void*)(size_t)cgltf_json_to_int(tokens+i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) - { - ++i; - out_data->accessors[accessor_index].offset = - cgltf_json_to_int(tokens+i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "componentType") == 0) - { - ++i; - int type = cgltf_json_to_int(tokens+i, json_chunk); - switch (type) - { - case 5120: - type = cgltf_component_type_r_8; - break; - case 5121: - type = cgltf_component_type_r_8u; - break; - case 5122: - type = cgltf_component_type_r_16; - break; - case 5123: - type = cgltf_component_type_r_16u; - break; - case 5125: - type = cgltf_component_type_r_32u; - break; - case 5126: - type = cgltf_component_type_r_32f; - break; - default: - type = cgltf_component_type_invalid; - break; - } - out_data->accessors[accessor_index].component_type = type; - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "count") == 0) - { - ++i; - out_data->accessors[accessor_index].count = - cgltf_json_to_int(tokens+i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "type") == 0) - { - ++i; - if (cgltf_json_strcmp(tokens+i, json_chunk, "SCALAR") == 0) - { - out_data->accessors[accessor_index].type = cgltf_type_scalar; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC2") == 0) - { - out_data->accessors[accessor_index].type = cgltf_type_vec2; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC3") == 0) - { - out_data->accessors[accessor_index].type = cgltf_type_vec3; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC4") == 0) - { - out_data->accessors[accessor_index].type = cgltf_type_vec4; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT2") == 0) - { - out_data->accessors[accessor_index].type = cgltf_type_mat2; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT3") == 0) - { - out_data->accessors[accessor_index].type = cgltf_type_mat3; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT4") == 0) - { - out_data->accessors[accessor_index].type = cgltf_type_mat4; - } - ++i; - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - } - - return i; -} - -static int cgltf_parse_json_rgba(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_rgba* out) -{ - int components = tokens[i].size; - if (components >= 2) { - out->r = cgltf_json_to_float(tokens + ++i, json_chunk); - out->g = cgltf_json_to_float(tokens + ++i, json_chunk); - - if (components > 2) - out->b = cgltf_json_to_float(tokens + ++i, json_chunk); - - if (components > 3) - out->a = cgltf_json_to_float(tokens + ++i, json_chunk); - } - else { - out->r = cgltf_json_to_float(tokens + ++i, json_chunk); - out->g = out->r; - out->b = out->r; - out->a = out->r; - } - - return ++i; -} - -static int cgltf_parse_json_texture_view(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture_view* out) { - - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - if (cgltf_json_strcmp(tokens + i, json_chunk, "index") == 0) - { - ++i; - out->texture = (void*)(size_t)cgltf_json_to_int(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "texCoord") == 0) - { - ++i; - out->texcoord = cgltf_json_to_int(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "scale") == 0) - { - ++i; - out->scale = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else - { - i = cgltf_skip_json(tokens, i + 1); - } - } - - return i; -} - -static int cgltf_parse_json_pbr(jsmntok_t const* tokens, int i, - const uint8_t* json_chunk, cgltf_size mat_index, cgltf_data* out_data) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - if (cgltf_json_strcmp(tokens+i, json_chunk, "metallicFactor") == 0) - { - ++i; - out_data->materials[mat_index].pbr.metallic_factor = - cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "roughnessFactor") == 0) - { - ++i; - out_data->materials[mat_index].pbr.roughness_factor = - cgltf_json_to_float(tokens+i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "baseColorFactor") == 0) - { - i = cgltf_parse_json_rgba(tokens, i + 1, json_chunk, - &(out_data->materials[mat_index].pbr.base_color)); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "baseColorTexture") == 0) - { - i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, - &(out_data->materials[mat_index].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, - &(out_data->materials[mat_index].pbr.metallic_roughness_texture)); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - } - - return i; -} - -static int cgltf_parse_json_image(cgltf_options* options, jsmntok_t const* tokens, int i, - const uint8_t* json_chunk, cgltf_size img_index, cgltf_data* out_data) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - memset(&out_data->images[img_index], 0, sizeof(cgltf_image)); - int size = tokens[i].size; - ++i; - - out_data->images[img_index].buffer_view = (void*)-1; - - for (int j = 0; j < size; ++j) - { - if (cgltf_json_strcmp(tokens + i, json_chunk, "uri") == 0) - { - ++i; - int strsize = tokens[i].end - tokens[i].start; - out_data->images[img_index].uri = options->memory_alloc(options->memory_user_data, strsize + 1); - strncpy(out_data->images[img_index].uri, - (const char*)json_chunk + tokens[i].start, - strsize); - out_data->images[img_index].uri[strsize] = 0; - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) - { - ++i; - out_data->images[img_index].buffer_view = - (void*)(size_t)cgltf_json_to_int(tokens+i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "mimeType") == 0) - { - ++i; - int strsize = tokens[i].end - tokens[i].start; - out_data->images[img_index].mime_type = options->memory_alloc(options->memory_user_data, strsize + 1); - strncpy(out_data->images[img_index].mime_type, - (const char*)json_chunk + tokens[i].start, - strsize); - out_data->images[img_index].mime_type[strsize] = 0; - ++i; - } - else - { - i = cgltf_skip_json(tokens, i + 1); - } - } - - return i; -} - -static int cgltf_parse_json_sampler(cgltf_options* options, jsmntok_t const* tokens, int i, - const uint8_t* json_chunk, cgltf_size smp_index, cgltf_data* out_data) { - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - memset(&out_data->samplers[smp_index], 0, sizeof(cgltf_sampler)); - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - if (cgltf_json_strcmp(tokens + i, json_chunk, "magFilter") == 0) - { - ++i; - out_data->samplers[smp_index].mag_filter - = cgltf_json_to_int(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "minFilter") == 0) - { - ++i; - out_data->samplers[smp_index].min_filter - = cgltf_json_to_int(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "wrapS") == 0) - { - ++i; - out_data->samplers[smp_index].wrap_s - = cgltf_json_to_int(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "wrapT") == 0) - { - ++i; - out_data->samplers[smp_index].wrap_t - = cgltf_json_to_int(tokens + i, json_chunk); - ++i; - } - else - { - i = cgltf_skip_json(tokens, i + 1); - } - } - - return i; -} - - -static int cgltf_parse_json_texture(cgltf_options* options, jsmntok_t const* tokens, int i, - const uint8_t* json_chunk, cgltf_size tex_index, cgltf_data* out_data) { - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - memset(&out_data->textures[tex_index], 0, sizeof(cgltf_texture)); - out_data->textures[tex_index].image = (void*)-1; - out_data->textures[tex_index].sampler = (void*)-1; - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - if (cgltf_json_strcmp(tokens + i, json_chunk, "sampler") == 0) - { - ++i; - out_data->textures[tex_index].sampler - = (void*)(size_t)cgltf_json_to_int(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "source") == 0) - { - ++i; - out_data->textures[tex_index].image - = (void*)(size_t)cgltf_json_to_int(tokens + i, json_chunk); - ++i; - } - else - { - i = cgltf_skip_json(tokens, i + 1); - } - } - - return i; -} - -static int cgltf_parse_json_material(cgltf_options* options, jsmntok_t const* tokens, int i, - const uint8_t* json_chunk, cgltf_size mat_index, cgltf_data* out_data) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - cgltf_material* material = &out_data->materials[mat_index]; - - memset(material, 0, sizeof(cgltf_material)); - material->emissive_texture.texture = (void*)-1; - material->emissive_texture.scale = 1.0f; - - material->normal_texture.texture = (void*)-1; - material->normal_texture.scale = 1.0f; - - material->occlusion_texture.texture = (void*)-1; - material->occlusion_texture.scale = 1.0f; - - material->pbr.base_color_texture.texture = (void*)-1; - material->pbr.base_color_texture.scale = 1.0f; - - material->pbr.metallic_roughness_texture.texture = (void*)-1; - material->pbr.metallic_roughness_texture.scale = 1.0f; - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) - { - ++i; - int strsize = tokens[i].end - tokens[i].start; - material->name = options->memory_alloc(options->memory_user_data, strsize + 1); - strncpy(material->name, - (const char*)json_chunk + tokens[i].start, - strsize); - material->name[strsize] = 0; - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "pbrMetallicRoughness") == 0) - { - i = cgltf_parse_json_pbr(tokens, i+1, json_chunk, mat_index, out_data); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "emissiveFactor") == 0) - { - i = cgltf_parse_json_rgba(tokens, i + 1, json_chunk, - &(material->emissive_color)); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "normalTexture") == 0) - { - i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, - &(material->normal_texture)); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "emissiveTexture") == 0) - { - i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, - &(material->emissive_texture)); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "occlusionTexture") == 0) - { - i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, - &(material->occlusion_texture)); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "doubleSided") == 0) - { - ++i; - material->double_sided = - cgltf_json_to_bool(tokens + i, json_chunk); - ++i; - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - } return i; } static int cgltf_parse_json_accessors(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); - out_data->accessors_count = tokens[i].size; - out_data->accessors = options->memory_alloc(options->memory_user_data, sizeof(cgltf_accessor) * out_data->accessors_count); - ++i; - for (cgltf_size j = 0 ; j < out_data->accessors_count; ++j) + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_accessor), (void**)&out_data->accessors, &out_data->accessors_count); + if (i < 0) { - i = cgltf_parse_json_accessor(tokens, i, json_chunk, j, out_data); + return i; + } + + for (cgltf_size j = 0; j < out_data->accessors_count; ++j) + { + i = cgltf_parse_json_accessor(tokens, i, json_chunk, &out_data->accessors[j]); if (i < 0) { return i; @@ -1191,13 +2827,15 @@ static int cgltf_parse_json_accessors(cgltf_options* options, jsmntok_t const* t static int cgltf_parse_json_materials(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); - out_data->materials_count = tokens[i].size; - out_data->materials = options->memory_alloc(options->memory_user_data, sizeof(cgltf_material) * out_data->materials_count); - ++i; + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_material), (void**)&out_data->materials, &out_data->materials_count); + if (i < 0) + { + return i; + } + for (cgltf_size j = 0; j < out_data->materials_count; ++j) { - i = cgltf_parse_json_material(options, tokens, i, json_chunk, j, out_data); + i = cgltf_parse_json_material(options, tokens, i, json_chunk, &out_data->materials[j]); if (i < 0) { return i; @@ -1206,89 +2844,98 @@ static int cgltf_parse_json_materials(cgltf_options* options, jsmntok_t const* t return i; } -static int cgltf_parse_json_images(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); - out_data->images_count = tokens[i].size; - out_data->images = options->memory_alloc(options->memory_user_data, sizeof(cgltf_image) * out_data->images_count); - ++i; +static int cgltf_parse_json_images(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_image), (void**)&out_data->images, &out_data->images_count); + if (i < 0) + { + return i; + } - for (cgltf_size j = 0; j < out_data->images_count; ++j) { - i = cgltf_parse_json_image(options, tokens, i, json_chunk, j, out_data); - if (i < 0) { + for (cgltf_size j = 0; j < out_data->images_count; ++j) + { + i = cgltf_parse_json_image(options, tokens, i, json_chunk, &out_data->images[j]); + if (i < 0) + { return i; } } return i; } -static int cgltf_parse_json_textures(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); - out_data->textures_count = tokens[i].size; - out_data->textures = options->memory_alloc(options->memory_user_data, sizeof(cgltf_texture) * out_data->textures_count); - ++i; +static int cgltf_parse_json_textures(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_texture), (void**)&out_data->textures, &out_data->textures_count); + if (i < 0) + { + return i; + } - for (cgltf_size j = 0; j < out_data->textures_count; ++j) { - i = cgltf_parse_json_texture(options, tokens, i, json_chunk, j, out_data); - if (i < 0) { + for (cgltf_size j = 0; j < out_data->textures_count; ++j) + { + i = cgltf_parse_json_texture(options, tokens, i, json_chunk, &out_data->textures[j]); + if (i < 0) + { return i; } } return i; } -static int cgltf_parse_json_samplers(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); - out_data->samplers_count = tokens[i].size; - out_data->samplers = options->memory_alloc(options->memory_user_data, sizeof(cgltf_sampler) * out_data->samplers_count); - ++i; +static int cgltf_parse_json_samplers(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_sampler), (void**)&out_data->samplers, &out_data->samplers_count); + if (i < 0) + { + return i; + } - for (cgltf_size j = 0; j < out_data->samplers_count; ++j) { - i = cgltf_parse_json_sampler(options, tokens, i, json_chunk, j, out_data); - if (i < 0) { + for (cgltf_size j = 0; j < out_data->samplers_count; ++j) + { + i = cgltf_parse_json_sampler(options, tokens, i, json_chunk, &out_data->samplers[j]); + if (i < 0) + { return i; } } return i; } -static int cgltf_parse_json_buffer_view(jsmntok_t const* tokens, int i, - const uint8_t* json_chunk, cgltf_size buffer_view_index, - cgltf_data* out_data) +static int cgltf_parse_json_buffer_view(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; - memset(&out_data->buffer_views[buffer_view_index], 0, sizeof(cgltf_buffer_view)); - for (int j = 0; j < size; ++j) { + CGLTF_CHECK_KEY(tokens[i]); + if (cgltf_json_strcmp(tokens+i, json_chunk, "buffer") == 0) { ++i; - out_data->buffer_views[buffer_view_index].buffer = - (void*)(size_t)cgltf_json_to_int(tokens+i, json_chunk); + out_buffer_view->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_data->buffer_views[buffer_view_index].offset = + out_buffer_view->offset = cgltf_json_to_int(tokens+i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteLength") == 0) { ++i; - out_data->buffer_views[buffer_view_index].size = + out_buffer_view->size = cgltf_json_to_int(tokens+i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteStride") == 0) { ++i; - out_data->buffer_views[buffer_view_index].stride = + out_buffer_view->stride = cgltf_json_to_int(tokens+i, json_chunk); ++i; } @@ -1308,13 +2955,22 @@ static int cgltf_parse_json_buffer_view(jsmntok_t const* tokens, int i, type = cgltf_buffer_view_type_invalid; break; } - out_data->buffer_views[buffer_view_index].type = type; + out_buffer_view->type = (cgltf_buffer_view_type)type; ++i; } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_buffer_view->extras); + } else { i = cgltf_skip_json(tokens, i+1); } + + if (i < 0) + { + return i; + } } return i; @@ -1322,13 +2978,15 @@ static int cgltf_parse_json_buffer_view(jsmntok_t const* tokens, int i, static int cgltf_parse_json_buffer_views(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); - out_data->buffer_views_count = tokens[i].size; - out_data->buffer_views = options->memory_alloc(options->memory_user_data, sizeof(cgltf_buffer_view) * out_data->buffer_views_count); - ++i; - for (cgltf_size j = 0 ; j < out_data->buffer_views_count; ++j) + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_buffer_view), (void**)&out_data->buffer_views, &out_data->buffer_views_count); + if (i < 0) { - i = cgltf_parse_json_buffer_view(tokens, i, json_chunk, j, out_data); + return i; + } + + 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]); if (i < 0) { return i; @@ -1337,41 +2995,41 @@ static int cgltf_parse_json_buffer_views(cgltf_options* options, jsmntok_t const return i; } -static int cgltf_parse_json_buffer(cgltf_options* options, jsmntok_t const* tokens, int i, - const uint8_t* json_chunk, cgltf_size buffer_index, - cgltf_data* out_data) +static int cgltf_parse_json_buffer(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_buffer* out_buffer) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - out_data->buffers[buffer_index].uri = NULL; - 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, "byteLength") == 0) { ++i; - out_data->buffers[buffer_index].size = + out_buffer->size = cgltf_json_to_int(tokens+i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "uri") == 0) { - ++i; - int strsize = tokens[i].end - tokens[i].start; - out_data->buffers[buffer_index].uri = options->memory_alloc(options->memory_user_data, strsize + 1); - strncpy(out_data->buffers[buffer_index].uri, - (const char*)json_chunk + tokens[i].start, - strsize); - out_data->buffers[buffer_index].uri[strsize] = 0; - ++i; + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_buffer->uri); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_buffer->extras); } else { i = cgltf_skip_json(tokens, i+1); } + + if (i < 0) + { + return i; + } } return i; @@ -1379,13 +3037,15 @@ static int cgltf_parse_json_buffer(cgltf_options* options, jsmntok_t const* toke static int cgltf_parse_json_buffers(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); - out_data->buffers_count = tokens[i].size; - out_data->buffers = options->memory_alloc(options->memory_user_data, sizeof(cgltf_buffer) * out_data->buffers_count); - ++i; - for (cgltf_size j = 0 ; j < out_data->buffers_count; ++j) + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_buffer), (void**)&out_data->buffers, &out_data->buffers_count); + if (i < 0) { - i = cgltf_parse_json_buffer(options, tokens, i, json_chunk, j, out_data); + return i; + } + + for (cgltf_size j = 0; j < out_data->buffers_count; ++j) + { + i = cgltf_parse_json_buffer(options, tokens, i, json_chunk, &out_data->buffers[j]); if (i < 0) { return i; @@ -1394,313 +3054,1344 @@ static int cgltf_parse_json_buffers(cgltf_options* options, jsmntok_t const* tok return i; } -static cgltf_size cgltf_calc_size(cgltf_type type, cgltf_component_type component_type) +static int cgltf_parse_json_skin(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_skin* out_skin) { - cgltf_type size = 0; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - switch (component_type) + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) { - case cgltf_component_type_rgb_32f: - size = 12; - break; - case cgltf_component_type_rgba_32f: - size = 16; - break; - case cgltf_component_type_rg_32f: - size = 8; - break; - case cgltf_component_type_rg_8: - size = 2; - break; - case cgltf_component_type_rg_16: - size = 4; - break; - case cgltf_component_type_rgba_8: - size = 4; - break; - case cgltf_component_type_rgba_16: - size = 8; - break; - case cgltf_component_type_r_8: - case cgltf_component_type_r_8u: - size = 1; - break; - case cgltf_component_type_r_16: - case cgltf_component_type_r_16u: - size = 2; - break; - case cgltf_component_type_r_32u: - case cgltf_component_type_r_32f: - size = 4; - break; - case cgltf_component_type_invalid: - default: - size = 0; - break; + 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_skin->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "joints") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_skin->joints, &out_skin->joints_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_skin->joints_count; ++k) + { + out_skin->joints[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "skeleton") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_skin->skeleton = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "inverseBindMatrices") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_skin->inverse_bind_matrices = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_skin->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } } + return i; +} + +static int cgltf_parse_json_skins(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_skin), (void**)&out_data->skins, &out_data->skins_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->skins_count; ++j) + { + i = cgltf_parse_json_skin(options, tokens, i, json_chunk, &out_data->skins[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_camera(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_camera* out_camera) +{ + 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_camera->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "type") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens + i, json_chunk, "perspective") == 0) + { + out_camera->type = cgltf_camera_type_perspective; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "orthographic") == 0) + { + out_camera->type = cgltf_camera_type_orthographic; + } + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "perspective") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int data_size = tokens[i].size; + ++i; + + out_camera->type = cgltf_camera_type_perspective; + + for (int k = 0; k < data_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "aspectRatio") == 0) + { + ++i; + out_camera->perspective.aspect_ratio = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "yfov") == 0) + { + ++i; + out_camera->perspective.yfov = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "zfar") == 0) + { + ++i; + out_camera->perspective.zfar = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "znear") == 0) + { + ++i; + out_camera->perspective.znear = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_camera->perspective.extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "orthographic") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int data_size = tokens[i].size; + ++i; + + out_camera->type = cgltf_camera_type_orthographic; + + for (int k = 0; k < data_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "xmag") == 0) + { + ++i; + out_camera->orthographic.xmag = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "ymag") == 0) + { + ++i; + out_camera->orthographic.ymag = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "zfar") == 0) + { + ++i; + out_camera->orthographic.zfar = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "znear") == 0) + { + ++i; + out_camera->orthographic.znear = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_camera->orthographic.extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_camera->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_cameras(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_camera), (void**)&out_data->cameras, &out_data->cameras_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->cameras_count; ++j) + { + i = cgltf_parse_json_camera(options, tokens, i, json_chunk, &out_data->cameras[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_light(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_light* out_light) +{ + 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_light->name); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "color") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_light->color, 3); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "intensity") == 0) + { + ++i; + out_light->intensity = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "type") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens + i, json_chunk, "directional") == 0) + { + out_light->type = cgltf_light_type_directional; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "point") == 0) + { + out_light->type = cgltf_light_type_point; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "spot") == 0) + { + out_light->type = cgltf_light_type_spot; + } + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "range") == 0) + { + ++i; + out_light->range = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "spot") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int data_size = tokens[i].size; + ++i; + + for (int k = 0; k < data_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "innerConeAngle") == 0) + { + ++i; + out_light->spot_inner_cone_angle = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "outerConeAngle") == 0) + { + ++i; + out_light->spot_outer_cone_angle = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_lights(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_light), (void**)&out_data->lights, &out_data->lights_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->lights_count; ++j) + { + i = cgltf_parse_json_light(options, tokens, i, json_chunk, &out_data->lights[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_node(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_node* out_node) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + out_node->rotation[3] = 1.0f; + out_node->scale[0] = 1.0f; + out_node->scale[1] = 1.0f; + out_node->scale[2] = 1.0f; + out_node->matrix[0] = 1.0f; + out_node->matrix[5] = 1.0f; + out_node->matrix[10] = 1.0f; + out_node->matrix[15] = 1.0f; + + 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_node->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "children") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_node->children, &out_node->children_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_node->children_count; ++k) + { + out_node->children[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "mesh") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_node->mesh = CGLTF_PTRINDEX(cgltf_mesh, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "skin") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_node->skin = CGLTF_PTRINDEX(cgltf_skin, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "camera") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_node->camera = CGLTF_PTRINDEX(cgltf_camera, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "translation") == 0) + { + out_node->has_translation = 1; + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->translation, 3); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "rotation") == 0) + { + out_node->has_rotation = 1; + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->rotation, 4); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "scale") == 0) + { + out_node->has_scale = 1; + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->scale, 3); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "matrix") == 0) + { + out_node->has_matrix = 1; + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->matrix, 16); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "weights") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_float), (void**)&out_node->weights, &out_node->weights_count); + if (i < 0) + { + return i; + } + + i = cgltf_parse_json_float_array(tokens, i - 1, json_chunk, out_node->weights, (int)out_node->weights_count); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_node->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int extensions_size = tokens[i].size; + ++i; + + for (int k = 0; k < extensions_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_lights_punctual") == 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, "light") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_node->light = CGLTF_PTRINDEX(cgltf_light, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_nodes(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_node), (void**)&out_data->nodes, &out_data->nodes_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->nodes_count; ++j) + { + i = cgltf_parse_json_node(options, tokens, i, json_chunk, &out_data->nodes[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_scene(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_scene* out_scene) +{ + 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_scene->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "nodes") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_scene->nodes, &out_scene->nodes_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_scene->nodes_count; ++k) + { + out_scene->nodes[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_scene->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_scenes(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_scene), (void**)&out_data->scenes, &out_data->scenes_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->scenes_count; ++j) + { + i = cgltf_parse_json_scene(options, tokens, i, json_chunk, &out_data->scenes[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_animation_sampler(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation_sampler* out_sampler) +{ + (void)options; + 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, "input") == 0) + { + ++i; + out_sampler->input = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "output") == 0) + { + ++i; + out_sampler->output = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "interpolation") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens + i, json_chunk, "LINEAR") == 0) + { + out_sampler->interpolation = cgltf_interpolation_type_linear; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "STEP") == 0) + { + out_sampler->interpolation = cgltf_interpolation_type_step; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "CUBICSPLINE") == 0) + { + out_sampler->interpolation = cgltf_interpolation_type_cubic_spline; + } + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sampler->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_animation_channel(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation_channel* out_channel) +{ + (void)options; + 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, "sampler") == 0) + { + ++i; + out_channel->sampler = CGLTF_PTRINDEX(cgltf_animation_sampler, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "target") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int target_size = tokens[i].size; + ++i; + + for (int k = 0; k < target_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "node") == 0) + { + ++i; + out_channel->target_node = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "path") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens+i, json_chunk, "translation") == 0) + { + out_channel->target_path = cgltf_animation_path_type_translation; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "rotation") == 0) + { + out_channel->target_path = cgltf_animation_path_type_rotation; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "scale") == 0) + { + out_channel->target_path = cgltf_animation_path_type_scale; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "weights") == 0) + { + out_channel->target_path = cgltf_animation_path_type_weights; + } + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_channel->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_animation(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation* out_animation) +{ + 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_animation->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "samplers") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_animation_sampler), (void**)&out_animation->samplers, &out_animation->samplers_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_animation->samplers_count; ++k) + { + i = cgltf_parse_json_animation_sampler(options, tokens, i, json_chunk, &out_animation->samplers[k]); + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "channels") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_animation_channel), (void**)&out_animation->channels, &out_animation->channels_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_animation->channels_count; ++k) + { + i = cgltf_parse_json_animation_channel(options, tokens, i, json_chunk, &out_animation->channels[k]); + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_animation->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_animations(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_animation), (void**)&out_data->animations, &out_data->animations_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->animations_count; ++j) + { + i = cgltf_parse_json_animation(options, tokens, i, json_chunk, &out_data->animations[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); + + 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, "copyright") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->copyright); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "generator") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->generator); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "version") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->version); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "minVersion") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->min_version); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_asset->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static cgltf_size cgltf_num_components(cgltf_type type) { switch (type) { case cgltf_type_vec2: - size *= 2; - break; + return 2; case cgltf_type_vec3: - size *= 3; - break; + return 3; case cgltf_type_vec4: - size *= 4; - break; + return 4; case cgltf_type_mat2: - size *= 4; - break; + return 4; case cgltf_type_mat3: - size *= 9; - break; + return 9; case cgltf_type_mat4: - size *= 16; - break; + return 16; case cgltf_type_invalid: case cgltf_type_scalar: default: - size *= 1; - break; + return 1; } - - return size; } -cgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* json_chunk, cgltf_size size, cgltf_data* out_data) +static cgltf_size cgltf_component_size(cgltf_component_type component_type) { + switch (component_type) + { + case cgltf_component_type_r_8: + case cgltf_component_type_r_8u: + return 1; + case cgltf_component_type_r_16: + case cgltf_component_type_r_16u: + return 2; + case cgltf_component_type_r_32u: + case cgltf_component_type_r_32f: + return 4; + case cgltf_component_type_invalid: + default: + return 0; + } +} + +static cgltf_size cgltf_calc_size(cgltf_type type, cgltf_component_type component_type) { - jsmn_parser parser = {0}; + cgltf_size component_size = cgltf_component_size(component_type); + if (type == cgltf_type_mat2 && component_size == 1) + { + return 8 * component_size; + } + else if (type == cgltf_type_mat3 && (component_size == 1 || component_size == 2)) + { + return 12 * component_size; + } + return component_size * cgltf_num_components(type); +} + +static int cgltf_fixup_pointers(cgltf_data* out_data); + +static int cgltf_parse_json_root(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + 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, "asset") == 0) + { + i = cgltf_parse_json_asset(options, tokens, i + 1, json_chunk, &out_data->asset); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "meshes") == 0) + { + i = cgltf_parse_json_meshes(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "accessors") == 0) + { + i = cgltf_parse_json_accessors(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "bufferViews") == 0) + { + i = cgltf_parse_json_buffer_views(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "buffers") == 0) + { + i = cgltf_parse_json_buffers(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "materials") == 0) + { + i = cgltf_parse_json_materials(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "images") == 0) + { + i = cgltf_parse_json_images(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "textures") == 0) + { + i = cgltf_parse_json_textures(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "samplers") == 0) + { + i = cgltf_parse_json_samplers(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "skins") == 0) + { + i = cgltf_parse_json_skins(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "cameras") == 0) + { + i = cgltf_parse_json_cameras(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "nodes") == 0) + { + i = cgltf_parse_json_nodes(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "scenes") == 0) + { + i = cgltf_parse_json_scenes(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "scene") == 0) + { + ++i; + out_data->scene = CGLTF_PTRINDEX(cgltf_scene, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "animations") == 0) + { + i = cgltf_parse_json_animations(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_data->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int extensions_size = tokens[i].size; + ++i; + + for (int k = 0; k < extensions_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_lights_punctual") == 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, "lights") == 0) + { + i = cgltf_parse_json_lights(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); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensionsUsed") == 0) + { + i = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_data->extensions_used, &out_data->extensions_used_count); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensionsRequired") == 0) + { + i = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_data->extensions_required, &out_data->extensions_required_count); + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +cgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* json_chunk, cgltf_size size, cgltf_data** out_data) +{ + jsmn_parser parser = { 0, 0, 0 }; if (options->json_token_count == 0) { - options->json_token_count = jsmn_parse(&parser, (const char*)json_chunk, size, NULL, 0); + int token_count = jsmn_parse(&parser, (const char*)json_chunk, size, NULL, 0); + + if (token_count <= 0) + { + return cgltf_result_invalid_json; + } + + options->json_token_count = token_count; } - jsmntok_t* tokens = options->memory_alloc(options->memory_user_data, sizeof(jsmntok_t) * options->json_token_count); + jsmntok_t* tokens = (jsmntok_t*)options->memory_alloc(options->memory_user_data, sizeof(jsmntok_t) * (options->json_token_count + 1)); + + if (!tokens) + { + return cgltf_result_out_of_memory; + } jsmn_init(&parser); int token_count = jsmn_parse(&parser, (const char*)json_chunk, size, tokens, options->json_token_count); - if (token_count < 0 - || tokens[0].type != JSMN_OBJECT) + if (token_count <= 0) { + options->memory_free(options->memory_user_data, tokens); return cgltf_result_invalid_json; } - // The root is an object. + // this makes sure that we always have an UNDEFINED token at the end of the stream + // for invalid JSON inputs this makes sure we don't perform out of bound reads of token data + tokens[token_count].type = JSMN_UNDEFINED; - for (int i = 1; i < token_count; ) + cgltf_data* data = (cgltf_data*)options->memory_alloc(options->memory_user_data, sizeof(cgltf_data)); + + if (!data) { - jsmntok_t const* tok = &tokens[i]; - if (tok->type == JSMN_STRING - && i + 1 < token_count) - { - int const name_length = tok->end - tok->start; - if (name_length == 6 - && strncmp((const char*)json_chunk + tok->start, "meshes", 6) == 0) - { - i = cgltf_parse_json_meshes(options, tokens, i+1, json_chunk, out_data); - } - else if (name_length == 9 - && strncmp((const char*)json_chunk + tok->start, "accessors", 9) == 0) - { - i = cgltf_parse_json_accessors(options, tokens, i+1, json_chunk, out_data); - } - else if (name_length == 11 - && strncmp((const char*)json_chunk + tok->start, "bufferViews", 11) == 0) - { - i = cgltf_parse_json_buffer_views(options, tokens, i+1, json_chunk, out_data); - } - else if (name_length == 7 - && strncmp((const char*)json_chunk + tok->start, "buffers", 7) == 0) - { - i = cgltf_parse_json_buffers(options, tokens, i+1, json_chunk, out_data); - } - else if (name_length == 9 - && strncmp((const char*)json_chunk + tok->start, "materials", 9) == 0) - { - i = cgltf_parse_json_materials(options, tokens, i+1, json_chunk, out_data); - } - else if (name_length == 6 - && strncmp((const char*)json_chunk + tok->start, "images", 6) == 0) - { - i = cgltf_parse_json_images(options, tokens, i + 1, json_chunk, out_data); - } - else if (name_length == 8 - && strncmp((const char*)json_chunk + tok->start, "textures", 8) == 0) - { - i = cgltf_parse_json_textures(options, tokens, i + 1, json_chunk, out_data); - } - else if (name_length == 8 - && strncmp((const char*)json_chunk + tok->start, "samplers", 8) == 0) - { - i = cgltf_parse_json_samplers(options, tokens, i + 1, json_chunk, out_data); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return cgltf_result_invalid_json; - } - } + options->memory_free(options->memory_user_data, tokens); + return cgltf_result_out_of_memory; } + memset(data, 0, sizeof(cgltf_data)); + data->memory_free = options->memory_free; + data->memory_user_data = options->memory_user_data; + + int i = cgltf_parse_json_root(options, tokens, 0, json_chunk, data); + options->memory_free(options->memory_user_data, tokens); - /* Fix up pointers */ - for (cgltf_size i = 0; i < out_data->meshes_count; ++i) + if (i < 0) { - for (cgltf_size j = 0; j < out_data->meshes[i].primitives_count; ++j) - { - if (out_data->meshes[i].primitives[j].indices ==(void*)-1) - { - out_data->meshes[i].primitives[j].indices = NULL; - } - else - { - out_data->meshes[i].primitives[j].indices - = &out_data->accessors[(cgltf_size)out_data->meshes[i].primitives[j].indices]; - } - - for (cgltf_size k = 0; k < out_data->meshes[i].primitives[j].attributes_count; ++k) - { - out_data->meshes[i].primitives[j].attributes[k].data - = &out_data->accessors[(cgltf_size)out_data->meshes[i].primitives[j].attributes[k].data]; - } - } + cgltf_free(data); + return (i == CGLTF_ERROR_NOMEM) ? cgltf_result_out_of_memory : cgltf_result_invalid_gltf; } - for (cgltf_size i = 0; i < out_data->accessors_count; ++i) + if (cgltf_fixup_pointers(data) < 0) { - if (out_data->accessors[i].buffer_view == (void*)-1) - { - out_data->accessors[i].buffer_view = NULL; - } - else - { - out_data->accessors[i].buffer_view - = &out_data->buffer_views[(cgltf_size)out_data->accessors[i].buffer_view]; - out_data->accessors[i].stride = 0; - if (out_data->accessors[i].buffer_view) - { - out_data->accessors[i].stride = out_data->accessors[i].buffer_view->stride; - } - } - if (out_data->accessors[i].stride == 0) - { - out_data->accessors[i].stride = cgltf_calc_size(out_data->accessors[i].type, out_data->accessors[i].component_type); - } + cgltf_free(data); + return cgltf_result_invalid_gltf; } - for (cgltf_size i = 0; i < out_data->textures_count; ++i) - { - if (out_data->textures[i].image == (void*)-1) - { - out_data->textures[i].image = NULL; - } - else - { - out_data->textures[i].image = - &out_data->images[(cgltf_size)out_data->textures[i].image]; - } + data->json = (const char*)json_chunk; + data->json_size = size; - if (out_data->textures[i].sampler == (void*)-1) - { - out_data->textures[i].sampler = NULL; - } - else - { - out_data->textures[i].sampler = - &out_data->samplers[(cgltf_size)out_data->textures[i].sampler]; - } - } - - for (cgltf_size i = 0; i < out_data->images_count; ++i) - { - if (out_data->images[i].buffer_view == (void*)-1) - { - out_data->images[i].buffer_view = NULL; - } - else - { - out_data->images[i].buffer_view - = &out_data->buffer_views[(cgltf_size)out_data->images[i].buffer_view]; - } - } - - for (cgltf_size i = 0; i < out_data->materials_count; ++i) - { - if (out_data->materials[i].emissive_texture.texture == (void*)-1) - { - out_data->materials[i].emissive_texture.texture = NULL; - } - else - { - out_data->materials[i].emissive_texture.texture = - &out_data->textures[(cgltf_size)out_data->materials[i].emissive_texture.texture]; - } - - if (out_data->materials[i].normal_texture.texture == (void*)-1) - { - out_data->materials[i].normal_texture.texture = NULL; - } - else - { - out_data->materials[i].normal_texture.texture = - &out_data->textures[(cgltf_size)out_data->materials[i].normal_texture.texture]; - } - - if (out_data->materials[i].occlusion_texture.texture == (void*)-1) - { - out_data->materials[i].occlusion_texture.texture = NULL; - } - else - { - out_data->materials[i].occlusion_texture.texture = - &out_data->textures[(cgltf_size)out_data->materials[i].occlusion_texture.texture]; - } - - if (out_data->materials[i].pbr.base_color_texture.texture == (void*)-1) - { - out_data->materials[i].pbr.base_color_texture.texture = NULL; - } - else - { - out_data->materials[i].pbr.base_color_texture.texture = - &out_data->textures[(cgltf_size)out_data->materials[i].pbr.base_color_texture.texture]; - } - - if (out_data->materials[i].pbr.metallic_roughness_texture.texture == (void*)-1) - { - out_data->materials[i].pbr.metallic_roughness_texture.texture = NULL; - } - else - { - out_data->materials[i].pbr.metallic_roughness_texture.texture = - &out_data->textures[(cgltf_size)out_data->materials[i].pbr.metallic_roughness_texture.texture]; - } - } - - for (cgltf_size i = 0; i < out_data->buffer_views_count; ++i) - { - out_data->buffer_views[i].buffer - = &out_data->buffers[(cgltf_size)out_data->buffer_views[i].buffer]; - } + *out_data = data; return cgltf_result_success; } +static int cgltf_fixup_pointers(cgltf_data* data) +{ + for (cgltf_size i = 0; i < data->meshes_count; ++i) + { + for (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j) + { + CGLTF_PTRFIXUP(data->meshes[i].primitives[j].indices, data->accessors, data->accessors_count); + CGLTF_PTRFIXUP(data->meshes[i].primitives[j].material, data->materials, data->materials_count); + + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k) + { + CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].attributes[k].data, data->accessors, data->accessors_count); + } + + 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) + { + CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].targets[k].attributes[m].data, data->accessors, data->accessors_count); + } + } + } + } + + for (cgltf_size i = 0; i < data->accessors_count; ++i) + { + CGLTF_PTRFIXUP(data->accessors[i].buffer_view, data->buffer_views, data->buffer_views_count); + + if (data->accessors[i].is_sparse) + { + CGLTF_PTRFIXUP_REQ(data->accessors[i].sparse.indices_buffer_view, data->buffer_views, data->buffer_views_count); + CGLTF_PTRFIXUP_REQ(data->accessors[i].sparse.values_buffer_view, data->buffer_views, data->buffer_views_count); + } + + if (data->accessors[i].buffer_view) + { + data->accessors[i].stride = data->accessors[i].buffer_view->stride; + } + + if (data->accessors[i].stride == 0) + { + data->accessors[i].stride = cgltf_calc_size(data->accessors[i].type, data->accessors[i].component_type); + } + } + + for (cgltf_size i = 0; i < data->textures_count; ++i) + { + CGLTF_PTRFIXUP(data->textures[i].image, data->images, data->images_count); + CGLTF_PTRFIXUP(data->textures[i].sampler, data->samplers, data->samplers_count); + } + + for (cgltf_size i = 0; i < data->images_count; ++i) + { + CGLTF_PTRFIXUP(data->images[i].buffer_view, data->buffer_views, data->buffer_views_count); + } + + for (cgltf_size i = 0; i < data->materials_count; ++i) + { + CGLTF_PTRFIXUP(data->materials[i].normal_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].emissive_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].occlusion_texture.texture, data->textures, data->textures_count); + + CGLTF_PTRFIXUP(data->materials[i].pbr_metallic_roughness.base_color_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture, data->textures, data->textures_count); + + 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); + } + + for (cgltf_size i = 0; i < data->buffer_views_count; ++i) + { + CGLTF_PTRFIXUP_REQ(data->buffer_views[i].buffer, data->buffers, data->buffers_count); + } + + for (cgltf_size i = 0; i < data->skins_count; ++i) + { + for (cgltf_size j = 0; j < data->skins[i].joints_count; ++j) + { + CGLTF_PTRFIXUP_REQ(data->skins[i].joints[j], data->nodes, data->nodes_count); + } + + CGLTF_PTRFIXUP(data->skins[i].skeleton, data->nodes, data->nodes_count); + CGLTF_PTRFIXUP(data->skins[i].inverse_bind_matrices, data->accessors, data->accessors_count); + } + + for (cgltf_size i = 0; i < data->nodes_count; ++i) + { + for (cgltf_size j = 0; j < data->nodes[i].children_count; ++j) + { + CGLTF_PTRFIXUP_REQ(data->nodes[i].children[j], data->nodes, data->nodes_count); + + if (data->nodes[i].children[j]->parent) + { + return CGLTF_ERROR_JSON; + } + + data->nodes[i].children[j]->parent = &data->nodes[i]; + } + + CGLTF_PTRFIXUP(data->nodes[i].mesh, data->meshes, data->meshes_count); + CGLTF_PTRFIXUP(data->nodes[i].skin, data->skins, data->skins_count); + CGLTF_PTRFIXUP(data->nodes[i].camera, data->cameras, data->cameras_count); + CGLTF_PTRFIXUP(data->nodes[i].light, data->lights, data->lights_count); + } + + for (cgltf_size i = 0; i < data->scenes_count; ++i) + { + for (cgltf_size j = 0; j < data->scenes[i].nodes_count; ++j) + { + CGLTF_PTRFIXUP_REQ(data->scenes[i].nodes[j], data->nodes, data->nodes_count); + + if (data->scenes[i].nodes[j]->parent) + { + return CGLTF_ERROR_JSON; + } + } + } + + CGLTF_PTRFIXUP(data->scene, data->scenes, data->scenes_count); + + for (cgltf_size i = 0; i < data->animations_count; ++i) + { + for (cgltf_size j = 0; j < data->animations[i].samplers_count; ++j) + { + CGLTF_PTRFIXUP_REQ(data->animations[i].samplers[j].input, data->accessors, data->accessors_count); + CGLTF_PTRFIXUP_REQ(data->animations[i].samplers[j].output, data->accessors, data->accessors_count); + } + + for (cgltf_size j = 0; j < data->animations[i].channels_count; ++j) + { + CGLTF_PTRFIXUP_REQ(data->animations[i].channels[j].sampler, data->animations[i].samplers, data->animations[i].samplers_count); + CGLTF_PTRFIXUP(data->animations[i].channels[j].target_node, data->nodes, data->nodes_count); + } + } + + return 0; +} + /* * -- jsmn.c start -- * Source: https://github.com/zserge/jsmn * License: MIT + * + * Copyright (c) 2010 Serge A. Zaitsev + + * 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. */ + /** * Allocates a fresh unused token from the token pull. */ @@ -1849,8 +4540,8 @@ static int jsmn_parse_string(jsmn_parser *parser, const char *js, /** * Parse JSON string and fill tokens. */ -int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, - jsmntok_t *tokens, unsigned int num_tokens) { +static int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, size_t num_tokens) { int r; int i; jsmntok_t *token; @@ -2007,7 +4698,7 @@ int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, * Creates a new parser based over a given buffer with an array of tokens * available. */ -void jsmn_init(jsmn_parser *parser) { +static void jsmn_init(jsmn_parser *parser) { parser->pos = 0; parser->toknext = 0; parser->toksuper = -1; @@ -2018,6 +4709,25 @@ void jsmn_init(jsmn_parser *parser) { #endif /* #ifdef CGLTF_IMPLEMENTATION */ -#ifdef __cplusplus -} -#endif +/* cgltf is distributed under MIT license: + * + * Copyright (c) 2018 Johannes Kuhlmann + + * 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. + */ diff --git a/raylib/external/dr_flac.h b/raylib/external/dr_flac.h index c836847..250d0bd 100644 --- a/raylib/external/dr_flac.h +++ b/raylib/external/dr_flac.h @@ -1,119 +1,118 @@ -// FLAC audio decoder. Public domain. See "unlicense" statement at the end of this file. -// dr_flac - v0.9.7 - 2018-07-05 -// -// David Reid - mackron@gmail.com +/* +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 -// USAGE -// -// dr_flac is a single-file library. To use it, do something like the following in one .c file. -// #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: -// -// drflac* pFlac = drflac_open_file("MySong.flac"); -// if (pFlac == NULL) { -// // Failed to open FLAC file -// } -// -// drflac_int32* pSamples = malloc(pFlac->totalSampleCount * sizeof(drflac_int32)); -// drflac_uint64 numberOfInterleavedSamplesActuallyRead = drflac_read_s32(pFlac, pFlac->totalSampleCount, 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. -// -// 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: -// -// while (drflac_read_s32(pFlac, chunkSize, 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. -// -// -// If you just want to quickly decode an entire FLAC file in one go you can do something like this: -// -// unsigned int channels; -// unsigned int sampleRate; -// drflac_uint64 totalSampleCount; -// drflac_int32* pSampleData = drflac_open_and_decode_file_s32("MySong.flac", &channels, &sampleRate, &totalSampleCount); -// if (pSampleData == NULL) { -// // Failed to open and decode FLAC file. -// } -// -// ... -// -// drflac_free(pSampleData); -// -// -// 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 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. -// -// -// -// OPTIONS -// #define these options before including this file. -// -// #define DR_FLAC_NO_STDIO -// Disable drflac_open_file(). -// -// #define DR_FLAC_NO_OGG -// Disables support for Ogg/FLAC streams. -// -// #define DR_FLAC_NO_WIN32_IO -// In the Win32 build, dr_flac uses the Win32 IO APIs for drflac_open_file() by default. This setting will make it use the -// standard FILE APIs instead. Ignored when DR_FLAC_NO_STDIO is #defined. (The rationale for this configuration is that -// there's a bug in one compiler's Win32 implementation of the FILE APIs which is not present in the Win32 IO APIs.) -// -// #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. -// -// #define DR_FLAC_NO_CRC -// Disables CRC checks. This will offer a performance boost when CRC is unnecessary. -// -// #define DR_FLAC_NO_SIMD -// Disables SIMD optimizations (SSE on x86/x64 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. -// - Rice codes in unencoded binary form (see https://xiph.org/flac/format.html#rice_partition) has not been tested. If anybody -// knows where I can find some test files for this, let me know. -// - 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. +David Reid - mackron@gmail.com +*/ + +/* +USAGE +===== +dr_flac is a single-file library. To use it, do something like the following in one .c file. + #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: + + drflac* pFlac = drflac_open_file("MySong.flac"); + 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. + +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: + + 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. + + +If you just want to quickly decode an entire FLAC file in one go you can do something like this: + + 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); + if (pSampleData == NULL) { + // Failed to open and decode FLAC file. + } + + ... + + drflac_free(pSampleData); + + +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 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. + + + +OPTIONS +======= +#define these options before including this file. + +#define DR_FLAC_NO_STDIO + 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. + +#define DR_FLAC_NO_CRC + Disables CRC checks. This will offer a performance boost when CRC is unnecessary. + +#define DR_FLAC_NO_SIMD + Disables SIMD optimizations (SSE on x86/x64 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. +- 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. +*/ #ifndef dr_flac_h #define dr_flac_h @@ -145,9 +144,25 @@ typedef drflac_uint32 drflac_bool32; #define DRFLAC_TRUE 1 #define DRFLAC_FALSE 0 -// 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. +#if defined(_MSC_VER) && _MSC_VER >= 1700 /* Visual Studio 2012 */ + #define DRFLAC_DEPRECATED __declspec(deprecated) +#elif (defined(__GNUC__) && __GNUC__ >= 4) /* GCC 4 */ + #define DRFLAC_DEPRECATED __attribute__((deprecated)) +#elif defined(__has_feature) /* Clang */ + #if __has_feature(attribute_deprecated) + #define DRFLAC_DEPRECATED __attribute__((deprecated)) + #else + #define DRFLAC_DEPRECATED + #endif +#else + #define DRFLAC_DEPRECATED +#endif + +/* +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 @@ -156,24 +171,18 @@ typedef drflac_uint32 drflac_bool32; extern "C" { #endif -// Check if we can enable 64-bit optimizations. -#if defined(_WIN64) +/* Check if we can enable 64-bit optimizations. */ +#if defined(_WIN64) || defined(_LP64) || defined(__LP64__) #define DRFLAC_64BIT #endif -#if defined(__GNUC__) -#if defined(__x86_64__) || defined(__ppc64__) -#define DRFLAC_64BIT -#endif -#endif - #ifdef DRFLAC_64BIT typedef drflac_uint64 drflac_cache_t; #else typedef drflac_uint32 drflac_cache_t; #endif -// The various metadata block types. +/* The various metadata block types. */ #define DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO 0 #define DRFLAC_METADATA_BLOCK_TYPE_PADDING 1 #define DRFLAC_METADATA_BLOCK_TYPE_APPLICATION 2 @@ -183,7 +192,7 @@ typedef drflac_uint32 drflac_cache_t; #define DRFLAC_METADATA_BLOCK_TYPE_PICTURE 6 #define DRFLAC_METADATA_BLOCK_TYPE_INVALID 127 -// The various picture types specified in the PICTURE block. +/* The various picture types specified in the PICTURE block. */ #define DRFLAC_PICTURE_TYPE_OTHER 0 #define DRFLAC_PICTURE_TYPE_FILE_ICON 1 #define DRFLAC_PICTURE_TYPE_OTHER_FILE_ICON 2 @@ -219,12 +228,12 @@ typedef enum drflac_seek_origin_current } drflac_seek_origin; -// Packing is important on this structure because we map this directly to the raw data within the SEEKTABLE metadata block. +/* Packing is important on this structure because we map this directly to the raw data within the SEEKTABLE metadata block. */ #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_uint64 frameOffset; /* The offset from the first byte of the header of the first frame. */ drflac_uint16 sampleCount; } drflac_seekpoint; #pragma pack() @@ -244,15 +253,17 @@ typedef struct 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. */ drflac_uint32 type; - // A pointer to the raw data. This points to a temporary buffer so don't hold on to it. It's best to - // not modify the contents of this buffer. Use the structures below for more meaningful and structured - // information about the metadata. It's possible for this to be null. + /* + A pointer to the raw data. This points to a temporary buffer so don't hold on to it. It's best to + not modify the contents of this buffer. Use the structures below for more meaningful and structured + information about the metadata. It's possible for this to be null. + */ const void* pRawData; - // The size in bytes of the block and the buffer pointed to by pRawData if it's non-NULL. + /* The size in bytes of the block and the buffer pointed to by pRawData if it's non-NULL. */ drflac_uint32 rawDataSize; union @@ -282,7 +293,7 @@ typedef struct drflac_uint32 vendorLength; const char* vendor; drflac_uint32 commentCount; - const char* comments; + const void* pComments; } vorbis_comment; struct @@ -291,7 +302,7 @@ typedef struct drflac_uint64 leadInSampleCount; drflac_bool32 isCD; drflac_uint8 trackCount; - const drflac_uint8* pTrackData; + const void* pTrackData; } cuesheet; struct @@ -312,40 +323,46 @@ typedef struct } drflac_metadata; -// 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. -// -// 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. +/* +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. + +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. -// -// 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. +/* +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. + +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. +*/ 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. +/* +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. +*/ typedef void (* drflac_meta_proc)(void* pUserData, drflac_metadata* pMetadata); -// Structure for internal use. Only used for decoders opened with drflac_open_memory. +/* Structure for internal use. Only used for decoders opened with drflac_open_memory. */ typedef struct { const drflac_uint8* data; @@ -353,420 +370,588 @@ typedef struct size_t currentReadPos; } drflac__memory_stream; -// Structure for internal use. Used for bit streaming. +/* Structure for internal use. Used for bit streaming. */ typedef struct { - // The function to call when more data needs to be read. + /* The function to call when more data needs to be read. */ drflac_read_proc onRead; - // The function to call when the current read position needs to be moved. + /* The function to call when the current read position needs to be moved. */ drflac_seek_proc onSeek; - // The user data to pass around to onRead and onSeek. + /* The user data to pass around to onRead and onSeek. */ void* pUserData; - // The number of unaligned bytes in the L2 cache. This will always be 0 until the end of the stream is hit. At the end of the - // stream there will be a number of bytes that don't cleanly fit in an L1 cache line, so we use this variable to know whether - // or not the bistreamer needs to run on a slower path to read those last bytes. This will never be more than sizeof(drflac_cache_t). + /* + The number of unaligned bytes in the L2 cache. This will always be 0 until the end of the stream is hit. At the end of the + stream there will be a number of bytes that don't cleanly fit in an L1 cache line, so we use this variable to know whether + or not the bistreamer needs to run on a slower path to read those last bytes. This will never be more than sizeof(drflac_cache_t). + */ size_t unalignedByteCount; - // The content of the unaligned bytes. + /* The content of the unaligned bytes. */ drflac_cache_t unalignedCache; - // The index of the next valid cache line in the "L2" cache. + /* The index of the next valid cache line in the "L2" cache. */ drflac_uint32 nextL2Line; - // The number of bits that have been consumed by the cache. This is used to determine how many valid bits are remaining. + /* The number of bits that have been consumed by the cache. This is used to determine how many valid bits are remaining. */ drflac_uint32 consumedBits; - // The cached data which was most recently read from the client. There are two levels of cache. Data flows as such: - // Client -> L2 -> L1. The L2 -> L1 movement is aligned and runs on a fast path in just a few instructions. + /* + The cached data which was most recently read from the client. There are two levels of cache. Data flows as such: + Client -> L2 -> L1. The L2 -> L1 movement is aligned and runs on a fast path in just a few instructions. + */ drflac_cache_t cacheL2[DR_FLAC_BUFFER_SIZE/sizeof(drflac_cache_t)]; drflac_cache_t cache; - // CRC-16. This is updated whenever bits are read from the bit stream. Manually set this to 0 to reset the CRC. For FLAC, this - // is reset to 0 at the beginning of each frame. + /* + CRC-16. This is updated whenever bits are read from the bit stream. Manually set this to 0 to reset the CRC. For FLAC, this + is reset to 0 at the beginning of each frame. + */ drflac_uint16 crc16; - drflac_cache_t crc16Cache; // A cache for optimizing CRC calculations. This is filled when when the L1 cache is reloaded. - drflac_uint32 crc16CacheIgnoredBytes; // The number of bytes to ignore when updating the CRC-16 from the CRC-16 cache. + drflac_cache_t crc16Cache; /* A cache for optimizing CRC calculations. This is filled when when the L1 cache is reloaded. */ + drflac_uint32 crc16CacheIgnoredBytes; /* The number of bytes to ignore when updating the CRC-16 from the CRC-16 cache. */ } drflac_bs; typedef struct { - // The type of the subframe: SUBFRAME_CONSTANT, SUBFRAME_VERBATIM, SUBFRAME_FIXED or SUBFRAME_LPC. + /* The type of the subframe: SUBFRAME_CONSTANT, SUBFRAME_VERBATIM, SUBFRAME_FIXED or SUBFRAME_LPC. */ drflac_uint8 subframeType; - // The number of wasted bits per sample as specified by the sub-frame header. + /* The number of wasted bits per sample as specified by the sub-frame header. */ drflac_uint8 wastedBitsPerSample; - // The order to use for the prediction stage for SUBFRAME_FIXED and SUBFRAME_LPC. + /* 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. + /* + 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. + /* + 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; } 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 sample. If fixed block sizes are used, this will + always be set to 0. + */ drflac_uint64 sampleNumber; - // 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. + /* 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; - // The sample rate of this frame. + /* The sample rate of this frame. */ drflac_uint32 sampleRate; - // The number of samples in each sub-frame within this frame. + /* The number of samples in each sub-frame within this frame. */ drflac_uint16 blockSize; - // The channel assignment of this frame. This is not always set to the channel count. If interchannel decorrelation is being used this - // will be set to DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE, DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE or DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE. + /* + The channel assignment of this frame. This is not always set to the channel count. If interchannel decorrelation is being used this + will be set to DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE, DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE or DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE. + */ drflac_uint8 channelAssignment; - // The number of bits per sample within this frame. + /* The number of bits per sample within this frame. */ drflac_uint8 bitsPerSample; - // The frame's CRC. + /* The frame's CRC. */ drflac_uint8 crc8; } drflac_frame_header; typedef struct { - // The header. + /* The header. */ 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 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. + */ drflac_uint32 samplesRemaining; - // The list of sub-frames within the frame. There is one sub-frame for each channel, and there's a maximum of 8 channels. + /* 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]; } drflac_frame; typedef struct { - // The function to call when a metadata block is read. + /* The function to call when a metadata block is read. */ drflac_meta_proc onMeta; - // The user data posted to the metadata callback function. + /* The user data posted to the metadata callback function. */ void* pUserDataMD; - // The sample rate. Will be set to something like 44100. + /* The sample rate. Will be set to something like 44100. */ drflac_uint32 sampleRate; - // The number of channels. This will be set to 1 for monaural streams, 2 for stereo, etc. Maximum 8. This is set based on the - // value specified in the STREAMINFO block. + /* + The number of channels. This will be set to 1 for monaural streams, 2 for stereo, etc. Maximum 8. This is set based on the + value specified in the STREAMINFO block. + */ drflac_uint8 channels; - // The bits per sample. Will be set to something like 16, 24, etc. + /* The bits per sample. Will be set to something like 16, 24, etc. */ drflac_uint8 bitsPerSample; - // The maximum block size, in samples. This number represents the number of samples in each channel (not combined). + /* The maximum block size, in samples. This number represents the number of samples in each channel (not combined). */ drflac_uint16 maxBlockSize; - // 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 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. + */ drflac_uint64 totalSampleCount; + drflac_uint64 totalPCMFrameCount; /* <-- Equal to totalSampleCount / channels. */ - // The container type. This is set based on whether or not the decoder was opened from a native or Ogg stream. + /* The container type. This is set based on whether or not the decoder was opened from a native or Ogg stream. */ drflac_container container; - // The number of seekpoints in the seektable. + /* The number of seekpoints in the seektable. */ drflac_uint32 seekpointCount; - // Information about the frame the decoder is currently sitting on. + /* Information about the frame the decoder is currently sitting on. */ drflac_frame currentFrame; - // The index of the sample the decoder is currently sitting on. This is only used for seeking. + /* 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. + /* The position of the first frame in the stream. This is only ever used for seeking. */ drflac_uint64 firstFramePos; - // A hack to avoid a malloc() when opening a decoder with drflac_open_memory(). + /* A hack to avoid a malloc() when opening a decoder with drflac_open_memory(). */ drflac__memory_stream memoryStream; - // A pointer to the decoded sample data. This is an offset of pExtraData. + /* A pointer to the decoded sample data. This is an offset of pExtraData. */ drflac_int32* pDecodedSamples; - // A pointer to the seek table. This is an offset of pExtraData, or NULL if there is no seek table. + /* A pointer to the seek table. This is an offset of pExtraData, or NULL if there is no seek table. */ drflac_seekpoint* pSeekpoints; - // Internal use only. Only used with Ogg containers. Points to a drflac_oggbs object. This is an offset of pExtraData. + /* Internal use only. Only used with Ogg containers. Points to a drflac_oggbs object. This is an offset of pExtraData. */ void* _oggbs; - // The bit streamer. The raw FLAC data is fed through this object. + /* The bit streamer. The raw FLAC data is fed through this object. */ drflac_bs bs; - // Variable length extra data. We attach this to the end of the object so we can avoid unnecessary mallocs. + /* Variable length extra data. We attach this to the end of the object so we can avoid unnecessary mallocs. */ drflac_uint8 pExtraData[1]; } drflac; -// 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. -// -// 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. -// -// 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. -// -// See also: drflac_open_file(), drflac_open_memory(), drflac_open_with_metadata(), drflac_close() +/* +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. + +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. + +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. + +See 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); -// 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. -// -// 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. +/* +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. + +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. +*/ drflac* drflac_open_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_container container, void* pUserData); -// 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. -// -// Close the decoder with drflac_close(). -// -// 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. -// -// The caller is notified of the metadata via the onMeta callback. All metadata blocks will be handled before the function -// returns. -// -// 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. -// -// See also: drflac_open_file_with_metadata(), drflac_open_memory_with_metadata(), drflac_open(), drflac_close() +/* +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. + +Close the decoder with drflac_close(). + +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. + +The caller is notified of the metadata via the onMeta callback. All metadata blocks will be handled before the function +returns. + +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. + +See 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); -// 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() +/* +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() +*/ drflac* drflac_open_with_metadata_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData); -// Closes the given FLAC decoder. -// -// pFlac [in] The decoder to close. -// -// This will destroy the decoder object. +/* +Closes the given FLAC decoder. + +pFlac [in] The decoder to close. + +This will destroy the decoder object. +*/ void drflac_close(drflac* pFlac); -// Reads sample data from the given FLAC decoder, output as interleaved signed 32-bit PCM. -// -// pFlac [in] The decoder. -// samplesToRead [in] The number of samples to read. -// pBufferOut [out, optional] A pointer to the buffer that will receive the decoded samples. -// -// Returns the number of samples actually read. -// -// pBufferOut can be null, in which case the call will act as a seek, and the return value will be the number of samples -// seeked. -drflac_uint64 drflac_read_s32(drflac* pFlac, drflac_uint64 samplesToRead, drflac_int32* pBufferOut); +/* +Reads sample data from the given FLAC decoder, output as interleaved signed 32-bit PCM. -// Same as drflac_read_s32(), except outputs samples as 16-bit integer PCM rather than 32-bit. -// -// pFlac [in] The decoder. -// samplesToRead [in] The number of samples to read. -// pBufferOut [out, optional] A pointer to the buffer that will receive the decoded samples. -// -// Returns the number of samples actually read. -// -// pBufferOut can be null, in which case the call will act as a seek, and the return value will be the number of samples -// seeked. -// -// Note that this is lossy for streams where the bits per sample is larger than 16. -drflac_uint64 drflac_read_s16(drflac* pFlac, drflac_uint64 samplesToRead, drflac_int16* pBufferOut); +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. -// Same as drflac_read_s32(), except outputs samples as 32-bit floating-point PCM. -// -// pFlac [in] The decoder. -// samplesToRead [in] The number of samples to read. -// pBufferOut [out, optional] A pointer to the buffer that will receive the decoded samples. -// -// Returns the number of samples actually read. -// -// pBufferOut can be null, in which case the call will act as a seek, and the return value will be the number of samples -// 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_f32(drflac* pFlac, drflac_uint64 samplesToRead, float* pBufferOut); +Returns the number of PCM frames actually read. -// Seeks to the sample at the given index. -// -// pFlac [in] The decoder. -// sampleIndex [in] The index of the sample to seek to. See notes below. -// -// Returns DRFLAC_TRUE if successful; DRFLAC_FALSE otherwise. -// -// The sample index is based on interleaving. In a stereo stream, for example, the sample at index 0 is the first sample -// in the left channel; the sample at index 1 is the first sample on the right channel, and so on. -// -// When seeking, you will likely want to ensure it's rounded to a multiple of the channel count. You can do this with -// something like drflac_seek_to_sample(pFlac, (mySampleIndex + (mySampleIndex % pFlac->channels))) -drflac_bool32 drflac_seek_to_sample(drflac* pFlac, drflac_uint64 sampleIndex); +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); + +/* +Same as drflac_read_pcm_frames_s32(), except outputs samples as 16-bit integer PCM rather than 32-bit. + +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); + +/* +Same as drflac_read_pcm_frames_s32(), except outputs samples as 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. +*/ +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. +*/ +drflac_bool32 drflac_seek_to_pcm_frame(drflac* pFlac, drflac_uint64 pcmFrameIndex); #ifndef DR_FLAC_NO_STDIO -// 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. -// -// 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() +/* +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. + +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() +*/ drflac* drflac_open_file(const char* filename); -// Opens a FLAC decoder from the file at the given path and notifies the caller of the metadata chunks (album art, etc.) -// -// Look at the documentation for drflac_open_with_metadata() for more information on how metadata is handled. +/* +Opens a FLAC decoder from the file at the given path and notifies the caller of the metadata chunks (album art, etc.) + +Look at the documentation for drflac_open_with_metadata() for more information on how metadata is handled. +*/ drflac* drflac_open_file_with_metadata(const char* filename, drflac_meta_proc onMeta, void* pUserData); #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. +/* +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. +*/ drflac* drflac_open_memory(const void* data, size_t dataSize); -// Opens a FLAC decoder from a pre-allocated block of memory and notifies the caller of the metadata chunks (album art, etc.) -// -// Look at the documentation for drflac_open_with_metadata() for more information on how metadata is handled. +/* +Opens a FLAC decoder from a pre-allocated block of memory and notifies the caller of the metadata chunks (album art, etc.) + +Look at the documentation for drflac_open_with_metadata() for more information on how metadata is handled. +*/ drflac* drflac_open_memory_with_metadata(const void* data, size_t dataSize, drflac_meta_proc onMeta, void* pUserData); -//// High Level APIs //// +/* High Level APIs */ -// 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(). -// -// 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_decode_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); +/* +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(). -// Same as drflac_open_and_decode_s32(), except returns signed 16-bit integer samples. -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); +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. -// Same as drflac_open_and_decode_s32(), except returns 32-bit floating-point samples. -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); +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); + +/* 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); + +/* 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); #ifndef DR_FLAC_NO_STDIO -// Same as drflac_open_and_decode_s32() except opens the decoder from a file. -drflac_int32* drflac_open_and_decode_file_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); +/* 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); -// Same as drflac_open_and_decode_file_s32(), except returns signed 16-bit integer samples. -drflac_int16* drflac_open_and_decode_file_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); +/* 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); -// Same as drflac_open_and_decode_file_f32(), except returns 32-bit floating-point samples. -float* drflac_open_and_decode_file_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); +/* 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); #endif -// Same as drflac_open_and_decode_s32() except opens the decoder from a block of memory. -drflac_int32* drflac_open_and_decode_memory_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); +/* 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); -// Same as drflac_open_and_decode_memory_s32(), except returns signed 16-bit integer samples. -drflac_int16* drflac_open_and_decode_memory_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); +/* 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); -// Same as drflac_open_and_decode_memory_s32(), except returns 32-bit floating-point samples. -float* drflac_open_and_decode_memory_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); +/* 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); -// Frees memory that was allocated internally by dr_flac. +/* Frees memory that was allocated internally by dr_flac. */ void drflac_free(void* p); -// Structure representing an iterator for vorbis comments in a VORBIS_COMMENT metadata block. +/* Structure representing an iterator for vorbis comments in a VORBIS_COMMENT metadata block. */ typedef struct { drflac_uint32 countRemaining; const char* pRunningData; } drflac_vorbis_comment_iterator; -// 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 char* pComments); +/* +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); -// 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. +/* +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); +/* Structure representing an iterator for cuesheet tracks in a CUESHEET metadata block. */ +typedef struct +{ + drflac_uint32 countRemaining; + const char* pRunningData; +} drflac_cuesheet_track_iterator; + +/* Packing is important on this structure because we map this directly to the raw data within the CUESHEET metadata block. */ +#pragma pack(4) +typedef struct +{ + drflac_uint64 offset; + drflac_uint8 index; + drflac_uint8 reserved[3]; +} drflac_cuesheet_track_index; +#pragma pack() + +typedef struct +{ + drflac_uint64 offset; + drflac_uint8 trackNumber; + char ISRC[12]; + drflac_bool8 isAudio; + drflac_bool8 preEmphasis; + drflac_uint8 indexCount; + const drflac_cuesheet_track_index* pIndexPoints; +} drflac_cuesheet_track; + +/* +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); + +/* 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); + + +/* 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 -#endif //dr_flac_h +#endif /* dr_flac_h */ -/////////////////////////////////////////////////////////////////////////////// -// -// IMPLEMENTATION -// -/////////////////////////////////////////////////////////////////////////////// +/************************************************************************************************************************************************************ + ************************************************************************************************************************************************************ + + IMPLEMENTATION + + ************************************************************************************************************************************************************ + ************************************************************************************************************************************************************/ #ifdef DR_FLAC_IMPLEMENTATION + +/* Disable some annoying warnings. */ +#if defined(__GNUC__) + #pragma GCC diagnostic push + #if __GNUC__ >= 7 + #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" + #endif +#endif + +#ifdef __linux__ + #ifndef _BSD_SOURCE + #define _BSD_SOURCE + #endif + #ifndef __USE_BSD + #define __USE_BSD + #endif + #include +#endif + #include #include -// CPU architecture. -#if defined(__x86_64__) || defined(_M_X64) -#define DRFLAC_X64 -#elif defined(__i386) || defined(_M_IX86) -#define DRFLAC_X86 -#elif defined(__arm__) || defined(_M_ARM) -#define DRFLAC_ARM +#ifdef _MSC_VER +#define DRFLAC_INLINE __forceinline +#else +#ifdef __GNUC__ +#define DRFLAC_INLINE __inline__ __attribute__((always_inline)) +#else +#define DRFLAC_INLINE +#endif #endif -// Compile-time CPU feature support. +/* CPU architecture. */ +#if defined(__x86_64__) || defined(_M_X64) + #define DRFLAC_X64 +#elif defined(__i386) || defined(_M_IX86) + #define DRFLAC_X86 +#elif defined(__arm__) || defined(_M_ARM) + #define DRFLAC_ARM +#endif + +/* Intrinsics Support */ +#if !defined(DR_FLAC_NO_SIMD) + #if defined(DRFLAC_X64) || defined(DRFLAC_X86) + #if defined(_MSC_VER) && !defined(__clang__) + /* MSVC. */ + #if _MSC_VER >= 1400 && !defined(DRFLAC_NO_SSE2) /* 2005 */ + #define DRFLAC_SUPPORT_SSE2 + #endif + #if _MSC_VER >= 1600 && !defined(DRFLAC_NO_SSE41) /* 2010 */ + #define DRFLAC_SUPPORT_SSE41 + #endif + #else + /* Assume GNUC-style. */ + #if defined(__SSE2__) && !defined(DRFLAC_NO_SSE2) + #define DRFLAC_SUPPORT_SSE2 + #endif + #if defined(__SSE4_1__) && !defined(DRFLAC_NO_SSE41) + #define DRFLAC_SUPPORT_SSE41 + #endif + #endif + + /* If at this point we still haven't determined compiler support for the intrinsics just fall back to __has_include. */ + #if !defined(__GNUC__) && !defined(__clang__) && defined(__has_include) + #if !defined(DRFLAC_SUPPORT_SSE2) && !defined(DRFLAC_NO_SSE2) && __has_include() + #define DRFLAC_SUPPORT_SSE2 + #endif + #if !defined(DRFLAC_SUPPORT_SSE41) && !defined(DRFLAC_NO_SSE41) && __has_include() + #define DRFLAC_SUPPORT_SSE41 + #endif + #endif + + #if defined(DRFLAC_SUPPORT_SSE41) + #include + #elif defined(DRFLAC_SUPPORT_SSE2) + #include + #endif + #endif + + #if defined(DRFLAC_ARM) + #if !defined(DRFLAC_NO_NEON) && (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64)) + #define DRFLAC_SUPPORT_NEON + #endif + + /* Fall back to looking for the #include file. */ + #if !defined(__GNUC__) && !defined(__clang__) && defined(__has_include) + #if !defined(DRFLAC_SUPPORT_NEON) && !defined(DRFLAC_NO_NEON) && __has_include() + #define DRFLAC_SUPPORT_NEON + #endif + #endif + + #if defined(DRFLAC_SUPPORT_NEON) + #include + #endif + #endif +#endif + +/* Compile-time CPU feature support. */ #if !defined(DR_FLAC_NO_SIMD) && (defined(DRFLAC_X86) || defined(DRFLAC_X64)) #if defined(_MSC_VER) && !defined(__clang__) #if _MSC_VER >= 1400 @@ -776,52 +961,127 @@ const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator* pIter, dr __cpuid(info, fid); } #else - #define DRFLAC_NO_CPUID + #define DRFLAC_NO_CPUID #endif #else #if defined(__GNUC__) || defined(__clang__) static void drflac__cpuid(int info[4], int fid) { - __asm__ __volatile__ ( - "cpuid" : "=a"(info[0]), "=b"(info[1]), "=c"(info[2]), "=d"(info[3]) : "a"(fid), "c"(0) - ); + /* + 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__) + __asm__ __volatile__ ( + "xchg{l} {%%}ebx, %k1;" + "cpuid;" + "xchg{l} {%%}ebx, %k1;" + : "=a"(info[0]), "=&r"(info[1]), "=c"(info[2]), "=d"(info[3]) : "a"(fid), "c"(0) + ); + #else + __asm__ __volatile__ ( + "cpuid" : "=a"(info[0]), "=b"(info[1]), "=c"(info[2]), "=d"(info[3]) : "a"(fid), "c"(0) + ); + #endif } #else - #define DRFLAC_NO_CPUID + #define DRFLAC_NO_CPUID #endif #endif #else -#define DRFLAC_NO_CPUID + #define DRFLAC_NO_CPUID #endif - -#ifdef __linux__ -#define _BSD_SOURCE -#include +static DRFLAC_INLINE drflac_bool32 drflac_has_sse2() +{ +#if defined(DRFLAC_SUPPORT_SSE2) + #if (defined(DRFLAC_X64) || defined(DRFLAC_X86)) && !defined(DRFLAC_NO_SSE2) + #if defined(DRFLAC_X64) + return DRFLAC_TRUE; /* 64-bit targets always support SSE2. */ + #elif (defined(_M_IX86_FP) && _M_IX86_FP == 2) || defined(__SSE2__) + return DRFLAC_TRUE; /* If the compiler is allowed to freely generate SSE2 code we can assume support. */ + #else + #if defined(DRFLAC_NO_CPUID) + return DRFLAC_FALSE; + #else + int info[4]; + drflac__cpuid(info, 1); + return (info[3] & (1 << 26)) != 0; + #endif + #endif + #else + return DRFLAC_FALSE; /* SSE2 is only supported on x86 and x64 architectures. */ + #endif +#else + return DRFLAC_FALSE; /* No compiler support. */ #endif +} + +static DRFLAC_INLINE drflac_bool32 drflac_has_sse41() +{ +#if defined(DRFLAC_SUPPORT_SSE41) + #if (defined(DRFLAC_X64) || defined(DRFLAC_X86)) && !defined(DRFLAC_NO_SSE41) + #if defined(DRFLAC_X64) + return DRFLAC_TRUE; /* 64-bit targets always support SSE4.1. */ + #elif (defined(_M_IX86_FP) && _M_IX86_FP == 2) || defined(__SSE4_1__) + return DRFLAC_TRUE; /* If the compiler is allowed to freely generate SSE41 code we can assume support. */ + #else + #if defined(DRFLAC_NO_CPUID) + return DRFLAC_FALSE; + #else + int info[4]; + drflac__cpuid(info, 1); + return (info[2] & (1 << 19)) != 0; + #endif + #endif + #else + return DRFLAC_FALSE; /* SSE41 is only supported on x86 and x64 architectures. */ + #endif +#else + return DRFLAC_FALSE; /* No compiler support. */ +#endif +} + #if defined(_MSC_VER) && _MSC_VER >= 1500 && (defined(DRFLAC_X86) || defined(DRFLAC_X64)) -#define DRFLAC_HAS_LZCNT_INTRINSIC + #define DRFLAC_HAS_LZCNT_INTRINSIC #elif (defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7))) -#define DRFLAC_HAS_LZCNT_INTRINSIC + #define DRFLAC_HAS_LZCNT_INTRINSIC #elif defined(__clang__) #if __has_builtin(__builtin_clzll) || __has_builtin(__builtin_clzl) - #define DRFLAC_HAS_LZCNT_INTRINSIC + #define DRFLAC_HAS_LZCNT_INTRINSIC #endif #endif #if defined(_MSC_VER) && _MSC_VER >= 1300 -#define DRFLAC_HAS_BYTESWAP_INTRINSIC -#elif defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) -#define DRFLAC_HAS_BYTESWAP_INTRINSIC + #define DRFLAC_HAS_BYTESWAP16_INTRINSIC + #define DRFLAC_HAS_BYTESWAP32_INTRINSIC + #define DRFLAC_HAS_BYTESWAP64_INTRINSIC #elif defined(__clang__) - #if __has_builtin(__builtin_bswap16) && __has_builtin(__builtin_bswap32) && __has_builtin(__builtin_bswap64) - #define DRFLAC_HAS_BYTESWAP_INTRINSIC + #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 +#elif defined(__GNUC__) + #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) + #define DRFLAC_HAS_BYTESWAP32_INTRINSIC + #define DRFLAC_HAS_BYTESWAP64_INTRINSIC + #endif + #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) + #define DRFLAC_HAS_BYTESWAP16_INTRINSIC #endif #endif -// Standard library stuff. +/* Standard library stuff. */ #ifndef DRFLAC_ASSERT #include #define DRFLAC_ASSERT(expression) assert(expression) @@ -842,21 +1102,11 @@ const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator* pIter, dr #define DRFLAC_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) #endif -#define DRFLAC_MAX_SIMD_VECTOR_SIZE 64 // 64 for AVX-512 in the future. - -#ifdef _MSC_VER -#define DRFLAC_INLINE __forceinline -#else -#ifdef __GNUC__ -#define DRFLAC_INLINE inline __attribute__((always_inline)) -#else -#define DRFLAC_INLINE inline -#endif -#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_ERROR -1 /* A generic error. */ #define DRFLAC_INVALID_ARGS -2 #define DRFLAC_END_OF_STREAM -128 #define DRFLAC_CRC_MISMATCH -129 @@ -875,6 +1125,12 @@ 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 @@ -882,30 +1138,57 @@ typedef drflac_int32 drflac_result; #define drflac_zero_memory DRFLAC_ZERO_MEMORY -// CPU caps. +/* CPU caps. */ static drflac_bool32 drflac__gIsLZCNTSupported = DRFLAC_FALSE; #ifndef DRFLAC_NO_CPUID -static drflac_bool32 drflac__gIsSSE42Supported = DRFLAC_FALSE; -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. +*/ +#if defined(__has_feature) + #if __has_feature(thread_sanitizer) + #define DRFLAC_NO_THREAD_SANITIZE __attribute__((no_sanitize("thread"))) + #else + #define DRFLAC_NO_THREAD_SANITIZE + #endif +#else + #define DRFLAC_NO_THREAD_SANITIZE +#endif +static drflac_bool32 drflac__gIsSSE2Supported = DRFLAC_FALSE; +static drflac_bool32 drflac__gIsSSE41Supported = DRFLAC_FALSE; +DRFLAC_NO_THREAD_SANITIZE static void drflac__init_cpu_caps() { - int info[4] = {0}; + static drflac_bool32 isCPUCapsInitialized = DRFLAC_FALSE; - // LZCNT - drflac__cpuid(info, 0x80000001); - drflac__gIsLZCNTSupported = (info[2] & (1 << 5)) != 0; + if (!isCPUCapsInitialized) { + int info[4] = {0}; - // SSE4.2 - drflac__cpuid(info, 1); - drflac__gIsSSE42Supported = (info[2] & (1 << 19)) != 0; + /* LZCNT */ + drflac__cpuid(info, 0x80000001); + drflac__gIsLZCNTSupported = (info[2] & (1 << 5)) != 0; + + /* SSE2 */ + drflac__gIsSSE2Supported = drflac_has_sse2(); + + /* SSE4.1 */ + drflac__gIsSSE41Supported = drflac_has_sse41(); + + /* Initialized. */ + isCPUCapsInitialized = DRFLAC_TRUE; + } } #endif -//// Endian Management //// +/* Endian Management */ static DRFLAC_INLINE drflac_bool32 drflac__is_little_endian() { #if defined(DRFLAC_X86) || defined(DRFLAC_X64) return DRFLAC_TRUE; +#elif defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN + return DRFLAC_TRUE; #else int n = 1; return (*(char*)&n) == 1; @@ -914,7 +1197,7 @@ static DRFLAC_INLINE drflac_bool32 drflac__is_little_endian() static DRFLAC_INLINE drflac_uint16 drflac__swap_endian_uint16(drflac_uint16 n) { -#ifdef DRFLAC_HAS_BYTESWAP_INTRINSIC +#ifdef DRFLAC_HAS_BYTESWAP16_INTRINSIC #if defined(_MSC_VER) return _byteswap_ushort(n); #elif defined(__GNUC__) || defined(__clang__) @@ -930,7 +1213,7 @@ 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_BYTESWAP_INTRINSIC +#ifdef DRFLAC_HAS_BYTESWAP32_INTRINSIC #if defined(_MSC_VER) return _byteswap_ulong(n); #elif defined(__GNUC__) || defined(__clang__) @@ -948,7 +1231,7 @@ 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_BYTESWAP_INTRINSIC +#ifdef DRFLAC_HAS_BYTESWAP64_INTRINSIC #if defined(_MSC_VER) return _byteswap_uint64(n); #elif defined(__GNUC__) || defined(__clang__) @@ -971,55 +1254,39 @@ static DRFLAC_INLINE drflac_uint64 drflac__swap_endian_uint64(drflac_uint64 n) static DRFLAC_INLINE drflac_uint16 drflac__be2host_16(drflac_uint16 n) { -#ifdef __linux__ - return be16toh(n); -#else if (drflac__is_little_endian()) { return drflac__swap_endian_uint16(n); } return n; -#endif } static DRFLAC_INLINE drflac_uint32 drflac__be2host_32(drflac_uint32 n) { -#ifdef __linux__ - return be32toh(n); -#else if (drflac__is_little_endian()) { return drflac__swap_endian_uint32(n); } return n; -#endif } static DRFLAC_INLINE drflac_uint64 drflac__be2host_64(drflac_uint64 n) { -#ifdef __linux__ - return be64toh(n); -#else if (drflac__is_little_endian()) { return drflac__swap_endian_uint64(n); } return n; -#endif } static DRFLAC_INLINE drflac_uint32 drflac__le2host_32(drflac_uint32 n) { -#ifdef __linux__ - return le32toh(n); -#else if (!drflac__is_little_endian()) { return drflac__swap_endian_uint32(n); } return n; -#endif } @@ -1036,7 +1303,7 @@ static DRFLAC_INLINE drflac_uint32 drflac__unsynchsafe_32(drflac_uint32 n) -// The CRC code below is based on this document: http://zlib.net/crc_v3.txt +/* The CRC code below is based on this document: http://zlib.net/crc_v3.txt */ static drflac_uint8 drflac__crc8_table[] = { 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, @@ -1098,8 +1365,6 @@ static DRFLAC_INLINE drflac_uint8 drflac_crc8_byte(drflac_uint8 crc, drflac_uint static DRFLAC_INLINE drflac_uint8 drflac_crc8(drflac_uint8 crc, drflac_uint32 data, drflac_uint32 count) { - drflac_assert(count <= 32); - #ifdef DR_FLAC_NO_CRC (void)crc; (void)data; @@ -1107,7 +1372,7 @@ static DRFLAC_INLINE drflac_uint8 drflac_crc8(drflac_uint8 crc, drflac_uint32 da return 0; #else #if 0 - // REFERENCE (use of this implementation requires an explicit flush by doing "drflac_crc8(crc, 0, 8);") + /* REFERENCE (use of this implementation requires an explicit flush by doing "drflac_crc8(crc, 0, 8);") */ drflac_uint8 p = 0x07; for (int i = count-1; i >= 0; --i) { drflac_uint8 bit = (data & (1 << i)) >> i; @@ -1119,13 +1384,19 @@ static DRFLAC_INLINE drflac_uint8 drflac_crc8(drflac_uint8 crc, drflac_uint32 da } return crc; #else - drflac_uint32 wholeBytes = count >> 3; - drflac_uint32 leftoverBits = count - (wholeBytes*8); + drflac_uint32 wholeBytes; + drflac_uint32 leftoverBits; + drflac_uint64 leftoverDataMask; static drflac_uint64 leftoverDataMaskTable[8] = { 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F }; - drflac_uint64 leftoverDataMask = leftoverDataMaskTable[leftoverBits]; + + drflac_assert(count <= 32); + + wholeBytes = count >> 3; + leftoverBits = count - (wholeBytes*8); + leftoverDataMask = leftoverDataMaskTable[leftoverBits]; switch (wholeBytes) { case 4: crc = drflac_crc8_byte(crc, (drflac_uint8)((data & (0xFF000000UL << leftoverBits)) >> (24 + leftoverBits))); @@ -1165,8 +1436,6 @@ static DRFLAC_INLINE drflac_uint16 drflac_crc16_bytes(drflac_uint16 crc, drflac_ static DRFLAC_INLINE drflac_uint16 drflac_crc16__32bit(drflac_uint16 crc, drflac_uint32 data, drflac_uint32 count) { - drflac_assert(count <= 64); - #ifdef DR_FLAC_NO_CRC (void)crc; (void)data; @@ -1174,7 +1443,7 @@ static DRFLAC_INLINE drflac_uint16 drflac_crc16__32bit(drflac_uint16 crc, drflac return 0; #else #if 0 - // REFERENCE (use of this implementation requires an explicit flush by doing "drflac_crc16(crc, 0, 16);") + /* REFERENCE (use of this implementation requires an explicit flush by doing "drflac_crc16(crc, 0, 16);") */ drflac_uint16 p = 0x8005; for (int i = count-1; i >= 0; --i) { drflac_uint16 bit = (data & (1ULL << i)) >> i; @@ -1187,13 +1456,19 @@ static DRFLAC_INLINE drflac_uint16 drflac_crc16__32bit(drflac_uint16 crc, drflac return crc; #else - drflac_uint32 wholeBytes = count >> 3; - drflac_uint32 leftoverBits = count - (wholeBytes*8); + drflac_uint32 wholeBytes; + drflac_uint32 leftoverBits; + drflac_uint64 leftoverDataMask; static drflac_uint64 leftoverDataMaskTable[8] = { 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F }; - drflac_uint64 leftoverDataMask = leftoverDataMaskTable[leftoverBits]; + + drflac_assert(count <= 64); + + wholeBytes = count >> 3; + leftoverBits = count - (wholeBytes*8); + leftoverDataMask = leftoverDataMaskTable[leftoverBits]; switch (wholeBytes) { default: @@ -1210,32 +1485,36 @@ static DRFLAC_INLINE drflac_uint16 drflac_crc16__32bit(drflac_uint16 crc, drflac static DRFLAC_INLINE drflac_uint16 drflac_crc16__64bit(drflac_uint16 crc, drflac_uint64 data, drflac_uint32 count) { - drflac_assert(count <= 64); - #ifdef DR_FLAC_NO_CRC (void)crc; (void)data; (void)count; return 0; #else - drflac_uint32 wholeBytes = count >> 3; - drflac_uint32 leftoverBits = count - (wholeBytes*8); + drflac_uint32 wholeBytes; + drflac_uint32 leftoverBits; + drflac_uint64 leftoverDataMask; static drflac_uint64 leftoverDataMaskTable[8] = { 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F }; - drflac_uint64 leftoverDataMask = leftoverDataMaskTable[leftoverBits]; + + drflac_assert(count <= 64); + + wholeBytes = count >> 3; + leftoverBits = count - (wholeBytes*8); + leftoverDataMask = leftoverDataMaskTable[leftoverBits]; switch (wholeBytes) { default: - case 8: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & ((drflac_uint64)0xFF00000000000000 << leftoverBits)) >> (56 + leftoverBits))); - case 7: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & ((drflac_uint64)0x00FF000000000000 << leftoverBits)) >> (48 + leftoverBits))); - case 6: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & ((drflac_uint64)0x0000FF0000000000 << leftoverBits)) >> (40 + leftoverBits))); - case 5: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & ((drflac_uint64)0x000000FF00000000 << leftoverBits)) >> (32 + leftoverBits))); - case 4: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & ((drflac_uint64)0x00000000FF000000 << leftoverBits)) >> (24 + leftoverBits))); - case 3: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & ((drflac_uint64)0x0000000000FF0000 << leftoverBits)) >> (16 + leftoverBits))); - case 2: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & ((drflac_uint64)0x000000000000FF00 << leftoverBits)) >> ( 8 + leftoverBits))); - case 1: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & ((drflac_uint64)0x00000000000000FF << leftoverBits)) >> ( 0 + leftoverBits))); + case 8: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0xFF000000 << 32) << leftoverBits)) >> (56 + leftoverBits))); /* Weird "<< 32" bitshift is required for C89 because it doesn't support 64-bit constants. Should be optimized out by a good compiler. */ + case 7: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x00FF0000 << 32) << leftoverBits)) >> (48 + leftoverBits))); + case 6: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x0000FF00 << 32) << leftoverBits)) >> (40 + leftoverBits))); + case 5: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x000000FF << 32) << leftoverBits)) >> (32 + leftoverBits))); + case 4: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0xFF000000 ) << leftoverBits)) >> (24 + leftoverBits))); + case 3: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x00FF0000 ) << leftoverBits)) >> (16 + leftoverBits))); + case 2: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x0000FF00 ) << leftoverBits)) >> ( 8 + leftoverBits))); + case 1: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x000000FF ) << leftoverBits)) >> ( 0 + leftoverBits))); case 0: if (leftoverBits > 0) crc = (crc << leftoverBits) ^ drflac__crc16_table[(crc >> (16 - leftoverBits)) ^ (data & leftoverDataMask)]; } return crc; @@ -1259,27 +1538,26 @@ static DRFLAC_INLINE drflac_uint16 drflac_crc16(drflac_uint16 crc, drflac_cache_ #define drflac__be2host__cache_line drflac__be2host_32 #endif -// BIT READING ATTEMPT #2 -// -// This uses a 32- or 64-bit bit-shifted cache - as bits are read, the cache is shifted such that the first valid bit is sitting -// on the most significant bit. It uses the notion of an L1 and L2 cache (borrowed from CPU architecture), where the L1 cache -// is a 32- or 64-bit unsigned integer (depending on whether or not a 32- or 64-bit build is being compiled) and the L2 is an -// array of "cache lines", with each cache line being the same size as the L1. The L2 is a buffer of about 4KB and is where data -// from onRead() is read into. -#define DRFLAC_CACHE_L1_SIZE_BYTES(bs) (sizeof((bs)->cache)) -#define DRFLAC_CACHE_L1_SIZE_BITS(bs) (sizeof((bs)->cache)*8) -#define DRFLAC_CACHE_L1_BITS_REMAINING(bs) (DRFLAC_CACHE_L1_SIZE_BITS(bs) - ((bs)->consumedBits)) -#ifdef DRFLAC_64BIT -#define DRFLAC_CACHE_L1_SELECTION_MASK(_bitCount) (~(((drflac_uint64)-1LL) >> (_bitCount))) -#else -#define DRFLAC_CACHE_L1_SELECTION_MASK(_bitCount) (~(((drflac_uint32)-1) >> (_bitCount))) -#endif -#define DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, _bitCount) (DRFLAC_CACHE_L1_SIZE_BITS(bs) - (_bitCount)) -#define DRFLAC_CACHE_L1_SELECT(bs, _bitCount) (((bs)->cache) & DRFLAC_CACHE_L1_SELECTION_MASK(_bitCount)) -#define DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, _bitCount) (DRFLAC_CACHE_L1_SELECT((bs), _bitCount) >> DRFLAC_CACHE_L1_SELECTION_SHIFT((bs), _bitCount)) -#define DRFLAC_CACHE_L2_SIZE_BYTES(bs) (sizeof((bs)->cacheL2)) -#define DRFLAC_CACHE_L2_LINE_COUNT(bs) (DRFLAC_CACHE_L2_SIZE_BYTES(bs) / sizeof((bs)->cacheL2[0])) -#define DRFLAC_CACHE_L2_LINES_REMAINING(bs) (DRFLAC_CACHE_L2_LINE_COUNT(bs) - (bs)->nextL2Line) +/* +BIT READING ATTEMPT #2 + +This uses a 32- or 64-bit bit-shifted cache - as bits are read, the cache is shifted such that the first valid bit is sitting +on the most significant bit. It uses the notion of an L1 and L2 cache (borrowed from CPU architecture), where the L1 cache +is a 32- or 64-bit unsigned integer (depending on whether or not a 32- or 64-bit build is being compiled) and the L2 is an +array of "cache lines", with each cache line being the same size as the L1. The L2 is a buffer of about 4KB and is where data +from onRead() is read into. +*/ +#define DRFLAC_CACHE_L1_SIZE_BYTES(bs) (sizeof((bs)->cache)) +#define DRFLAC_CACHE_L1_SIZE_BITS(bs) (sizeof((bs)->cache)*8) +#define DRFLAC_CACHE_L1_BITS_REMAINING(bs) (DRFLAC_CACHE_L1_SIZE_BITS(bs) - (bs)->consumedBits) +#define DRFLAC_CACHE_L1_SELECTION_MASK(_bitCount) (~((~(drflac_cache_t)0) >> (_bitCount))) +#define DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, _bitCount) (DRFLAC_CACHE_L1_SIZE_BITS(bs) - (_bitCount)) +#define DRFLAC_CACHE_L1_SELECT(bs, _bitCount) (((bs)->cache) & DRFLAC_CACHE_L1_SELECTION_MASK(_bitCount)) +#define DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, _bitCount) (DRFLAC_CACHE_L1_SELECT((bs), (_bitCount)) >> DRFLAC_CACHE_L1_SELECTION_SHIFT((bs), (_bitCount))) +#define DRFLAC_CACHE_L1_SELECT_AND_SHIFT_SAFE(bs, _bitCount)(DRFLAC_CACHE_L1_SELECT((bs), (_bitCount)) >> (DRFLAC_CACHE_L1_SELECTION_SHIFT((bs), (_bitCount)) & (DRFLAC_CACHE_L1_SIZE_BITS(bs)-1))) +#define DRFLAC_CACHE_L2_SIZE_BYTES(bs) (sizeof((bs)->cacheL2)) +#define DRFLAC_CACHE_L2_LINE_COUNT(bs) (DRFLAC_CACHE_L2_SIZE_BYTES(bs) / sizeof((bs)->cacheL2[0])) +#define DRFLAC_CACHE_L2_LINES_REMAINING(bs) (DRFLAC_CACHE_L2_LINE_COUNT(bs) - (bs)->nextL2Line) #ifndef DR_FLAC_NO_CRC @@ -1297,19 +1575,23 @@ static DRFLAC_INLINE void drflac__update_crc16(drflac_bs* bs) 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. + /* 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); - // The bits that were read from the L1 cache need to be accumulated. The number of bytes needing to be accumulated is determined - // by the number of bits that have been consumed. + /* + The bits that were read from the L1 cache need to be accumulated. The number of bytes needing to be accumulated is determined + by the number of bits that have been consumed. + */ if (DRFLAC_CACHE_L1_BITS_REMAINING(bs) == 0) { drflac__update_crc16(bs); } else { - // We only accumulate the consumed bits. + /* We only accumulate the consumed bits. */ bs->crc16 = drflac_crc16_bytes(bs->crc16, bs->crc16Cache >> DRFLAC_CACHE_L1_BITS_REMAINING(bs), (bs->consumedBits >> 3) - bs->crc16CacheIgnoredBytes); - // The bits that we just accumulated should never be accumulated again. We need to keep track of how many bytes were accumulated - // so we can handle that later. + /* + The bits that we just accumulated should never be accumulated again. We need to keep track of how many bytes were accumulated + so we can handle that later. + */ bs->crc16CacheIgnoredBytes = bs->consumedBits >> 3; } @@ -1319,19 +1601,24 @@ static DRFLAC_INLINE drflac_uint16 drflac__flush_crc16(drflac_bs* bs) static DRFLAC_INLINE drflac_bool32 drflac__reload_l1_cache_from_l2(drflac_bs* bs) { - // Fast path. Try loading straight from L2. + size_t bytesRead; + size_t alignedL1LineCount; + + /* Fast path. Try loading straight from L2. */ if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { bs->cache = bs->cacheL2[bs->nextL2Line++]; return DRFLAC_TRUE; } - // If we get here it means we've run out of data in the L2 cache. We'll need to fetch more from the client, if there's - // any left. + /* + If we get here it means we've run out of data in the L2 cache. We'll need to fetch more from the client, if there's + any left. + */ if (bs->unalignedByteCount > 0) { - return DRFLAC_FALSE; // If we have any unaligned bytes it means there's no more aligned bytes left in the client. + return DRFLAC_FALSE; /* If we have any unaligned bytes it means there's no more aligned bytes left in the client. */ } - size_t bytesRead = bs->onRead(bs->pUserData, bs->cacheL2, DRFLAC_CACHE_L2_SIZE_BYTES(bs)); + bytesRead = bs->onRead(bs->pUserData, bs->cacheL2, DRFLAC_CACHE_L2_SIZE_BYTES(bs)); bs->nextL2Line = 0; if (bytesRead == DRFLAC_CACHE_L2_SIZE_BYTES(bs)) { @@ -1340,13 +1627,15 @@ static DRFLAC_INLINE drflac_bool32 drflac__reload_l1_cache_from_l2(drflac_bs* bs } - // If we get here it means we were unable to retrieve enough data to fill the entire L2 cache. It probably - // means we've just reached the end of the file. We need to move the valid data down to the end of the buffer - // and adjust the index of the next line accordingly. Also keep in mind that the L2 cache must be aligned to - // the size of the L1 so we'll need to seek backwards by any misaligned bytes. - size_t alignedL1LineCount = bytesRead / DRFLAC_CACHE_L1_SIZE_BYTES(bs); + /* + If we get here it means we were unable to retrieve enough data to fill the entire L2 cache. It probably + means we've just reached the end of the file. We need to move the valid data down to the end of the buffer + and adjust the index of the next line accordingly. Also keep in mind that the L2 cache must be aligned to + the size of the L1 so we'll need to seek backwards by any misaligned bytes. + */ + alignedL1LineCount = bytesRead / DRFLAC_CACHE_L1_SIZE_BYTES(bs); - // We need to keep track of any unaligned bytes for later use. + /* We need to keep track of any unaligned bytes for later use. */ bs->unalignedByteCount = bytesRead - (alignedL1LineCount * DRFLAC_CACHE_L1_SIZE_BYTES(bs)); if (bs->unalignedByteCount > 0) { bs->unalignedCache = bs->cacheL2[alignedL1LineCount]; @@ -1354,7 +1643,8 @@ static DRFLAC_INLINE drflac_bool32 drflac__reload_l1_cache_from_l2(drflac_bs* bs if (alignedL1LineCount > 0) { size_t offset = DRFLAC_CACHE_L2_LINE_COUNT(bs) - alignedL1LineCount; - for (size_t i = alignedL1LineCount; i > 0; --i) { + size_t i; + for (i = alignedL1LineCount; i > 0; --i) { bs->cacheL2[i-1 + offset] = bs->cacheL2[i-1]; } @@ -1362,7 +1652,7 @@ static DRFLAC_INLINE drflac_bool32 drflac__reload_l1_cache_from_l2(drflac_bs* bs bs->cache = bs->cacheL2[bs->nextL2Line++]; return DRFLAC_TRUE; } else { - // If we get into this branch it means we weren't able to load any L1-aligned data. + /* If we get into this branch it means we weren't able to load any L1-aligned data. */ bs->nextL2Line = DRFLAC_CACHE_L2_LINE_COUNT(bs); return DRFLAC_FALSE; } @@ -1370,11 +1660,13 @@ static DRFLAC_INLINE drflac_bool32 drflac__reload_l1_cache_from_l2(drflac_bs* bs static drflac_bool32 drflac__reload_cache(drflac_bs* bs) { + size_t bytesRead; + #ifndef DR_FLAC_NO_CRC drflac__update_crc16(bs); #endif - // Fast path. Try just moving the next value in the L2 cache to the L1 cache. + /* Fast path. Try just moving the next value in the L2 cache to the L1 cache. */ if (drflac__reload_l1_cache_from_l2(bs)) { bs->cache = drflac__be2host__cache_line(bs->cache); bs->consumedBits = 0; @@ -1384,13 +1676,16 @@ static drflac_bool32 drflac__reload_cache(drflac_bs* bs) return DRFLAC_TRUE; } - // Slow path. + /* Slow path. */ - // If we get here it means we have failed to load the L1 cache from the L2. Likely we've just reached the end of the stream and the last - // few bytes did not meet the alignment requirements for the L2 cache. In this case we need to fall back to a slower path and read the - // data from the unaligned cache. - size_t bytesRead = bs->unalignedByteCount; + /* + If we get here it means we have failed to load the L1 cache from the L2. Likely we've just reached the end of the stream and the last + few bytes did not meet the alignment requirements for the L2 cache. In this case we need to fall back to a slower path and read the + data from the unaligned cache. + */ + bytesRead = bs->unalignedByteCount; if (bytesRead == 0) { + bs->consumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs); /* <-- The stream has been exhausted, so marked the bits as consumed. */ return DRFLAC_FALSE; } @@ -1398,8 +1693,8 @@ static drflac_bool32 drflac__reload_cache(drflac_bs* bs) bs->consumedBits = (drflac_uint32)(DRFLAC_CACHE_L1_SIZE_BYTES(bs) - bytesRead) * 8; bs->cache = drflac__be2host__cache_line(bs->unalignedCache); - bs->cache &= DRFLAC_CACHE_L1_SELECTION_MASK(DRFLAC_CACHE_L1_SIZE_BITS(bs) - bs->consumedBits); // <-- Make sure the consumed bits are always set to zero. Other parts of the library depend on this property. - bs->unalignedByteCount = 0; // <-- At this point the unaligned bytes have been moved into the cache and we thus have no more unaligned bytes. + bs->cache &= DRFLAC_CACHE_L1_SELECTION_MASK(DRFLAC_CACHE_L1_BITS_REMAINING(bs)); /* <-- Make sure the consumed bits are always set to zero. Other parts of the library depend on this property. */ + bs->unalignedByteCount = 0; /* <-- At this point the unaligned bytes have been moved into the cache and we thus have no more unaligned bytes. */ #ifndef DR_FLAC_NO_CRC bs->crc16Cache = bs->cache >> bs->consumedBits; @@ -1410,10 +1705,10 @@ static drflac_bool32 drflac__reload_cache(drflac_bs* bs) static void drflac__reset_cache(drflac_bs* bs) { - bs->nextL2Line = DRFLAC_CACHE_L2_LINE_COUNT(bs); // <-- This clears the L2 cache. - bs->consumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs); // <-- This clears the L1 cache. + bs->nextL2Line = DRFLAC_CACHE_L2_LINE_COUNT(bs); /* <-- This clears the L2 cache. */ + bs->consumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs); /* <-- This clears the L1 cache. */ bs->cache = 0; - bs->unalignedByteCount = 0; // <-- This clears the trailing unaligned bytes. + bs->unalignedByteCount = 0; /* <-- This clears the trailing unaligned bytes. */ bs->unalignedCache = 0; #ifndef DR_FLAC_NO_CRC @@ -1437,18 +1732,31 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_uint32(drflac_bs* bs, unsigned i } if (bitCount <= DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + /* + If we want to load all 32-bits from a 32-bit cache we need to do it slightly differently because we can't do + a 32-bit shift on a 32-bit integer. This will never be the case on 64-bit caches, so we can have a slightly + more optimal solution for this. + */ +#ifdef DRFLAC_64BIT + *pResultOut = (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCount); + bs->consumedBits += bitCount; + bs->cache <<= bitCount; +#else if (bitCount < DRFLAC_CACHE_L1_SIZE_BITS(bs)) { *pResultOut = (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCount); bs->consumedBits += bitCount; bs->cache <<= bitCount; } else { + /* Cannot shift by 32-bits, so need to do it differently. */ *pResultOut = (drflac_uint32)bs->cache; bs->consumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs); bs->cache = 0; } +#endif + return DRFLAC_TRUE; } else { - // 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. + /* 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); @@ -1466,17 +1774,19 @@ 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_uint32 result; if (!drflac__read_uint32(bs, bitCount, &result)) { return DRFLAC_FALSE; } - drflac_uint32 signbit = ((result >> (bitCount-1)) & 0x01); + signbit = ((result >> (bitCount-1)) & 0x01); result |= (~signbit + 1) << bitCount; *pResult = (drflac_int32)result; @@ -1486,15 +1796,16 @@ static drflac_bool32 drflac__read_int32(drflac_bs* bs, unsigned int bitCount, dr #ifdef DRFLAC_64BIT static drflac_bool32 drflac__read_uint64(drflac_bs* bs, unsigned int bitCount, drflac_uint64* pResultOut) { + drflac_uint32 resultHi; + drflac_uint32 resultLo; + drflac_assert(bitCount <= 64); drflac_assert(bitCount > 32); - drflac_uint32 resultHi; if (!drflac__read_uint32(bs, bitCount - 32, &resultHi)) { return DRFLAC_FALSE; } - drflac_uint32 resultLo; if (!drflac__read_uint32(bs, 32, &resultLo)) { return DRFLAC_FALSE; } @@ -1504,18 +1815,20 @@ static drflac_bool32 drflac__read_uint64(drflac_bs* bs, unsigned int bitCount, d } #endif -// Function below is unused, but leaving it here in case I need to quickly add it again. +/* Function below is unused, but leaving it here in case I need to quickly add it again. */ #if 0 static drflac_bool32 drflac__read_int64(drflac_bs* bs, unsigned int bitCount, drflac_int64* pResultOut) { + drflac_uint64 result; + drflac_uint64 signbit; + drflac_assert(bitCount <= 64); - drflac_uint64 result; if (!drflac__read_uint64(bs, bitCount, &result)) { return DRFLAC_FALSE; } - drflac_uint64 signbit = ((result >> (bitCount-1)) & 0x01); + signbit = ((result >> (bitCount-1)) & 0x01); result |= (~signbit + 1) << bitCount; *pResultOut = (drflac_int64)result; @@ -1525,12 +1838,13 @@ static drflac_bool32 drflac__read_int64(drflac_bs* bs, unsigned int bitCount, dr static drflac_bool32 drflac__read_uint16(drflac_bs* bs, unsigned int bitCount, drflac_uint16* pResult) { + drflac_uint32 result; + drflac_assert(bs != NULL); drflac_assert(pResult != NULL); drflac_assert(bitCount > 0); drflac_assert(bitCount <= 16); - drflac_uint32 result; if (!drflac__read_uint32(bs, bitCount, &result)) { return DRFLAC_FALSE; } @@ -1542,12 +1856,13 @@ static drflac_bool32 drflac__read_uint16(drflac_bs* bs, unsigned int bitCount, d #if 0 static drflac_bool32 drflac__read_int16(drflac_bs* bs, unsigned int bitCount, drflac_int16* pResult) { + drflac_int32 result; + drflac_assert(bs != NULL); drflac_assert(pResult != NULL); drflac_assert(bitCount > 0); drflac_assert(bitCount <= 16); - drflac_int32 result; if (!drflac__read_int32(bs, bitCount, &result)) { return DRFLAC_FALSE; } @@ -1559,12 +1874,13 @@ static drflac_bool32 drflac__read_int16(drflac_bs* bs, unsigned int bitCount, dr static drflac_bool32 drflac__read_uint8(drflac_bs* bs, unsigned int bitCount, drflac_uint8* pResult) { + drflac_uint32 result; + drflac_assert(bs != NULL); drflac_assert(pResult != NULL); drflac_assert(bitCount > 0); drflac_assert(bitCount <= 8); - drflac_uint32 result; if (!drflac__read_uint32(bs, bitCount, &result)) { return DRFLAC_FALSE; } @@ -1575,12 +1891,13 @@ static drflac_bool32 drflac__read_uint8(drflac_bs* bs, unsigned int bitCount, dr static drflac_bool32 drflac__read_int8(drflac_bs* bs, unsigned int bitCount, drflac_int8* pResult) { + drflac_int32 result; + drflac_assert(bs != NULL); drflac_assert(pResult != NULL); drflac_assert(bitCount > 0); drflac_assert(bitCount <= 8); - drflac_int32 result; if (!drflac__read_int32(bs, bitCount, &result)) { return DRFLAC_FALSE; } @@ -1597,12 +1914,12 @@ static drflac_bool32 drflac__seek_bits(drflac_bs* bs, size_t bitsToSeek) bs->cache <<= bitsToSeek; return DRFLAC_TRUE; } else { - // It straddles the cached data. This function isn't called too frequently so I'm favouring simplicity here. + /* It straddles the cached data. This function isn't called too frequently so I'm favouring simplicity here. */ bitsToSeek -= DRFLAC_CACHE_L1_BITS_REMAINING(bs); bs->consumedBits += DRFLAC_CACHE_L1_BITS_REMAINING(bs); bs->cache = 0; - // Simple case. Seek in groups of the same number as bits that fit within a cache line. + /* Simple case. Seek in groups of the same number as bits that fit within a cache line. */ #ifdef DRFLAC_64BIT while (bitsToSeek >= DRFLAC_CACHE_L1_SIZE_BITS(bs)) { drflac_uint64 bin; @@ -1621,7 +1938,7 @@ static drflac_bool32 drflac__seek_bits(drflac_bs* bs, size_t bitsToSeek) } #endif - // Whole leftover bytes. + /* Whole leftover bytes. */ while (bitsToSeek >= 8) { drflac_uint8 bin; if (!drflac__read_uint8(bs, 8, &bin)) { @@ -1630,13 +1947,13 @@ static drflac_bool32 drflac__seek_bits(drflac_bs* bs, size_t bitsToSeek) bitsToSeek -= 8; } - // Leftover bits. + /* Leftover bits. */ if (bitsToSeek > 0) { drflac_uint8 bin; if (!drflac__read_uint8(bs, (drflac_uint32)bitsToSeek, &bin)) { return DRFLAC_FALSE; } - bitsToSeek = 0; // <-- Necessary for the assert below. + bitsToSeek = 0; /* <-- Necessary for the assert below. */ } drflac_assert(bitsToSeek == 0); @@ -1645,23 +1962,26 @@ 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. +/* 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); - // 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 - // thing to do is align to the next byte. + /* + 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 + thing to do is align to the next byte. + */ if (!drflac__seek_bits(bs, DRFLAC_CACHE_L1_BITS_REMAINING(bs) & 7)) { return DRFLAC_FALSE; } for (;;) { + drflac_uint8 hi; + #ifndef DR_FLAC_NO_CRC drflac__reset_crc16(bs); #endif - drflac_uint8 hi; if (!drflac__read_uint8(bs, 8, &hi)) { return DRFLAC_FALSE; } @@ -1682,8 +2002,8 @@ static drflac_bool32 drflac__find_and_seek_to_next_sync_code(drflac_bs* bs) } } - // Should never get here. - //return DRFLAC_FALSE; + /* Should never get here. */ + /*return DRFLAC_FALSE;*/ } @@ -1696,6 +2016,7 @@ static drflac_bool32 drflac__find_and_seek_to_next_sync_code(drflac_bs* bs) static DRFLAC_INLINE drflac_uint32 drflac__clz_software(drflac_cache_t x) { + drflac_uint32 n; static drflac_uint32 clz_table_4[] = { 0, 4, @@ -1704,13 +2025,17 @@ static DRFLAC_INLINE drflac_uint32 drflac__clz_software(drflac_cache_t x) 1, 1, 1, 1, 1, 1, 1, 1 }; - drflac_uint32 n = clz_table_4[x >> (sizeof(x)*8 - 4)]; + if (x == 0) { + return sizeof(x)*8; + } + + n = clz_table_4[x >> (sizeof(x)*8 - 4)]; if (n == 0) { #ifdef DRFLAC_64BIT - if ((x & 0xFFFFFFFF00000000ULL) == 0) { n = 32; x <<= 32; } - if ((x & 0xFFFF000000000000ULL) == 0) { n += 16; x <<= 16; } - if ((x & 0xFF00000000000000ULL) == 0) { n += 8; x <<= 8; } - if ((x & 0xF000000000000000ULL) == 0) { n += 4; x <<= 4; } + if ((x & ((drflac_uint64)0xFFFFFFFF << 32)) == 0) { n = 32; x <<= 32; } + if ((x & ((drflac_uint64)0xFFFF0000 << 32)) == 0) { n += 16; x <<= 16; } + if ((x & ((drflac_uint64)0xFF000000 << 32)) == 0) { n += 8; x <<= 8; } + if ((x & ((drflac_uint64)0xF0000000 << 32)) == 0) { n += 4; x <<= 4; } #else if ((x & 0xFFFF0000) == 0) { n = 16; x <<= 16; } if ((x & 0xFF000000) == 0) { n += 8; x <<= 8; } @@ -1725,7 +2050,7 @@ 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() { - // If the compiler itself does not support the intrinsic then we'll need to return 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 @@ -1743,13 +2068,16 @@ 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((unsigned long long)x); + return (drflac_uint32)__builtin_clzll((drflac_uint64)x); #else - return (drflac_uint32)__builtin_clzl((unsigned long)x); + return (drflac_uint32)__builtin_clzl((drflac_uint32)x); #endif #else - // Unsupported compiler. + /* Unsupported compiler. */ #error "This compiler does not support the lzcnt intrinsic." #endif #endif @@ -1757,9 +2085,16 @@ static DRFLAC_INLINE drflac_uint32 drflac__clz_lzcnt(drflac_cache_t x) #endif #ifdef DRFLAC_IMPLEMENT_CLZ_MSVC +#include /* For BitScanReverse(). */ + static DRFLAC_INLINE drflac_uint32 drflac__clz_msvc(drflac_cache_t x) { drflac_uint32 n; + + if (x == 0) { + return sizeof(x)*8; + } + #ifdef DRFLAC_64BIT _BitScanReverse64((unsigned long*)&n, x); #else @@ -1771,25 +2106,26 @@ static DRFLAC_INLINE drflac_uint32 drflac__clz_msvc(drflac_cache_t x) static DRFLAC_INLINE drflac_uint32 drflac__clz(drflac_cache_t x) { - // This function assumes at least one bit is set. Checking for 0 needs to be done at a higher level, outside this function. #ifdef DRFLAC_IMPLEMENT_CLZ_LZCNT if (drflac__is_lzcnt_supported()) { return drflac__clz_lzcnt(x); } else #endif { - #ifdef DRFLAC_IMPLEMENT_CLZ_MSVC +#ifdef DRFLAC_IMPLEMENT_CLZ_MSVC return drflac__clz_msvc(x); - #else +#else return drflac__clz_software(x); - #endif +#endif } } -static inline drflac_bool32 drflac__seek_past_next_set_bit(drflac_bs* bs, unsigned int* pOffsetOut) +static DRFLAC_INLINE drflac_bool32 drflac__seek_past_next_set_bit(drflac_bs* bs, unsigned int* pOffsetOut) { drflac_uint32 zeroCounter = 0; + drflac_uint32 setBitOffsetPlus1; + while (bs->cache == 0) { zeroCounter += (drflac_uint32)DRFLAC_CACHE_L1_BITS_REMAINING(bs); if (!drflac__reload_cache(bs)) { @@ -1797,7 +2133,7 @@ static inline drflac_bool32 drflac__seek_past_next_set_bit(drflac_bs* bs, unsign } } - drflac_uint32 setBitOffsetPlus1 = drflac__clz(bs->cache); + setBitOffsetPlus1 = drflac__clz(bs->cache); setBitOffsetPlus1 += 1; bs->consumedBits += setBitOffsetPlus1; @@ -1814,9 +2150,11 @@ static drflac_bool32 drflac__seek_to_byte(drflac_bs* bs, drflac_uint64 offsetFro 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 - // is intentional because it simplifies the implementation of the onSeek callbacks), however offsetFromStart is unsigned 64-bit. - // To resolve we just need to do an initial seek from the start, and then a series of offset seeks to make up the remainder. + /* + Seeking from the start is not quite as trivial as it sounds because the onSeek callback takes a signed 32-bit integer (which + is intentional because it simplifies the implementation of the onSeek callbacks), however offsetFromStart is unsigned 64-bit. + To resolve we just need to do an initial seek from the start, and then a series of offset seeks to make up the remainder. + */ if (offsetFromStart > 0x7FFFFFFF) { drflac_uint64 bytesRemaining = offsetFromStart; if (!bs->onSeek(bs->pUserData, 0x7FFFFFFF, drflac_seek_origin_start)) { @@ -1842,7 +2180,7 @@ static drflac_bool32 drflac__seek_to_byte(drflac_bs* bs, drflac_uint64 offsetFro } } - // The cache should be reset to force a reload of fresh data from the client. + /* The cache should be reset to force a reload of fresh data from the client. */ drflac__reset_cache(bs); return DRFLAC_TRUE; } @@ -1850,12 +2188,18 @@ static drflac_bool32 drflac__seek_to_byte(drflac_bs* bs, drflac_uint64 offsetFro static drflac_result drflac__read_utf8_coded_number(drflac_bs* bs, drflac_uint64* pNumberOut, drflac_uint8* pCRCOut) { + drflac_uint8 crc; + drflac_uint64 result; + unsigned char utf8[7] = {0}; + int byteCount; + int i; + drflac_assert(bs != NULL); drflac_assert(pNumberOut != NULL); + drflac_assert(pCRCOut != NULL); - drflac_uint8 crc = *pCRCOut; + crc = *pCRCOut; - unsigned char utf8[7] = {0}; if (!drflac__read_uint8(bs, 8, utf8)) { *pNumberOut = 0; return DRFLAC_END_OF_STREAM; @@ -1868,7 +2212,7 @@ static drflac_result drflac__read_utf8_coded_number(drflac_bs* bs, drflac_uint64 return DRFLAC_SUCCESS; } - int byteCount = 1; + byteCount = 1; if ((utf8[0] & 0xE0) == 0xC0) { byteCount = 2; } else if ((utf8[0] & 0xF0) == 0xE0) { @@ -1883,14 +2227,14 @@ static drflac_result drflac__read_utf8_coded_number(drflac_bs* bs, drflac_uint64 byteCount = 7; } else { *pNumberOut = 0; - return DRFLAC_CRC_MISMATCH; // Bad UTF-8 encoding. + return DRFLAC_CRC_MISMATCH; /* Bad UTF-8 encoding. */ } - // Read extra bytes. + /* Read extra bytes. */ drflac_assert(byteCount > 1); - drflac_uint64 result = (drflac_uint64)(utf8[0] & (0xFF >> (byteCount + 1))); - for (int i = 1; i < byteCount; ++i) { + 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; @@ -1907,20 +2251,21 @@ static drflac_result drflac__read_utf8_coded_number(drflac_bs* bs, drflac_uint64 +/* +The next two functions are responsible for calculating the prediction. -// The next two functions are responsible for calculating the prediction. -// -// When the bits per sample is >16 we need to use 64-bit integer arithmetic because otherwise we'll run out of precision. It's -// safe to assume this will be slower on 32-bit platforms so we use a more optimal solution when the bits per sample is <=16. +When the bits per sample is >16 we need to use 64-bit integer arithmetic because otherwise we'll run out of precision. It's +safe to assume this will be slower on 32-bit platforms so we use a more optimal solution when the bits per sample is <=16. +*/ static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_32(drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) { - drflac_assert(order <= 32); - - // 32-bit version. - - // VC++ optimizes this to a single jmp. I've not yet verified this for other compilers. drflac_int32 prediction = 0; + drflac_assert(order <= 32); + + /* 32-bit version. */ + + /* VC++ optimizes this to a single jmp. I've not yet verified this for other compilers. */ switch (order) { case 32: prediction += coefficients[31] * pDecodedSamples[-32]; @@ -1962,13 +2307,14 @@ static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_32(drflac_uint32 static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_64(drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) { + drflac_int64 prediction; + drflac_assert(order <= 32); - // 64-bit version. + /* 64-bit version. */ - // This method is faster on the 32-bit build when compiling with VC++. See note below. + /* This method is faster on the 32-bit build when compiling with VC++. See note below. */ #ifndef DRFLAC_64BIT - drflac_int64 prediction; if (order == 8) { prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; @@ -2085,18 +2431,21 @@ static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_64(drflac_uint32 } else { + int j; + prediction = 0; - for (int j = 0; j < (int)order; ++j) { + for (j = 0; j < (int)order; ++j) { prediction += coefficients[j] * (drflac_int64)pDecodedSamples[-j-1]; } } #endif - // VC++ optimizes this to a single jmp instruction, but only the 64-bit build. The 32-bit build generates less efficient code for some - // reason. The ugly version above is faster so we'll just switch between the two depending on the target platform. + /* + VC++ optimizes this to a single jmp instruction, but only the 64-bit build. The 32-bit build generates less efficient code for some + reason. The ugly version above is faster so we'll just switch between the two depending on the target platform. + */ #ifdef DRFLAC_64BIT - drflac_int64 prediction = 0; - + prediction = 0; switch (order) { case 32: prediction += coefficients[31] * (drflac_int64)pDecodedSamples[-32]; @@ -2137,16 +2486,451 @@ 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 -// Reference implementation for reading and decoding samples with residual. This is intentionally left unoptimized for the -// sake of readability and should only be used as a reference. +/* +Reference implementation for reading and decoding samples with residual. This is intentionally left unoptimized for the +sake of readability and should only be used as a reference. +*/ static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(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 i; + drflac_assert(bs != NULL); drflac_assert(count > 0); drflac_assert(pSamplesOut != NULL); - for (drflac_uint32 i = 0; i < count; ++i) { + for (i = 0; i < count; ++i) { drflac_uint32 zeroCounter = 0; for (;;) { drflac_uint8 bit; @@ -2193,6 +2977,8 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drfla static drflac_bool32 drflac__read_rice_parts__reference(drflac_bs* bs, drflac_uint8 riceParam, drflac_uint32* pZeroCounterOut, drflac_uint32* pRiceParamPartOut) { drflac_uint32 zeroCounter = 0; + drflac_uint32 decodedRice; + for (;;) { drflac_uint8 bit; if (!drflac__read_uint8(bs, 1, &bit)) { @@ -2206,7 +2992,6 @@ static drflac_bool32 drflac__read_rice_parts__reference(drflac_bs* bs, drflac_ui } } - drflac_uint32 decodedRice; if (riceParam > 0) { if (!drflac__read_uint32(bs, riceParam, &decodedRice)) { return DRFLAC_FALSE; @@ -2221,13 +3006,20 @@ static drflac_bool32 drflac__read_rice_parts__reference(drflac_bs* bs, drflac_ui } #endif +#if 0 static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts(drflac_bs* bs, drflac_uint8 riceParam, drflac_uint32* pZeroCounterOut, drflac_uint32* pRiceParamPartOut) { - drflac_cache_t riceParamMask = DRFLAC_CACHE_L1_SELECTION_MASK(riceParam); - drflac_cache_t resultHiShift = DRFLAC_CACHE_L1_SIZE_BITS(bs) - riceParam; + drflac_cache_t riceParamMask; + drflac_uint32 zeroCounter; + drflac_uint32 setBitOffsetPlus1; + 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_uint32 zeroCounter = 0; + riceParamMask = DRFLAC_CACHE_L1_SELECTION_MASK(riceParam); + + zeroCounter = 0; while (bs->cache == 0) { zeroCounter += (drflac_uint32)DRFLAC_CACHE_L1_BITS_REMAINING(bs); if (!drflac__reload_cache(bs)) { @@ -2235,64 +3027,409 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts(drflac_bs* bs, drflac } } - drflac_uint32 setBitOffsetPlus1 = drflac__clz(bs->cache); + setBitOffsetPlus1 = drflac__clz(bs->cache); zeroCounter += setBitOffsetPlus1; setBitOffsetPlus1 += 1; - - drflac_uint32 riceParamPart; - drflac_uint32 riceLength = setBitOffsetPlus1 + riceParam; + riceLength = setBitOffsetPlus1 + riceParam; if (riceLength < DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { - riceParamPart = (drflac_uint32)((bs->cache & (riceParamMask >> setBitOffsetPlus1)) >> (DRFLAC_CACHE_L1_SIZE_BITS(bs) - riceLength)); + riceParamPart = (drflac_uint32)((bs->cache & (riceParamMask >> setBitOffsetPlus1)) >> DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, riceLength)); bs->consumedBits += riceLength; bs->cache <<= riceLength; } else { - bs->consumedBits += riceLength; - if (setBitOffsetPlus1 < DRFLAC_CACHE_L1_SIZE_BITS(bs)) { - bs->cache <<= setBitOffsetPlus1; - } + drflac_uint32 bitCountLo; + drflac_cache_t resultHi; - // 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 bitCountLo = bs->consumedBits - DRFLAC_CACHE_L1_SIZE_BITS(bs); - drflac_cache_t resultHi = bs->cache & riceParamMask; // <-- This mask is OK because all bits after the first bits are always zero. + bs->consumedBits += riceLength; + bs->cache <<= setBitOffsetPlus1 & (DRFLAC_CACHE_L1_SIZE_BITS(bs)-1); /* <-- Equivalent to "if (setBitOffsetPlus1 < DRFLAC_CACHE_L1_SIZE_BITS(bs)) { bs->cache <<= setBitOffsetPlus1; }" */ + + /* 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. */ + bitCountLo = bs->consumedBits - DRFLAC_CACHE_L1_SIZE_BITS(bs); + resultHi = DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, riceParam); /* <-- Use DRFLAC_CACHE_L1_SELECT_AND_SHIFT_SAFE() if ever this function allows riceParam=0. */ if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { - #ifndef DR_FLAC_NO_CRC +#ifndef DR_FLAC_NO_CRC drflac__update_crc16(bs); - #endif +#endif bs->cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); bs->consumedBits = 0; - #ifndef DR_FLAC_NO_CRC +#ifndef DR_FLAC_NO_CRC bs->crc16Cache = bs->cache; - #endif +#endif } else { - // Slow path. We need to fetch more data from the client. + /* Slow path. We need to fetch more data from the client. */ if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; } } - riceParamPart = (drflac_uint32)((resultHi >> resultHiShift) | DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCountLo)); + riceParamPart = (drflac_uint32)(resultHi | DRFLAC_CACHE_L1_SELECT_AND_SHIFT_SAFE(bs, bitCountLo)); bs->consumedBits += bitCountLo; bs->cache <<= bitCountLo; } - *pZeroCounterOut = zeroCounter; - *pRiceParamPartOut = riceParamPart; + pZeroCounterOut[0] = zeroCounter; + pRiceParamPartOut[0] = riceParamPart; + + return DRFLAC_TRUE; +} +#endif + +static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts_x1(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; + + /* 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[0] = 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[0] = (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; + drflac_assert(riceParamPartLoBitCount > 0 && riceParamPartLoBitCount < 32); + + /* 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[0] = 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[0] = 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__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; + 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; + + /* 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) { + /* + 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. */ + bs_cache <<= riceParamPlus1; + bs_consumedBits += riceParamPlus1; + } else { + /* + 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. + */ + + /* 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); + + /* 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; + } + + 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. + */ + 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); + if (lzcount < sizeof(bs_cache)*8) { + break; + } + } + + 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_bool32 drflac__decode_samples_with_residual__rice__simple(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(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); - - static drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; - + drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; drflac_uint32 zeroCountPart0; drflac_uint32 zeroCountPart1; drflac_uint32 zeroCountPart2; @@ -2301,57 +3438,100 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__simple(drflac_b drflac_uint32 riceParamPart1; drflac_uint32 riceParamPart2; drflac_uint32 riceParamPart3; - drflac_uint32 i4 = 0; - drflac_uint32 count4 = count >> 2; - while (i4 < count4) { - // Rice extraction. - if (!drflac__read_rice_parts(bs, riceParam, &zeroCountPart0, &riceParamPart0) || - !drflac__read_rice_parts(bs, riceParam, &zeroCountPart1, &riceParamPart1) || - !drflac__read_rice_parts(bs, riceParam, &zeroCountPart2, &riceParamPart2) || - !drflac__read_rice_parts(bs, riceParam, &zeroCountPart3, &riceParamPart3)) { - return DRFLAC_FALSE; - } + drflac_uint32 riceParamMask; + const drflac_int32* pSamplesOutEnd; + drflac_uint32 i; - riceParamPart0 |= (zeroCountPart0 << riceParam); - riceParamPart1 |= (zeroCountPart1 << riceParam); - riceParamPart2 |= (zeroCountPart2 << riceParam); - riceParamPart3 |= (zeroCountPart3 << riceParam); + drflac_assert(bs != NULL); + drflac_assert(count > 0); + drflac_assert(pSamplesOut != NULL); - riceParamPart0 = (riceParamPart0 >> 1) ^ t[riceParamPart0 & 0x01]; - riceParamPart1 = (riceParamPart1 >> 1) ^ t[riceParamPart1 & 0x01]; - riceParamPart2 = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01]; - riceParamPart3 = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01]; + riceParamMask = ~((~0UL) << riceParam); + pSamplesOutEnd = pSamplesOut + ((count >> 2) << 2); + + if (bitsPerSample >= 24) { + 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 + against an array. Not sure why, but perhaps it's making more efficient use of registers? + */ + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart0, &riceParamPart0) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart1, &riceParamPart1) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart2, &riceParamPart2) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart3, &riceParamPart3)) { + return DRFLAC_FALSE; + } + + riceParamPart0 &= riceParamMask; + riceParamPart1 &= riceParamMask; + riceParamPart2 &= riceParamMask; + riceParamPart3 &= riceParamMask; + + riceParamPart0 |= (zeroCountPart0 << riceParam); + riceParamPart1 |= (zeroCountPart1 << riceParam); + riceParamPart2 |= (zeroCountPart2 << riceParam); + riceParamPart3 |= (zeroCountPart3 << riceParam); + + riceParamPart0 = (riceParamPart0 >> 1) ^ t[riceParamPart0 & 0x01]; + riceParamPart1 = (riceParamPart1 >> 1) ^ t[riceParamPart1 & 0x01]; + riceParamPart2 = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01]; + riceParamPart3 = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01]; - if (bitsPerSample > 16) { pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 0); pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 1); pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 2); pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 3); - } else { + + pSamplesOut += 4; + } + } else { + while (pSamplesOut < pSamplesOutEnd) { + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart0, &riceParamPart0) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart1, &riceParamPart1) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart2, &riceParamPart2) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart3, &riceParamPart3)) { + return DRFLAC_FALSE; + } + + riceParamPart0 &= riceParamMask; + riceParamPart1 &= riceParamMask; + riceParamPart2 &= riceParamMask; + riceParamPart3 &= riceParamMask; + + riceParamPart0 |= (zeroCountPart0 << riceParam); + riceParamPart1 |= (zeroCountPart1 << riceParam); + riceParamPart2 |= (zeroCountPart2 << riceParam); + riceParamPart3 |= (zeroCountPart3 << riceParam); + + riceParamPart0 = (riceParamPart0 >> 1) ^ t[riceParamPart0 & 0x01]; + riceParamPart1 = (riceParamPart1 >> 1) ^ t[riceParamPart1 & 0x01]; + riceParamPart2 = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01]; + riceParamPart3 = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01]; + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 0); pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 1); pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 2); pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 3); - } - i4 += 1; - pSamplesOut += 4; + pSamplesOut += 4; + } } - drflac_uint32 i = i4 << 2; + i = ((count >> 2) << 2); while (i < count) { - // Rice extraction. - if (!drflac__read_rice_parts(bs, riceParam, &zeroCountPart0, &riceParamPart0)) { + /* Rice extraction. */ + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart0, &riceParamPart0)) { return DRFLAC_FALSE; } - // Rice reconstruction. + /* Rice reconstruction. */ + riceParamPart0 &= riceParamMask; riceParamPart0 |= (zeroCountPart0 << riceParam); riceParamPart0 = (riceParamPart0 >> 1) ^ t[riceParamPart0 & 0x01]; - //riceParamPart0 = (riceParamPart0 >> 1) ^ (~(riceParamPart0 & 0x01) + 1); + /*riceParamPart0 = (riceParamPart0 >> 1) ^ (~(riceParamPart0 & 0x01) + 1);*/ - // Sample reconstruction. - if (bitsPerSample > 16) { + /* Sample reconstruction. */ + if (bitsPerSample >= 24) { 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); @@ -2360,29 +3540,188 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__simple(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) +{ + static drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; + + /*drflac_uint32 zeroCountParts[4];*/ + /*drflac_uint32 riceParamParts[4];*/ + + drflac_uint32 zeroCountParts0; + drflac_uint32 zeroCountParts1; + drflac_uint32 zeroCountParts2; + drflac_uint32 zeroCountParts3; + drflac_uint32 riceParamParts0; + drflac_uint32 riceParamParts1; + drflac_uint32 riceParamParts2; + drflac_uint32 riceParamParts3; + drflac_uint32 riceParamMask; + const drflac_int32* pSamplesOutEnd; + __m128i riceParamMask128; + __m128i one; + drflac_uint32 i; + + drflac_assert(bs != NULL); + drflac_assert(count > 0); + drflac_assert(pSamplesOut != NULL); + + riceParamMask = ~((~0UL) << riceParam); + riceParamMask128 = _mm_set1_epi32(riceParamMask); + one = _mm_set1_epi32(0x01); + + pSamplesOutEnd = pSamplesOut + ((count >> 2) << 2); + + if (bitsPerSample >= 24) { + while (pSamplesOut < pSamplesOutEnd) { + __m128i zeroCountPart128; + __m128i riceParamPart128; + drflac_uint32 riceParamParts[4]; + + /* 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. */ +#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; + } + + zeroCountPart128 = _mm_set_epi32(zeroCountParts3, zeroCountParts2, zeroCountParts1, zeroCountParts0); + riceParamPart128 = _mm_set_epi32(riceParamParts3, riceParamParts2, riceParamParts1, riceParamParts0); +#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]); +#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))); + +#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; + } + } + + + i = ((count >> 2) << 2); + while (i < 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. */ + 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); + } + + i += 1; + pSamplesOut += 1; + } + + return DRFLAC_TRUE; +} +#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) { -#if 0 - return drflac__decode_samples_with_residual__rice__reference(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); -#else - return drflac__decode_samples_with_residual__rice__simple(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); +#if defined(DRFLAC_SUPPORT_SSE41) + if (drflac__gIsSSE41Supported) { + return drflac__decode_samples_with_residual__rice__sse41(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + } else #endif + { + /* Scalar fallback. */ + #if 0 + return drflac__decode_samples_with_residual__rice__reference(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + #else + return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + #endif + } } -// Reads and seeks past a string of residual values as Rice codes. The decoder should be sitting on the first bit of the Rice codes. +/* Reads and seeks past a string of residual values as Rice codes. The decoder should be sitting on the first bit of the Rice codes. */ static drflac_bool32 drflac__read_and_seek_residual__rice(drflac_bs* bs, drflac_uint32 count, drflac_uint8 riceParam) { + drflac_uint32 i; + drflac_assert(bs != NULL); drflac_assert(count > 0); - for (drflac_uint32 i = 0; i < count; ++i) { - drflac_uint32 zeroCountPart; - drflac_uint32 riceParamPart; - if (!drflac__read_rice_parts(bs, riceParam, &zeroCountPart, &riceParamPart)) { + for (i = 0; i < count; ++i) { + if (!drflac__seek_rice_parts(bs, riceParam)) { return DRFLAC_FALSE; } } @@ -2392,14 +3731,20 @@ static drflac_bool32 drflac__read_and_seek_residual__rice(drflac_bs* bs, drflac_ static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 unencodedBitsPerSample, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { + drflac_uint32 i; + drflac_assert(bs != NULL); drflac_assert(count > 0); - drflac_assert(unencodedBitsPerSample > 0 && unencodedBitsPerSample <= 32); + drflac_assert(unencodedBitsPerSample <= 31); /* <-- unencodedBitsPerSample is a 5 bit number, so cannot exceed 31. */ drflac_assert(pSamplesOut != NULL); - for (unsigned int i = 0; i < count; ++i) { - if (!drflac__read_int32(bs, unencodedBitsPerSample, pSamplesOut + i)) { - return DRFLAC_FALSE; + for (i = 0; i < count; ++i) { + if (unencodedBitsPerSample > 0) { + if (!drflac__read_int32(bs, unencodedBitsPerSample, pSamplesOut + i)) { + return DRFLAC_FALSE; + } + } else { + pSamplesOut[i] = 0; } if (bitsPerSample > 16) { @@ -2413,60 +3758,66 @@ static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs* } -// Reads and decodes the residual for the sub-frame the decoder is currently sitting on. This function should be called -// when the decoder is sitting at the very start of the RESIDUAL block. The first residuals will be ignored. The -// and parameters are used to determine how many residual values need to be decoded. +/* +Reads and decodes the residual for the sub-frame the decoder is currently sitting on. This function should be called +when the decoder is sitting at the very start of the RESIDUAL block. The first residuals will be ignored. The + and parameters are used to determine how many residual values need to be decoded. +*/ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 blockSize, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) { + drflac_uint8 residualMethod; + drflac_uint8 partitionOrder; + 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(pDecodedSamples != NULL); /* <-- Should we allow NULL, in which case we just seek past the residual rather than do a full decode? */ - drflac_uint8 residualMethod; if (!drflac__read_uint8(bs, 2, &residualMethod)) { return DRFLAC_FALSE; } if (residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE && residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2) { - return DRFLAC_FALSE; // Unknown or unsupported residual coding method. + return DRFLAC_FALSE; /* Unknown or unsupported residual coding method. */ } - // Ignore the first values. + /* Ignore the first values. */ pDecodedSamples += order; - - drflac_uint8 partitionOrder; if (!drflac__read_uint8(bs, 4, &partitionOrder)) { return DRFLAC_FALSE; } - // From the FLAC spec: - // The Rice partition order in a Rice-coded residual section must be less than or equal to 8. + /* + From the FLAC spec: + The Rice partition order in a Rice-coded residual section must be less than or equal to 8. + */ if (partitionOrder > 8) { return DRFLAC_FALSE; } - // Validation check. + /* Validation check. */ if ((blockSize / (1 << partitionOrder)) <= order) { return DRFLAC_FALSE; } - drflac_uint32 samplesInPartition = (blockSize / (1 << partitionOrder)) - order; - drflac_uint32 partitionsRemaining = (1 << partitionOrder); + samplesInPartition = (blockSize / (1 << partitionOrder)) - order; + partitionsRemaining = (1 << partitionOrder); for (;;) { drflac_uint8 riceParam = 0; if (residualMethod == DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE) { if (!drflac__read_uint8(bs, 4, &riceParam)) { return DRFLAC_FALSE; } - if (riceParam == 16) { + if (riceParam == 15) { riceParam = 0xFF; } } else if (residualMethod == DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2) { if (!drflac__read_uint8(bs, 5, &riceParam)) { return DRFLAC_FALSE; } - if (riceParam == 32) { + if (riceParam == 31) { riceParam = 0xFF; } } @@ -2488,7 +3839,6 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_ pDecodedSamples += samplesInPartition; - if (partitionsRemaining == 1) { break; } @@ -2503,30 +3853,48 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_ return DRFLAC_TRUE; } -// Reads and seeks past the residual for the sub-frame the decoder is currently sitting on. This function should be called -// when the decoder is sitting at the very start of the RESIDUAL block. The first residuals will be set to 0. The -// and parameters are used to determine how many residual values need to be decoded. +/* +Reads and seeks past the residual for the sub-frame the decoder is currently sitting on. This function should be called +when the decoder is sitting at the very start of the RESIDUAL block. The first residuals will be set to 0. The + and parameters are used to determine how many residual values need to be decoded. +*/ static drflac_bool32 drflac__read_and_seek_residual(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 order) { + drflac_uint8 residualMethod; + drflac_uint8 partitionOrder; + drflac_uint32 samplesInPartition; + drflac_uint32 partitionsRemaining; + drflac_assert(bs != NULL); drflac_assert(blockSize != 0); - drflac_uint8 residualMethod; if (!drflac__read_uint8(bs, 2, &residualMethod)) { return DRFLAC_FALSE; } if (residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE && residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2) { - return DRFLAC_FALSE; // Unknown or unsupported residual coding method. + return DRFLAC_FALSE; /* Unknown or unsupported residual coding method. */ } - drflac_uint8 partitionOrder; if (!drflac__read_uint8(bs, 4, &partitionOrder)) { return DRFLAC_FALSE; } - drflac_uint32 samplesInPartition = (blockSize / (1 << partitionOrder)) - order; - drflac_uint32 partitionsRemaining = (1 << partitionOrder); + /* + From the FLAC spec: + The Rice partition order in a Rice-coded residual section must be less than or equal to 8. + */ + if (partitionOrder > 8) { + return DRFLAC_FALSE; + } + + /* Validation check. */ + if ((blockSize / (1 << partitionOrder)) <= order) { + return DRFLAC_FALSE; + } + + samplesInPartition = (blockSize / (1 << partitionOrder)) - order; + partitionsRemaining = (1 << partitionOrder); for (;;) { drflac_uint8 riceParam = 0; @@ -2534,14 +3902,14 @@ static drflac_bool32 drflac__read_and_seek_residual(drflac_bs* bs, drflac_uint32 if (!drflac__read_uint8(bs, 4, &riceParam)) { return DRFLAC_FALSE; } - if (riceParam == 16) { + if (riceParam == 15) { riceParam = 0xFF; } } else if (residualMethod == DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2) { if (!drflac__read_uint8(bs, 5, &riceParam)) { return DRFLAC_FALSE; } - if (riceParam == 32) { + if (riceParam == 31) { riceParam = 0xFF; } } @@ -2576,15 +3944,19 @@ 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) { - // Only a single sample needs to be decoded here. + drflac_uint32 i; + + /* Only a single sample needs to be decoded here. */ drflac_int32 sample; if (!drflac__read_int32(bs, bitsPerSample, &sample)) { return DRFLAC_FALSE; } - // We don't really need to expand this, but it does simplify the process of reading samples. If this becomes a performance issue (unlikely) - // we'll want to look at a more efficient way. - for (drflac_uint32 i = 0; i < blockSize; ++i) { + /* + We don't really need to expand this, but it does simplify the process of reading samples. If this becomes a performance issue (unlikely) + we'll want to look at a more efficient way. + */ + for (i = 0; i < blockSize; ++i) { pDecodedSamples[i] = sample; } @@ -2593,7 +3965,9 @@ static drflac_bool32 drflac__decode_samples__constant(drflac_bs* bs, drflac_uint static drflac_bool32 drflac__decode_samples__verbatim(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 bitsPerSample, drflac_int32* pDecodedSamples) { - for (drflac_uint32 i = 0; i < blockSize; ++i) { + drflac_uint32 i; + + for (i = 0; i < blockSize; ++i) { drflac_int32 sample; if (!drflac__read_int32(bs, bitsPerSample, &sample)) { return DRFLAC_FALSE; @@ -2607,7 +3981,9 @@ static drflac_bool32 drflac__decode_samples__verbatim(drflac_bs* bs, drflac_uint static drflac_bool32 drflac__decode_samples__fixed(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 bitsPerSample, drflac_uint8 lpcOrder, drflac_int32* pDecodedSamples) { - drflac_int32 lpcCoefficientsTable[5][4] = { + drflac_uint32 i; + + static drflac_int32 lpcCoefficientsTable[5][4] = { {0, 0, 0, 0}, {1, 0, 0, 0}, {2, -1, 0, 0}, @@ -2615,8 +3991,8 @@ static drflac_bool32 drflac__decode_samples__fixed(drflac_bs* bs, drflac_uint32 {4, -6, 4, -1} }; - // Warm up samples and coefficients. - for (drflac_uint32 i = 0; i < lpcOrder; ++i) { + /* Warm up samples and coefficients. */ + for (i = 0; i < lpcOrder; ++i) { drflac_int32 sample; if (!drflac__read_int32(bs, bitsPerSample, &sample)) { return DRFLAC_FALSE; @@ -2625,7 +4001,6 @@ static drflac_bool32 drflac__decode_samples__fixed(drflac_bs* bs, drflac_uint32 pDecodedSamples[i] = sample; } - if (!drflac__decode_samples_with_residual(bs, bitsPerSample, blockSize, lpcOrder, 0, lpcCoefficientsTable[lpcOrder], pDecodedSamples)) { return DRFLAC_FALSE; } @@ -2636,8 +4011,11 @@ static drflac_bool32 drflac__decode_samples__fixed(drflac_bs* bs, drflac_uint32 static drflac_bool32 drflac__decode_samples__lpc(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 bitsPerSample, drflac_uint8 lpcOrder, drflac_int32* pDecodedSamples) { drflac_uint8 i; + drflac_uint8 lpcPrecision; + drflac_int8 lpcShift; + drflac_int32 coefficients[32]; - // Warm up samples. + /* Warm up samples. */ for (i = 0; i < lpcOrder; ++i) { drflac_int32 sample; if (!drflac__read_int32(bs, bitsPerSample, &sample)) { @@ -2647,23 +4025,19 @@ static drflac_bool32 drflac__decode_samples__lpc(drflac_bs* bs, drflac_uint32 bl pDecodedSamples[i] = sample; } - drflac_uint8 lpcPrecision; if (!drflac__read_uint8(bs, 4, &lpcPrecision)) { return DRFLAC_FALSE; } if (lpcPrecision == 15) { - return DRFLAC_FALSE; // Invalid. + return DRFLAC_FALSE; /* Invalid. */ } lpcPrecision += 1; - - drflac_int8 lpcShift; if (!drflac__read_int8(bs, 5, &lpcShift)) { return DRFLAC_FALSE; } - - drflac_int32 coefficients[32]; + drflac_zero_memory(coefficients, sizeof(coefficients)); for (i = 0; i < lpcOrder; ++i) { if (!drflac__read_int32(bs, lpcPrecision, coefficients + i)) { return DRFLAC_FALSE; @@ -2678,71 +4052,82 @@ static drflac_bool32 drflac__decode_samples__lpc(drflac_bs* bs, drflac_uint32 bl } -static drflac_bool32 drflac__read_next_frame_header(drflac_bs* bs, drflac_uint8 streaminfoBitsPerSample, drflac_frame_header* header) +static drflac_bool32 drflac__read_next_flac_frame_header(drflac_bs* bs, drflac_uint8 streaminfoBitsPerSample, drflac_frame_header* header) { + 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); - 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. - - // Keep looping until we find a valid sync code. + /* Keep looping until we find a valid sync code. */ for (;;) { + drflac_uint8 crc8 = 0xCE; /* 0xCE = drflac_crc8(0, 0x3FFE, 14); */ + drflac_uint8 reserved = 0; + drflac_uint8 blockingStrategy = 0; + drflac_uint8 blockSize = 0; + drflac_uint8 sampleRate = 0; + drflac_uint8 channelAssignment = 0; + drflac_uint8 bitsPerSample = 0; + drflac_bool32 isVariableBlockSize; + if (!drflac__find_and_seek_to_next_sync_code(bs)) { return DRFLAC_FALSE; } - drflac_uint8 crc8 = 0xCE; // 0xCE = drflac_crc8(0, 0x3FFE, 14); - - drflac_uint8 reserved = 0; if (!drflac__read_uint8(bs, 1, &reserved)) { return DRFLAC_FALSE; } + if (reserved == 1) { + continue; + } crc8 = drflac_crc8(crc8, reserved, 1); - - drflac_uint8 blockingStrategy = 0; if (!drflac__read_uint8(bs, 1, &blockingStrategy)) { return DRFLAC_FALSE; } crc8 = drflac_crc8(crc8, blockingStrategy, 1); - - drflac_uint8 blockSize = 0; if (!drflac__read_uint8(bs, 4, &blockSize)) { return DRFLAC_FALSE; } + if (blockSize == 0) { + continue; + } crc8 = drflac_crc8(crc8, blockSize, 4); - - drflac_uint8 sampleRate = 0; if (!drflac__read_uint8(bs, 4, &sampleRate)) { return DRFLAC_FALSE; } crc8 = drflac_crc8(crc8, sampleRate, 4); - - drflac_uint8 channelAssignment = 0; if (!drflac__read_uint8(bs, 4, &channelAssignment)) { return DRFLAC_FALSE; } + if (channelAssignment > 10) { + continue; + } crc8 = drflac_crc8(crc8, channelAssignment, 4); - - drflac_uint8 bitsPerSample = 0; if (!drflac__read_uint8(bs, 3, &bitsPerSample)) { return DRFLAC_FALSE; } + if (bitsPerSample == 3 || bitsPerSample == 7) { + continue; + } crc8 = drflac_crc8(crc8, bitsPerSample, 3); if (!drflac__read_uint8(bs, 1, &reserved)) { return DRFLAC_FALSE; } + if (reserved == 1) { + continue; + } crc8 = drflac_crc8(crc8, reserved, 1); - drflac_bool32 isVariableBlockSize = blockingStrategy == 1; + isVariableBlockSize = blockingStrategy == 1; if (isVariableBlockSize) { drflac_uint64 sampleNumber; drflac_result result = drflac__read_utf8_coded_number(bs, &sampleNumber, &crc8); @@ -2765,7 +4150,7 @@ static drflac_bool32 drflac__read_next_frame_header(drflac_bs* bs, drflac_uint8 continue; } } - header->frameNumber = (drflac_uint32)frameNumber; // <-- Safe cast. + header->frameNumber = (drflac_uint32)frameNumber; /* <-- Safe cast. */ header->sampleNumber = 0; } @@ -2811,7 +4196,7 @@ static drflac_bool32 drflac__read_next_frame_header(drflac_bs* bs, drflac_uint8 crc8 = drflac_crc8(crc8, header->sampleRate, 16); header->sampleRate *= 10; } else { - continue; // Invalid. Assume an invalid block. + continue; /* Invalid. Assume an invalid block. */ } @@ -2826,11 +4211,11 @@ static drflac_bool32 drflac__read_next_frame_header(drflac_bs* bs, drflac_uint8 return DRFLAC_FALSE; } - #ifndef DR_FLAC_NO_CRC +#ifndef DR_FLAC_NO_CRC if (header->crc8 != crc8) { - continue; // CRC mismatch. Loop back to the top and find the next sync code. + continue; /* CRC mismatch. Loop back to the top and find the next sync code. */ } - #endif +#endif return DRFLAC_TRUE; } } @@ -2838,16 +4223,18 @@ static drflac_bool32 drflac__read_next_frame_header(drflac_bs* bs, drflac_uint8 static drflac_bool32 drflac__read_subframe_header(drflac_bs* bs, drflac_subframe* pSubframe) { drflac_uint8 header; + int type; + if (!drflac__read_uint8(bs, 8, &header)) { return DRFLAC_FALSE; } - // First bit should always be 0. + /* First bit should always be 0. */ if ((header & 0x80) != 0) { return DRFLAC_FALSE; } - int type = (header & 0x7E) >> 1; + type = (header & 0x7E) >> 1; if (type == 0) { pSubframe->subframeType = DRFLAC_SUBFRAME_CONSTANT; } else if (type == 1) { @@ -2872,7 +4259,7 @@ static drflac_bool32 drflac__read_subframe_header(drflac_bs* bs, drflac_subframe return DRFLAC_FALSE; } - // Wasted bits per sample. + /* Wasted bits per sample. */ pSubframe->wastedBitsPerSample = 0; if ((header & 0x01) == 1) { unsigned int wastedBitsPerSample; @@ -2887,15 +4274,17 @@ 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_assert(bs != NULL); drflac_assert(frame != NULL); - drflac_subframe* pSubframe = frame->subframes + subframeIndex; + pSubframe = frame->subframes + subframeIndex; if (!drflac__read_subframe_header(bs, pSubframe)) { return DRFLAC_FALSE; } - // Side channels require an extra bit per sample. Took a while to figure that one out... + /* Side channels require an extra bit per sample. Took a while to figure that one out... */ pSubframe->bitsPerSample = 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; @@ -2903,7 +4292,10 @@ static drflac_bool32 drflac__decode_subframe(drflac_bs* bs, drflac_frame* frame, pSubframe->bitsPerSample += 1; } - // Need to handle wasted bits per sample. + /* Need to handle wasted bits per sample. */ + if (pSubframe->wastedBitsPerSample >= pSubframe->bitsPerSample) { + return DRFLAC_FALSE; + } pSubframe->bitsPerSample -= pSubframe->wastedBitsPerSample; pSubframe->pDecodedSamples = pDecodedSamplesOut; @@ -2937,15 +4329,17 @@ 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_assert(bs != NULL); drflac_assert(frame != NULL); - drflac_subframe* pSubframe = frame->subframes + subframeIndex; + pSubframe = frame->subframes + subframeIndex; if (!drflac__read_subframe_header(bs, pSubframe)) { return DRFLAC_FALSE; } - // Side channels require an extra bit per sample. Took a while to figure that one out... + /* Side channels require an extra bit per sample. Took a while to figure that one out... */ pSubframe->bitsPerSample = 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; @@ -2953,7 +4347,10 @@ static drflac_bool32 drflac__seek_subframe(drflac_bs* bs, drflac_frame* frame, i pSubframe->bitsPerSample += 1; } - // Need to handle wasted bits per sample. + /* Need to handle wasted bits per sample. */ + if (pSubframe->wastedBitsPerSample >= pSubframe->bitsPerSample) { + return DRFLAC_FALSE; + } pSubframe->bitsPerSample -= pSubframe->wastedBitsPerSample; pSubframe->pDecodedSamples = NULL; @@ -2988,22 +4385,23 @@ static drflac_bool32 drflac__seek_subframe(drflac_bs* bs, drflac_frame* frame, i case DRFLAC_SUBFRAME_LPC: { + unsigned char lpcPrecision; + unsigned int bitsToSeek = pSubframe->lpcOrder * pSubframe->bitsPerSample; if (!drflac__seek_bits(bs, bitsToSeek)) { return DRFLAC_FALSE; } - unsigned char lpcPrecision; if (!drflac__read_uint8(bs, 4, &lpcPrecision)) { return DRFLAC_FALSE; } if (lpcPrecision == 15) { - return DRFLAC_FALSE; // Invalid. + return DRFLAC_FALSE; /* Invalid. */ } lpcPrecision += 1; - bitsToSeek = (pSubframe->lpcOrder * lpcPrecision) + 5; // +5 for shift. + bitsToSeek = (pSubframe->lpcOrder * lpcPrecision) + 5; /* +5 for shift. */ if (!drflac__seek_bits(bs, bitsToSeek)) { return DRFLAC_FALSE; } @@ -3022,35 +4420,43 @@ static drflac_bool32 drflac__seek_subframe(drflac_bs* bs, drflac_frame* frame, i static DRFLAC_INLINE drflac_uint8 drflac__get_channel_count_from_channel_assignment(drflac_int8 channelAssignment) { - drflac_assert(channelAssignment <= 10); - drflac_uint8 lookup[] = {1, 2, 3, 4, 5, 6, 7, 8, 2, 2, 2}; + + drflac_assert(channelAssignment <= 10); return lookup[channelAssignment]; } -static drflac_result drflac__decode_frame(drflac* pFlac) +static drflac_result drflac__decode_flac_frame(drflac* pFlac) { - // This function should be called while the stream is sitting on the first byte after the frame header. + int channelCount; + int i; + drflac_uint8 paddingSizeInBits; + drflac_uint16 desiredCRC16; +#ifndef DR_FLAC_NO_CRC + drflac_uint16 actualCRC16; +#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)); - // The frame block size must never be larger than the maximum block size defined by the FLAC stream. + /* 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) { return DRFLAC_ERROR; } - // The number of channels in the frame must match the channel count from the STREAMINFO block. - int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); + /* 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); if (channelCount != (int)pFlac->channels) { return DRFLAC_ERROR; } - for (int i = 0; i < channelCount; ++i) { - if (!drflac__decode_subframe(&pFlac->bs, &pFlac->currentFrame, i, pFlac->pDecodedSamples + (pFlac->currentFrame.header.blockSize * i))) { + 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)) { return DRFLAC_ERROR; } } - drflac_uint8 paddingSizeInBits = DRFLAC_CACHE_L1_BITS_REMAINING(&pFlac->bs) & 7; + paddingSizeInBits = DRFLAC_CACHE_L1_BITS_REMAINING(&pFlac->bs) & 7; if (paddingSizeInBits > 0) { drflac_uint8 padding = 0; if (!drflac__read_uint8(&pFlac->bs, paddingSizeInBits, &padding)) { @@ -3059,16 +4465,15 @@ static drflac_result drflac__decode_frame(drflac* pFlac) } #ifndef DR_FLAC_NO_CRC - drflac_uint16 actualCRC16 = drflac__flush_crc16(&pFlac->bs); + actualCRC16 = drflac__flush_crc16(&pFlac->bs); #endif - drflac_uint16 desiredCRC16; if (!drflac__read_uint16(&pFlac->bs, 16, &desiredCRC16)) { return DRFLAC_END_OF_STREAM; } #ifndef DR_FLAC_NO_CRC if (actualCRC16 != desiredCRC16) { - return DRFLAC_CRC_MISMATCH; // CRC mismatch. + return DRFLAC_CRC_MISMATCH; /* CRC mismatch. */ } #endif @@ -3077,51 +4482,59 @@ static drflac_result drflac__decode_frame(drflac* pFlac) return DRFLAC_SUCCESS; } -static drflac_result drflac__seek_frame(drflac* pFlac) +static drflac_result drflac__seek_flac_frame(drflac* pFlac) { - int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); - for (int i = 0; i < channelCount; ++i) { + int channelCount; + int i; + drflac_uint16 desiredCRC16; +#ifndef DR_FLAC_NO_CRC + drflac_uint16 actualCRC16; +#endif + + channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); + for (i = 0; i < channelCount; ++i) { if (!drflac__seek_subframe(&pFlac->bs, &pFlac->currentFrame, i)) { return DRFLAC_ERROR; } } - // Padding. + /* Padding. */ if (!drflac__seek_bits(&pFlac->bs, DRFLAC_CACHE_L1_BITS_REMAINING(&pFlac->bs) & 7)) { return DRFLAC_ERROR; } - // CRC. + /* CRC. */ #ifndef DR_FLAC_NO_CRC - drflac_uint16 actualCRC16 = drflac__flush_crc16(&pFlac->bs); + actualCRC16 = drflac__flush_crc16(&pFlac->bs); #endif - drflac_uint16 desiredCRC16; if (!drflac__read_uint16(&pFlac->bs, 16, &desiredCRC16)) { return DRFLAC_END_OF_STREAM; } #ifndef DR_FLAC_NO_CRC if (actualCRC16 != desiredCRC16) { - return DRFLAC_CRC_MISMATCH; // CRC mismatch. + return DRFLAC_CRC_MISMATCH; /* CRC mismatch. */ } #endif return DRFLAC_SUCCESS; } -static drflac_bool32 drflac__read_and_decode_next_frame(drflac* pFlac) +static drflac_bool32 drflac__read_and_decode_next_flac_frame(drflac* pFlac) { drflac_assert(pFlac != NULL); for (;;) { - if (!drflac__read_next_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + drflac_result result; + + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { return DRFLAC_FALSE; } - drflac_result result = drflac__decode_frame(pFlac); + result = drflac__decode_flac_frame(pFlac); if (result != DRFLAC_SUCCESS) { if (result == DRFLAC_CRC_MISMATCH) { - continue; // CRC mismatch. Skip to the next frame. + continue; /* CRC mismatch. Skip to the next frame. */ } else { return DRFLAC_FALSE; } @@ -3134,29 +4547,67 @@ static drflac_bool32 drflac__read_and_decode_next_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); - unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); + channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); - drflac_uint64 firstSampleInFrame = pFlac->currentFrame.header.sampleNumber; + firstSampleInFrame = pFlac->currentFrame.header.sampleNumber*channelCount; if (firstSampleInFrame == 0) { firstSampleInFrame = pFlac->currentFrame.header.frameNumber * pFlac->maxBlockSize*channelCount; } - drflac_uint64 lastSampleInFrame = firstSampleInFrame + (pFlac->currentFrame.header.blockSize*channelCount); + lastSampleInFrame = firstSampleInFrame + (pFlac->currentFrame.header.blockSize*channelCount); if (lastSampleInFrame > 0) { - lastSampleInFrame -= 1; // Needs to be zero based. + lastSampleInFrame -= 1; /* Needs to be zero based. */ } - if (pFirstSampleInFrameOut) *pFirstSampleInFrameOut = firstSampleInFrame; - if (pLastSampleInFrameOut) *pLastSampleInFrameOut = lastSampleInFrame; + 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); + + firstPCMFrame = pFlac->currentFrame.header.sampleNumber; + if (firstPCMFrame == 0) { + firstPCMFrame = pFlac->currentFrame.header.frameNumber * pFlac->maxBlockSize; + } + + lastPCMFrame = firstPCMFrame + (pFlac->currentFrame.header.blockSize); + if (lastPCMFrame > 0) { + lastPCMFrame -= 1; /* Needs to be zero based. */ + } + + if (pFirstPCMFrame) { + *pFirstPCMFrame = firstPCMFrame; + } + if (pLastPCMFrame) { + *pLastPCMFrame = lastPCMFrame; + } +} +#endif + static drflac_bool32 drflac__seek_to_first_frame(drflac* pFlac) { + drflac_bool32 result; + drflac_assert(pFlac != NULL); - drflac_bool32 result = drflac__seek_to_byte(&pFlac->bs, pFlac->firstFramePos); + result = drflac__seek_to_byte(&pFlac->bs, pFlac->firstFramePos); drflac_zero_memory(&pFlac->currentFrame, sizeof(pFlac->currentFrame)); pFlac->currentSample = 0; @@ -3164,94 +4615,134 @@ static drflac_bool32 drflac__seek_to_first_frame(drflac* pFlac) return result; } -static DRFLAC_INLINE drflac_result drflac__seek_to_next_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. + /* 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); - return drflac__seek_frame(pFlac); + return drflac__seek_flac_frame(pFlac); +} + +drflac_uint64 drflac__seek_forward_by_samples(drflac* pFlac, drflac_uint64 samplesToRead) +{ + drflac_uint64 samplesRead = 0; + while (samplesToRead > 0) { + if (pFlac->currentFrame.samplesRemaining == 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; + } else { + samplesRead += pFlac->currentFrame.samplesRemaining; + samplesToRead -= pFlac->currentFrame.samplesRemaining; + pFlac->currentFrame.samplesRemaining = 0; + } + } + } + + pFlac->currentSample += samplesRead; + return samplesRead; +} + +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) { + drflac_bool32 isMidFrame = DRFLAC_FALSE; + drflac_uint64 runningSampleCount; + drflac_assert(pFlac != NULL); - drflac_bool32 isMidFrame = DRFLAC_FALSE; - - // 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. - drflac_uint64 runningSampleCount; + /* 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) { - // Seeking forward. Need to seek from the current position. + /* Seeking forward. Need to seek from the current position. */ runningSampleCount = pFlac->currentSample; - // The frame header for the first frame may not yet have been read. We need to do that if necessary. + /* 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_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { return DRFLAC_FALSE; } } else { isMidFrame = DRFLAC_TRUE; } } else { - // Seeking backwards. Need to seek from the start of the file. + /* Seeking backwards. Need to seek from the start of the file. */ runningSampleCount = 0; - // Move back to the start. + /* Move back to the start. */ if (!drflac__seek_to_first_frame(pFlac)) { return DRFLAC_FALSE; } - // Decode the first frame in preparation for sample-exact seeking below. - if (!drflac__read_next_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + /* 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)) { return DRFLAC_FALSE; } } - // We need to as quickly as possible find the frame that contains the target sample. To do this, we iterate over each frame and inspect its - // header. If based on the header we can determine that the frame contains the sample, we do a full decode of that frame. + /* + We need to as quickly as possible find the frame that contains the target sample. To do this, we iterate over each frame and inspect its + 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__get_current_frame_sample_range(pFlac, &firstSampleInFrame, &lastSampleInFrame); - drflac_uint64 sampleCountInThisFrame = (lastSampleInFrame - firstSampleInFrame) + 1; + sampleCountInThisFrame = (lastSampleInFrame - firstSampleInFrame) + 1; if (sampleIndex < (runningSampleCount + sampleCountInThisFrame)) { - // 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. + /* + 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; if (!isMidFrame) { - drflac_result result = drflac__decode_frame(pFlac); + 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_read_s32(pFlac, samplesToDecode, NULL) == samplesToDecode; // <-- If this fails, something bad has happened (it should never fail). + /* 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). */ } else { if (result == DRFLAC_CRC_MISMATCH) { - goto next_iteration; // CRC mismatch. Pretend this frame never existed. + goto next_iteration; /* CRC mismatch. Pretend this frame never existed. */ } else { return DRFLAC_FALSE; } } } else { - // We started seeking mid-frame which means we need to skip the frame decoding part. - return drflac_read_s32(pFlac, samplesToDecode, NULL) == samplesToDecode; + /* We started seeking mid-frame which means we need to skip the frame decoding part. */ + return drflac__seek_forward_by_samples(pFlac, samplesToDecode) == samplesToDecode; } } else { - // It's not in this frame. We need to seek past the frame, but check if there was a CRC mismatch. If so, we pretend this - // frame never existed and leave the running sample count untouched. + /* + It's not in this frame. We need to seek past the frame, but check if there was a CRC mismatch. If so, we pretend this + frame never existed and leave the running sample count untouched. + */ if (!isMidFrame) { - drflac_result result = drflac__seek_to_next_frame(pFlac); + drflac_result result = drflac__seek_to_next_flac_frame(pFlac); if (result == DRFLAC_SUCCESS) { runningSampleCount += sampleCountInThisFrame; } else { if (result == DRFLAC_CRC_MISMATCH) { - goto next_iteration; // CRC mismatch. Pretend this frame never existed. + goto next_iteration; /* CRC mismatch. Pretend this frame never existed. */ } else { return DRFLAC_FALSE; } } } else { - // 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_frame() which only works if the decoder is sitting on the byte just after the frame header. + /* + 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; isMidFrame = DRFLAC_FALSE; @@ -3259,8 +4750,8 @@ static drflac_bool32 drflac__seek_to_sample__brute_force(drflac* pFlac, drflac_u } next_iteration: - // Grab the next frame in preparation for the next iteration. - if (!drflac__read_next_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + /* Grab the next frame in preparation for the next iteration. */ + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { return DRFLAC_FALSE; } } @@ -3269,15 +4760,18 @@ static drflac_bool32 drflac__seek_to_sample__brute_force(drflac* pFlac, drflac_u static drflac_bool32 drflac__seek_to_sample__seek_table(drflac* pFlac, drflac_uint64 sampleIndex) { + drflac_uint32 iClosestSeekpoint = 0; + drflac_bool32 isMidFrame = DRFLAC_FALSE; + drflac_uint64 runningSampleCount; + drflac_uint32 iSeekpoint; + drflac_assert(pFlac != NULL); if (pFlac->pSeekpoints == NULL || pFlac->seekpointCount == 0) { return DRFLAC_FALSE; } - - drflac_uint32 iClosestSeekpoint = 0; - for (drflac_uint32 iSeekpoint = 0; iSeekpoint < pFlac->seekpointCount; ++iSeekpoint) { + for (iSeekpoint = 0; iSeekpoint < pFlac->seekpointCount; ++iSeekpoint) { if (pFlac->pSeekpoints[iSeekpoint].firstSample*pFlac->channels >= sampleIndex) { break; } @@ -3285,82 +4779,87 @@ static drflac_bool32 drflac__seek_to_sample__seek_table(drflac* pFlac, drflac_ui iClosestSeekpoint = iSeekpoint; } - - drflac_bool32 isMidFrame = DRFLAC_FALSE; - - // 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. - drflac_uint64 runningSampleCount; + /* + 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 ((sampleIndex >= pFlac->currentSample) && (pFlac->pSeekpoints[iClosestSeekpoint].firstSample*pFlac->channels <= pFlac->currentSample)) { - // Optimized case. Just seek forward from where we are. + /* Optimized case. Just seek forward from where we are. */ runningSampleCount = pFlac->currentSample; - // The frame header for the first frame may not yet have been read. We need to do that if necessary. + /* 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_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { return DRFLAC_FALSE; } } else { isMidFrame = DRFLAC_TRUE; } } else { - // Slower case. Seek to the start of the seekpoint and then seek forward from there. + /* Slower case. Seek to the start of the seekpoint and then seek forward from there. */ runningSampleCount = pFlac->pSeekpoints[iClosestSeekpoint].firstSample*pFlac->channels; if (!drflac__seek_to_byte(&pFlac->bs, pFlac->firstFramePos + pFlac->pSeekpoints[iClosestSeekpoint].frameOffset)) { return DRFLAC_FALSE; } - // Grab the frame the seekpoint is sitting on in preparation for the sample-exact seeking below. - if (!drflac__read_next_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + /* 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)) { 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 sampleCountInThisFrame = (lastSampleInFrame - firstSampleInFrame) + 1; + sampleCountInThisFrame = (lastSampleInFrame - firstSampleInFrame) + 1; if (sampleIndex < (runningSampleCount + sampleCountInThisFrame)) { - // 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. + /* + 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; if (!isMidFrame) { - drflac_result result = drflac__decode_frame(pFlac); + 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_read_s32(pFlac, samplesToDecode, NULL) == samplesToDecode; // <-- If this fails, something bad has happened (it should never fail). + /* 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). */ } else { if (result == DRFLAC_CRC_MISMATCH) { - goto next_iteration; // CRC mismatch. Pretend this frame never existed. + goto next_iteration; /* CRC mismatch. Pretend this frame never existed. */ } else { return DRFLAC_FALSE; } } } else { - // We started seeking mid-frame which means we need to skip the frame decoding part. - return drflac_read_s32(pFlac, samplesToDecode, NULL) == samplesToDecode; + /* We started seeking mid-frame which means we need to skip the frame decoding part. */ + return drflac__seek_forward_by_samples(pFlac, samplesToDecode) == samplesToDecode; } } else { - // It's not in this frame. We need to seek past the frame, but check if there was a CRC mismatch. If so, we pretend this - // frame never existed and leave the running sample count untouched. + /* + It's not in this frame. We need to seek past the frame, but check if there was a CRC mismatch. If so, we pretend this + frame never existed and leave the running sample count untouched. + */ if (!isMidFrame) { - drflac_result result = drflac__seek_to_next_frame(pFlac); + drflac_result result = drflac__seek_to_next_flac_frame(pFlac); if (result == DRFLAC_SUCCESS) { runningSampleCount += sampleCountInThisFrame; } else { if (result == DRFLAC_CRC_MISMATCH) { - goto next_iteration; // CRC mismatch. Pretend this frame never existed. + goto next_iteration; /* CRC mismatch. Pretend this frame never existed. */ } else { return DRFLAC_FALSE; } } } else { - // 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_frame() which only works if the decoder is sitting on the byte just after the frame header. + /* + 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; isMidFrame = DRFLAC_FALSE; @@ -3368,8 +4867,8 @@ static drflac_bool32 drflac__seek_to_sample__seek_table(drflac* pFlac, drflac_ui } next_iteration: - // Grab the next frame in preparation for the next iteration. - if (!drflac__read_next_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + /* Grab the next frame in preparation for the next iteration. */ + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { return DRFLAC_FALSE; } } @@ -3379,8 +4878,8 @@ static drflac_bool32 drflac__seek_to_sample__seek_table(drflac* pFlac, drflac_ui #ifndef DR_FLAC_NO_OGG typedef struct { - drflac_uint8 capturePattern[4]; // Should be "OggS" - drflac_uint8 structureVersion; // Always 0. + drflac_uint8 capturePattern[4]; /* Should be "OggS" */ + drflac_uint8 structureVersion; /* Always 0. */ drflac_uint8 headerType; drflac_uint64 granulePosition; drflac_uint32 serialNumber; @@ -3407,8 +4906,8 @@ typedef struct drflac_uint64 runningFilePos; drflac_bool32 hasStreamInfoBlock; drflac_bool32 hasMetadataBlocks; - drflac_bs bs; // <-- A bit streamer is required for loading data during initialization. - drflac_frame_header firstFrameHeader; // <-- The header of the first frame that was read during relaxed initalization. Only set if there is no STREAMINFO block. + drflac_bs bs; /* <-- A bit streamer is required for loading data during initialization. */ + drflac_frame_header firstFrameHeader; /* <-- The header of the first frame that was read during relaxed initalization. Only set if there is no STREAMINFO block. */ #ifndef DR_FLAC_NO_OGG drflac_uint32 oggSerial; @@ -3420,9 +4919,9 @@ 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 & (0x01 << 31)) >> 31; - *blockType = (blockHeader & (0x7F << 24)) >> 24; - *blockSize = (blockHeader & 0xFFFFFF); + *isLastBlock = (blockHeader & 0x80000000UL) >> 31; + *blockType = (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) @@ -3438,26 +4937,27 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_and_decode_block_header(drflac_r drflac_bool32 drflac__read_streaminfo(drflac_read_proc onRead, void* pUserData, drflac_streaminfo* pStreamInfo) { - // min/max block size. drflac_uint32 blockSizes; + drflac_uint64 frameSizes = 0; + drflac_uint64 importantProps; + drflac_uint8 md5[16]; + + /* min/max block size. */ if (onRead(pUserData, &blockSizes, 4) != 4) { return DRFLAC_FALSE; } - // min/max frame size. - drflac_uint64 frameSizes = 0; + /* min/max frame size. */ if (onRead(pUserData, &frameSizes, 6) != 6) { return DRFLAC_FALSE; } - // Sample rate, channels, bits per sample and total sample count. - drflac_uint64 importantProps; + /* Sample rate, channels, bits per sample and total sample count. */ if (onRead(pUserData, &importantProps, 8) != 8) { return DRFLAC_FALSE; } - // MD5 - drflac_uint8 md5[16]; + /* MD5 */ if (onRead(pUserData, md5, sizeof(md5)) != sizeof(md5)) { return DRFLAC_FALSE; } @@ -3467,13 +4967,13 @@ drflac_bool32 drflac__read_streaminfo(drflac_read_proc onRead, void* pUserData, importantProps = drflac__be2host_64(importantProps); pStreamInfo->minBlockSize = (blockSizes & 0xFFFF0000) >> 16; - pStreamInfo->maxBlockSize = blockSizes & 0x0000FFFF; - pStreamInfo->minFrameSize = (drflac_uint32)((frameSizes & (drflac_uint64)0xFFFFFF0000000000) >> 40); - pStreamInfo->maxFrameSize = (drflac_uint32)((frameSizes & (drflac_uint64)0x000000FFFFFF0000) >> 16); - pStreamInfo->sampleRate = (drflac_uint32)((importantProps & (drflac_uint64)0xFFFFF00000000000) >> 44); - pStreamInfo->channels = (drflac_uint8 )((importantProps & (drflac_uint64)0x00000E0000000000) >> 41) + 1; - pStreamInfo->bitsPerSample = (drflac_uint8 )((importantProps & (drflac_uint64)0x000001F000000000) >> 36) + 1; - pStreamInfo->totalSampleCount = (importantProps & (drflac_uint64)0x0000000FFFFFFFFF) * pStreamInfo->channels; + 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)); return DRFLAC_TRUE; @@ -3481,13 +4981,16 @@ drflac_bool32 drflac__read_streaminfo(drflac_read_proc onRead, void* pUserData, 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) { - // 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 - // we'll be sitting on byte 42. + /* + 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 + we'll be sitting on byte 42. + */ drflac_uint64 runningFilePos = 42; drflac_uint64 seektablePos = 0; drflac_uint32 seektableSize = 0; for (;;) { + drflac_metadata metadata; drflac_uint8 isLastBlock = 0; drflac_uint8 blockType; drflac_uint32 blockSize; @@ -3496,8 +4999,6 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s } runningFilePos += 4; - - drflac_metadata metadata; metadata.type = blockType; metadata.pRawData = NULL; metadata.rawDataSize = 0; @@ -3506,6 +5007,10 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s { case DRFLAC_METADATA_BLOCK_TYPE_APPLICATION: { + if (blockSize < 4) { + return DRFLAC_FALSE; + } + if (onMeta) { void* pRawData = DRFLAC_MALLOC(blockSize); if (pRawData == NULL) { @@ -3534,7 +5039,10 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s seektableSize = blockSize; if (onMeta) { - void* pRawData = DRFLAC_MALLOC(blockSize); + drflac_uint32 iSeekpoint; + void* pRawData; + + pRawData = DRFLAC_MALLOC(blockSize); if (pRawData == NULL) { return DRFLAC_FALSE; } @@ -3549,8 +5057,8 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s metadata.data.seektable.seekpointCount = blockSize/sizeof(drflac_seekpoint); metadata.data.seektable.pSeekpoints = (const drflac_seekpoint*)pRawData; - // Endian swap. - for (drflac_uint32 iSeekpoint = 0; iSeekpoint < metadata.data.seektable.seekpointCount; ++iSeekpoint) { + /* 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); @@ -3565,8 +5073,17 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s case DRFLAC_METADATA_BLOCK_TYPE_VORBIS_COMMENT: { + if (blockSize < 8) { + return DRFLAC_FALSE; + } + if (onMeta) { - void* pRawData = DRFLAC_MALLOC(blockSize); + void* pRawData; + const char* pRunningData; + const char* pRunningDataEnd; + drflac_uint32 i; + + pRawData = DRFLAC_MALLOC(blockSize); if (pRawData == NULL) { return DRFLAC_FALSE; } @@ -3579,11 +5096,43 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s metadata.pRawData = pRawData; metadata.rawDataSize = blockSize; - const char* pRunningData = (const char*)pRawData; - metadata.data.vorbis_comment.vendorLength = drflac__le2host_32(*(drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.vorbis_comment.vendor = pRunningData; pRunningData += metadata.data.vorbis_comment.vendorLength; - metadata.data.vorbis_comment.commentCount = drflac__le2host_32(*(drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.vorbis_comment.comments = pRunningData; + pRunningData = (const char*)pRawData; + pRunningDataEnd = (const char*)pRawData + blockSize; + + metadata.data.vorbis_comment.vendorLength = drflac__le2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + + /* 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); + return DRFLAC_FALSE; + } + metadata.data.vorbis_comment.vendor = pRunningData; pRunningData += metadata.data.vorbis_comment.vendorLength; + metadata.data.vorbis_comment.commentCount = drflac__le2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + + /* 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); + return DRFLAC_FALSE; + } + metadata.data.vorbis_comment.pComments = pRunningData; + + /* Check that the comments section is valid before passing it to the callback */ + for (i = 0; i < metadata.data.vorbis_comment.commentCount; ++i) { + drflac_uint32 commentLength; + + if (pRunningDataEnd - pRunningData < 4) { + DRFLAC_FREE(pRawData); + 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); + return DRFLAC_FALSE; + } + pRunningData += commentLength; + } + onMeta(pUserDataMD, &metadata); DRFLAC_FREE(pRawData); @@ -3592,8 +5141,18 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s case DRFLAC_METADATA_BLOCK_TYPE_CUESHEET: { + if (blockSize < 396) { + return DRFLAC_FALSE; + } + if (onMeta) { - void* pRawData = DRFLAC_MALLOC(blockSize); + void* pRawData; + const char* pRunningData; + const char* pRunningDataEnd; + drflac_uint8 iTrack; + drflac_uint8 iIndex; + + pRawData = DRFLAC_MALLOC(blockSize); if (pRawData == NULL) { return DRFLAC_FALSE; } @@ -3606,12 +5165,42 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s metadata.pRawData = pRawData; metadata.rawDataSize = blockSize; - const char* pRunningData = (const char*)pRawData; - drflac_copy_memory(metadata.data.cuesheet.catalog, pRunningData, 128); pRunningData += 128; - metadata.data.cuesheet.leadInSampleCount = drflac__be2host_64(*(drflac_uint64*)pRunningData); pRunningData += 4; - metadata.data.cuesheet.isCD = ((pRunningData[0] & 0x80) >> 7) != 0; pRunningData += 259; - metadata.data.cuesheet.trackCount = pRunningData[0]; pRunningData += 1; - metadata.data.cuesheet.pTrackData = (const drflac_uint8*)pRunningData; + pRunningData = (const char*)pRawData; + pRunningDataEnd = (const char*)pRawData + blockSize; + + 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; + metadata.data.cuesheet.pTrackData = pRunningData; + + /* Check that the cuesheet tracks are valid before passing it to the callback */ + for (iTrack = 0; iTrack < metadata.data.cuesheet.trackCount; ++iTrack) { + drflac_uint8 indexCount; + drflac_uint32 indexPointSize; + + if (pRunningDataEnd - pRunningData < 36) { + DRFLAC_FREE(pRawData); + return DRFLAC_FALSE; + } + + /* Skip to the index point count */ + pRunningData += 35; + indexCount = pRunningData[0]; pRunningData += 1; + indexPointSize = indexCount * sizeof(drflac_cuesheet_track_index); + if (pRunningDataEnd - pRunningData < (drflac_int64)indexPointSize) { + DRFLAC_FREE(pRawData); + return DRFLAC_FALSE; + } + + /* Endian swap. */ + for (iIndex = 0; iIndex < indexCount; ++iIndex) { + drflac_cuesheet_track_index* pTrack = (drflac_cuesheet_track_index*)pRunningData; + pRunningData += sizeof(drflac_cuesheet_track_index); + pTrack->offset = drflac__be2host_64(pTrack->offset); + } + } + onMeta(pUserDataMD, &metadata); DRFLAC_FREE(pRawData); @@ -3620,8 +5209,16 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s case DRFLAC_METADATA_BLOCK_TYPE_PICTURE: { + if (blockSize < 32) { + return DRFLAC_FALSE; + } + if (onMeta) { - void* pRawData = DRFLAC_MALLOC(blockSize); + void* pRawData; + const char* pRunningData; + const char* pRunningDataEnd; + + pRawData = DRFLAC_MALLOC(blockSize); if (pRawData == NULL) { return DRFLAC_FALSE; } @@ -3634,18 +5231,39 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s metadata.pRawData = pRawData; metadata.rawDataSize = blockSize; - const char* pRunningData = (const char*)pRawData; - metadata.data.picture.type = drflac__be2host_32(*(drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.mimeLength = drflac__be2host_32(*(drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.mime = pRunningData; pRunningData += metadata.data.picture.mimeLength; - metadata.data.picture.descriptionLength = drflac__be2host_32(*(drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.description = pRunningData; - metadata.data.picture.width = drflac__be2host_32(*(drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.height = drflac__be2host_32(*(drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.colorDepth = drflac__be2host_32(*(drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.indexColorCount = drflac__be2host_32(*(drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.pictureDataSize = drflac__be2host_32(*(drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.pPictureData = (const drflac_uint8*)pRunningData; + pRunningData = (const char*)pRawData; + pRunningDataEnd = (const char*)pRawData + blockSize; + + metadata.data.picture.type = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.picture.mimeLength = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + + /* 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); + return DRFLAC_FALSE; + } + metadata.data.picture.mime = pRunningData; pRunningData += metadata.data.picture.mimeLength; + metadata.data.picture.descriptionLength = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + + /* 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); + return DRFLAC_FALSE; + } + metadata.data.picture.description = pRunningData; pRunningData += metadata.data.picture.descriptionLength; + metadata.data.picture.width = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.picture.height = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.picture.colorDepth = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.picture.indexColorCount = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.picture.pictureDataSize = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.picture.pPictureData = (const drflac_uint8*)pRunningData; + + /* 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); + return DRFLAC_FALSE; + } + onMeta(pUserDataMD, &metadata); DRFLAC_FREE(pRawData); @@ -3657,9 +5275,9 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s if (onMeta) { metadata.data.padding.unused = 0; - // Padding doesn't have anything meaningful in it, so just skip over it, but make sure the caller is aware of it by firing the callback. + /* Padding doesn't have anything meaningful in it, so just skip over it, but make sure the caller is aware of it by firing the callback. */ if (!onSeek(pUserData, blockSize, drflac_seek_origin_current)) { - isLastBlock = DRFLAC_TRUE; // An error occurred while seeking. Attempt to recover by treating this as the last block which will in turn terminate the loop. + isLastBlock = DRFLAC_TRUE; /* An error occurred while seeking. Attempt to recover by treating this as the last block which will in turn terminate the loop. */ } else { onMeta(pUserDataMD, &metadata); } @@ -3668,18 +5286,20 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s case DRFLAC_METADATA_BLOCK_TYPE_INVALID: { - // Invalid chunk. Just skip over this one. + /* Invalid chunk. Just skip over this one. */ if (onMeta) { if (!onSeek(pUserData, blockSize, drflac_seek_origin_current)) { - isLastBlock = DRFLAC_TRUE; // An error occurred while seeking. Attempt to recover by treating this as the last block which will in turn terminate the loop. + isLastBlock = DRFLAC_TRUE; /* An error occurred while seeking. Attempt to recover by treating this as the last block which will in turn terminate the loop. */ } } } break; default: { - // It's an unknown chunk, but not necessarily invalid. There's a chance more metadata blocks might be defined later on, so we - // can at the very least report the chunk to the application and let it look at the raw data. + /* + It's an unknown chunk, but not necessarily invalid. There's a chance more metadata blocks might be defined later on, so we + 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); if (pRawData == NULL) { @@ -3700,7 +5320,7 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s } break; } - // If we're not handling metadata, just skip over the block. If we are, it will have been handled earlier in the switch statement above. + /* If we're not handling metadata, just skip over the block. If we are, it will have been handled earlier in the switch statement above. */ if (onMeta == NULL && blockSize > 0) { if (!onSeek(pUserData, blockSize, drflac_seek_origin_current)) { isLastBlock = DRFLAC_TRUE; @@ -3722,42 +5342,45 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s 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) { - (void)onSeek; + /* Pre Condition: The bit stream should be sitting just past the 4-byte id header. */ - // Pre: The bit stream should be sitting just past the 4-byte id header. - - pInit->container = drflac_container_native; - - // The first metadata block should be the STREAMINFO block. drflac_uint8 isLastBlock; drflac_uint8 blockType; drflac_uint32 blockSize; + + (void)onSeek; + + pInit->container = drflac_container_native; + + /* The first metadata block should be the STREAMINFO block. */ if (!drflac__read_and_decode_block_header(onRead, pUserData, &isLastBlock, &blockType, &blockSize)) { return DRFLAC_FALSE; } if (blockType != DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO || blockSize != 34) { if (!relaxed) { - // We're opening in strict mode and the first block is not the STREAMINFO block. Error. + /* We're opening in strict mode and the first block is not the STREAMINFO block. Error. */ return DRFLAC_FALSE; } else { - // Relaxed mode. To open from here we need to just find the first frame and set the sample rate, etc. to whatever is defined - // for that frame. + /* + Relaxed mode. To open from here we need to just find the first frame and set the sample rate, etc. to whatever is defined + for that frame. + */ pInit->hasStreamInfoBlock = DRFLAC_FALSE; pInit->hasMetadataBlocks = DRFLAC_FALSE; - if (!drflac__read_next_frame_header(&pInit->bs, 0, &pInit->firstFrameHeader)) { - return DRFLAC_FALSE; // Couldn't find a frame. + if (!drflac__read_next_flac_frame_header(&pInit->bs, 0, &pInit->firstFrameHeader)) { + return DRFLAC_FALSE; /* Couldn't find a frame. */ } if (pInit->firstFrameHeader.bitsPerSample == 0) { - return DRFLAC_FALSE; // Failed to initialize because the first frame depends on the STREAMINFO block, which does not exist. + 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->maxBlockSize = 65535; /* <-- See notes here: https://xiph.org/flac/format.html#metadata_block_streaminfo */ return DRFLAC_TRUE; } } else { @@ -3771,8 +5394,8 @@ drflac_bool32 drflac__init_private__native(drflac_init_info* pInit, drflac_read_ 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->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; if (onMeta) { drflac_metadata metadata; @@ -3789,7 +5412,7 @@ drflac_bool32 drflac__init_private__native(drflac_init_info* pInit, drflac_read_ #ifndef DR_FLAC_NO_OGG #define DRFLAC_OGG_MAX_PAGE_SIZE 65307 -#define DRFLAC_OGG_CAPTURE_PATTERN_CRC32 1605413199 // CRC-32 of "OggS". +#define DRFLAC_OGG_CAPTURE_PATTERN_CRC32 1605413199 /* CRC-32 of "OggS". */ typedef enum { @@ -3797,7 +5420,7 @@ typedef enum drflac_ogg_fail_on_crc_mismatch } drflac_ogg_crc_mismatch_recovery; - +#ifndef DR_FLAC_NO_CRC static drflac_uint32 drflac__crc32_table[] = { 0x00000000L, 0x04C11DB7L, 0x09823B6EL, 0x0D4326D9L, 0x130476DCL, 0x17C56B6BL, 0x1A864DB2L, 0x1E475005L, @@ -3864,6 +5487,7 @@ static drflac_uint32 drflac__crc32_table[] = { 0xAFB010B1L, 0xAB710D06L, 0xA6322BDFL, 0xA2F33668L, 0xBCB4666DL, 0xB8757BDAL, 0xB5365D03L, 0xB1F740B4L }; +#endif static DRFLAC_INLINE drflac_uint32 drflac_crc32_byte(drflac_uint32 crc32, drflac_uint8 data) { @@ -3895,8 +5519,9 @@ static DRFLAC_INLINE drflac_uint32 drflac_crc32_uint64(drflac_uint32 crc32, drfl static DRFLAC_INLINE drflac_uint32 drflac_crc32_buffer(drflac_uint32 crc32, drflac_uint8* pData, drflac_uint32 dataSize) { - // This can be optimized. - for (drflac_uint32 i = 0; i < dataSize; ++i) { + /* This can be optimized. */ + drflac_uint32 i; + for (i = 0; i < dataSize; ++i) { crc32 = drflac_crc32_byte(crc32, pData[i]); } return crc32; @@ -3916,7 +5541,9 @@ static DRFLAC_INLINE drflac_uint32 drflac_ogg__get_page_header_size(drflac_ogg_p static DRFLAC_INLINE drflac_uint32 drflac_ogg__get_page_body_size(drflac_ogg_page_header* pHeader) { drflac_uint32 pageBodySize = 0; - for (int i = 0; i < pHeader->segmentCount; ++i) { + int i; + + for (i = 0; i < pHeader->segmentCount; ++i) { pageBodySize += pHeader->segmentTable[i]; } @@ -3925,9 +5552,11 @@ static DRFLAC_INLINE drflac_uint32 drflac_ogg__get_page_body_size(drflac_ogg_pag 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_uint8 data[23]; if (onRead(pUserData, data, 23) != 23) { return DRFLAC_END_OF_STREAM; } @@ -3941,13 +5570,12 @@ drflac_result drflac_ogg__read_page_header_after_capture_pattern(drflac_read_pro 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. + /* Calculate the CRC. Note that for the calculation the checksum part of the page needs to be set to 0. */ data[18] = 0; data[19] = 0; data[20] = 0; data[21] = 0; - drflac_uint32 i; for (i = 0; i < 23; ++i) { *pCRC32 = drflac_crc32_byte(*pCRC32, data[i]); } @@ -3967,20 +5595,23 @@ drflac_result drflac_ogg__read_page_header_after_capture_pattern(drflac_read_pro 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; - drflac_uint8 id[4]; if (onRead(pUserData, id, 4) != 4) { return DRFLAC_END_OF_STREAM; } *pBytesRead += 4; - // We need to read byte-by-byte until we find the OggS capture pattern. + /* We need to read byte-by-byte until we find the OggS capture pattern. */ for (;;) { if (drflac_ogg__is_capture_pattern(id)) { + drflac_result result; + *pCRC32 = DRFLAC_OGG_CAPTURE_PATTERN_CRC32; - drflac_result result = drflac_ogg__read_page_header_after_capture_pattern(onRead, pUserData, pHeader, pBytesRead, pCRC32); + result = drflac_ogg__read_page_header_after_capture_pattern(onRead, pUserData, pHeader, pBytesRead, pCRC32); if (result == DRFLAC_SUCCESS) { return DRFLAC_SUCCESS; } else { @@ -3991,7 +5622,7 @@ drflac_result drflac_ogg__read_page_header(drflac_read_proc onRead, void* pUserD } } } else { - // The first 4 bytes did not equal the capture pattern. Read the next byte and try again. + /* The first 4 bytes did not equal the capture pattern. Read the next byte and try again. */ id[0] = id[1]; id[1] = id[2]; id[2] = id[3]; @@ -4004,25 +5635,27 @@ drflac_result drflac_ogg__read_page_header(drflac_read_proc onRead, void* pUserD } -// The main part of the Ogg encapsulation is the conversion from the physical Ogg bitstream to the native FLAC bitstream. It works -// in three general stages: Ogg Physical Bitstream -> Ogg/FLAC Logical Bitstream -> FLAC Native Bitstream. dr_flac is designed -// in such a way that the core sections assume everything is delivered in native format. Therefore, for each encapsulation type -// dr_flac is supporting there needs to be a layer sitting on top of the onRead and onSeek callbacks that ensures the bits read from -// the physical Ogg bitstream are converted and delivered in native FLAC format. +/* +The main part of the Ogg encapsulation is the conversion from the physical Ogg bitstream to the native FLAC bitstream. It works +in three general stages: Ogg Physical Bitstream -> Ogg/FLAC Logical Bitstream -> FLAC Native Bitstream. dr_flac is designed +in such a way that the core sections assume everything is delivered in native format. Therefore, for each encapsulation type +dr_flac is supporting there needs to be a layer sitting on top of the onRead and onSeek callbacks that ensures the bits read from +the physical Ogg bitstream are converted and delivered in native FLAC format. +*/ typedef struct { - drflac_read_proc onRead; // The original onRead callback from drflac_open() and family. - drflac_seek_proc onSeek; // The original onSeek callback from drflac_open() and family. - void* pUserData; // The user data passed on onRead and onSeek. This is the user data that was passed on drflac_open() and family. - drflac_uint64 currentBytePos; // The position of the byte we are sitting on in the physical byte stream. Used for efficient seeking. - drflac_uint64 firstBytePos; // The position of the first byte in the physical bitstream. Points to the start of the "OggS" identifier of the FLAC bos page. - drflac_uint32 serialNumber; // The serial number of the FLAC audio pages. This is determined by the initial header page that was read during initialization. - drflac_ogg_page_header bosPageHeader; // Used for seeking. + drflac_read_proc onRead; /* The original onRead callback from drflac_open() and family. */ + drflac_seek_proc onSeek; /* The original onSeek callback from drflac_open() and family. */ + void* pUserData; /* The user data passed on onRead and onSeek. This is the user data that was passed on drflac_open() and family. */ + drflac_uint64 currentBytePos; /* The position of the byte we are sitting on in the physical byte stream. Used for efficient seeking. */ + drflac_uint64 firstBytePos; /* The position of the first byte in the physical bitstream. Points to the start of the "OggS" identifier of the FLAC bos page. */ + drflac_uint32 serialNumber; /* The serial number of the FLAC audio pages. This is determined by the initial header page that was read during initialization. */ + drflac_ogg_page_header bosPageHeader; /* Used for seeking. */ drflac_ogg_page_header currentPageHeader; drflac_uint32 bytesRemainingInPage; drflac_uint32 pageDataSize; drflac_uint8 pageData[DRFLAC_OGG_MAX_PAGE_SIZE]; -} drflac_oggbs; // oggbs = Ogg Bitstream +} drflac_oggbs; /* oggbs = Ogg Bitstream */ static size_t drflac_oggbs__read_physical(drflac_oggbs* oggbs, void* bufferOut, size_t bytesToRead) { @@ -4059,7 +5692,7 @@ static drflac_bool32 drflac_oggbs__seek_physical(drflac_oggbs* oggbs, drflac_uin offset -= 0x7FFFFFFF; } - if (!oggbs->onSeek(oggbs->pUserData, (int)offset, drflac_seek_origin_current)) { // <-- Safe cast thanks to the loop above. + if (!oggbs->onSeek(oggbs->pUserData, (int)offset, drflac_seek_origin_current)) { /* <-- Safe cast thanks to the loop above. */ return DRFLAC_FALSE; } oggbs->currentBytePos += offset; @@ -4074,18 +5707,23 @@ static drflac_bool32 drflac_oggbs__goto_next_page(drflac_oggbs* oggbs, drflac_og for (;;) { drflac_uint32 crc32 = 0; drflac_uint32 bytesRead; + drflac_uint32 pageBodySize; +#ifndef DR_FLAC_NO_CRC + drflac_uint32 actualCRC32; +#endif + if (drflac_ogg__read_page_header(oggbs->onRead, oggbs->pUserData, &header, &bytesRead, &crc32) != DRFLAC_SUCCESS) { return DRFLAC_FALSE; } oggbs->currentBytePos += bytesRead; - drflac_uint32 pageBodySize = drflac_ogg__get_page_body_size(&header); + pageBodySize = drflac_ogg__get_page_body_size(&header); if (pageBodySize > DRFLAC_OGG_MAX_PAGE_SIZE) { - continue; // Invalid page size. Assume it's corrupted and just move to the next page. + continue; /* Invalid page size. Assume it's corrupted and just move to the next page. */ } if (header.serialNumber != oggbs->serialNumber) { - // It's not a FLAC page. Skip it. + /* It's not a FLAC page. Skip it. */ if (pageBodySize > 0 && !drflac_oggbs__seek_physical(oggbs, pageBodySize, drflac_seek_origin_current)) { return DRFLAC_FALSE; } @@ -4093,27 +5731,29 @@ static drflac_bool32 drflac_oggbs__goto_next_page(drflac_oggbs* oggbs, drflac_og } - // We need to read the entire page and then do a CRC check on it. If there's a CRC mismatch we need to skip this page. + /* We need to read the entire page and then do a CRC check on it. If there's a CRC mismatch we need to skip this page. */ if (drflac_oggbs__read_physical(oggbs, oggbs->pageData, pageBodySize) != pageBodySize) { return DRFLAC_FALSE; } oggbs->pageDataSize = pageBodySize; #ifndef DR_FLAC_NO_CRC - drflac_uint32 actualCRC32 = drflac_crc32_buffer(crc32, oggbs->pageData, oggbs->pageDataSize); + actualCRC32 = drflac_crc32_buffer(crc32, oggbs->pageData, oggbs->pageDataSize); if (actualCRC32 != header.checksum) { if (recoveryMethod == drflac_ogg_recover_on_crc_mismatch) { - continue; // CRC mismatch. Skip this page. + continue; /* CRC mismatch. Skip this page. */ } else { - // Even though we are failing on a CRC mismatch, we still want our stream to be in a good state. Therefore we - // go to the next valid page to ensure we're in a good state, but return false to let the caller know that the - // seek did not fully complete. + /* + Even though we are failing on a CRC mismatch, we still want our stream to be in a good state. Therefore we + go to the next valid page to ensure we're in a good state, but return false to let the caller know that the + seek did not fully complete. + */ drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch); return DRFLAC_FALSE; } } #else - (void)recoveryMethod; // <-- Silence a warning. + (void)recoveryMethod; /* <-- Silence a warning. */ #endif oggbs->currentPageHeader = header; @@ -4122,7 +5762,7 @@ static drflac_bool32 drflac_oggbs__goto_next_page(drflac_oggbs* oggbs, drflac_og } } -// Function below is unused at the moment, but I might be re-adding it later. +/* Function below is unused at the moment, but I might be re-adding it later. */ #if 0 static drflac_uint8 drflac_oggbs__get_current_segment_index(drflac_oggbs* oggbs, drflac_uint8* pBytesRemainingInSeg) { @@ -4145,7 +5785,7 @@ static drflac_uint8 drflac_oggbs__get_current_segment_index(drflac_oggbs* oggbs, static drflac_bool32 drflac_oggbs__seek_to_next_packet(drflac_oggbs* oggbs) { - // The current packet ends when we get to the segment with a lacing value of < 255 which is not at the end of a page. + /* The current packet ends when we get to the segment with a lacing value of < 255 which is not at the end of a page. */ for (;;) { drflac_bool32 atEndOfPage = DRFLAC_FALSE; @@ -4166,24 +5806,28 @@ static drflac_bool32 drflac_oggbs__seek_to_next_packet(drflac_oggbs* oggbs) bytesToEndOfPacketOrPage += segmentSize; } - // At this point we will have found either the packet or the end of the page. If were at the end of the page we'll - // want to load the next page and keep searching for the end of the packet. + /* + At this point we will have found either the packet or the end of the page. If were at the end of the page we'll + want to load the next page and keep searching for the end of the packet. + */ drflac_oggbs__seek_physical(oggbs, bytesToEndOfPacketOrPage, drflac_seek_origin_current); oggbs->bytesRemainingInPage -= bytesToEndOfPacketOrPage; if (atEndOfPage) { - // We're potentially at the next packet, but we need to check the next page first to be sure because the packet may - // straddle pages. + /* + We're potentially at the next packet, but we need to check the next page first to be sure because the packet may + straddle pages. + */ if (!drflac_oggbs__goto_next_page(oggbs)) { return DRFLAC_FALSE; } - // If it's a fresh packet it most likely means we're at the next packet. + /* If it's a fresh packet it most likely means we're at the next packet. */ if ((oggbs->currentPageHeader.headerType & 0x01) == 0) { return DRFLAC_TRUE; } } else { - // We're at the next packet. + /* We're at the next packet. */ return DRFLAC_TRUE; } } @@ -4191,9 +5835,9 @@ static drflac_bool32 drflac_oggbs__seek_to_next_packet(drflac_oggbs* oggbs) static drflac_bool32 drflac_oggbs__seek_to_next_frame(drflac_oggbs* oggbs) { - // The bitstream should be sitting on the first byte just after the header of the frame. + /* The bitstream should be sitting on the first byte just after the header of the frame. */ - // What we're actually doing here is seeking to the start of the next packet. + /* What we're actually doing here is seeking to the start of the next packet. */ return drflac_oggbs__seek_to_next_packet(oggbs); } #endif @@ -4201,12 +5845,13 @@ static drflac_bool32 drflac_oggbs__seek_to_next_frame(drflac_oggbs* oggbs) static size_t drflac__on_read_ogg(void* pUserData, void* bufferOut, size_t bytesToRead) { drflac_oggbs* oggbs = (drflac_oggbs*)pUserData; - drflac_assert(oggbs != NULL); - drflac_uint8* pRunningBufferOut = (drflac_uint8*)bufferOut; - - // Reading is done page-by-page. If we've run out of bytes in the page we need to move to the next one. size_t bytesRead = 0; + + 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; @@ -4217,7 +5862,7 @@ static size_t drflac__on_read_ogg(void* pUserData, void* bufferOut, size_t bytes break; } - // If we get here it means some of the requested data is contained in the next pages. + /* 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); bytesRead += oggbs->bytesRemainingInPage; @@ -4227,7 +5872,7 @@ static size_t drflac__on_read_ogg(void* pUserData, void* bufferOut, size_t bytes 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. + break; /* Failed to go to the next page. Might have simply hit the end of the stream. */ } } @@ -4237,10 +5882,12 @@ static size_t drflac__on_read_ogg(void* pUserData, void* bufferOut, size_t bytes static drflac_bool32 drflac__on_seek_ogg(void* pUserData, int offset, drflac_seek_origin origin) { drflac_oggbs* oggbs = (drflac_oggbs*)pUserData; - drflac_assert(oggbs != NULL); - drflac_assert(offset > 0 || (offset == 0 && origin == drflac_seek_origin_start)); + int bytesSeeked = 0; - // Seeking is always forward which makes things a lot simpler. + 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) { if (!drflac_oggbs__seek_physical(oggbs, (int)oggbs->firstBytePos, drflac_seek_origin_start)) { return DRFLAC_FALSE; @@ -4253,10 +5900,8 @@ 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); - int bytesSeeked = 0; while (bytesSeeked < offset) { int bytesRemainingToSeek = offset - bytesSeeked; drflac_assert(bytesRemainingToSeek >= 0); @@ -4267,7 +5912,7 @@ static drflac_bool32 drflac__on_seek_ogg(void* pUserData, int offset, drflac_see break; } - // If we get here it means some of the requested data is contained in the next pages. + /* If we get here it means some of the requested data is contained in the next pages. */ if (oggbs->bytesRemainingInPage > 0) { bytesSeeked += (int)oggbs->bytesRemainingInPage; oggbs->bytesRemainingInPage = 0; @@ -4275,7 +5920,7 @@ static drflac_bool32 drflac__on_seek_ogg(void* pUserData, int offset, drflac_see 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. + /* Failed to go to the next page. We either hit the end of the stream or had a CRC mismatch. */ return DRFLAC_FALSE; } } @@ -4286,38 +5931,45 @@ static drflac_bool32 drflac__on_seek_ogg(void* pUserData, int offset, drflac_see drflac_bool32 drflac_ogg__seek_to_sample(drflac* pFlac, drflac_uint64 sampleIndex) { drflac_oggbs* oggbs = (drflac_oggbs*)pFlac->_oggbs; + drflac_uint64 originalBytePos; + drflac_uint64 runningGranulePosition; + drflac_uint64 runningFrameBytePos; + drflac_uint64 runningSampleCount; - drflac_uint64 originalBytePos = oggbs->currentBytePos; // For recovery. + drflac_assert(oggbs != NULL); - // First seek to the first frame. + originalBytePos = oggbs->currentBytePos; /* For recovery. */ + + /* First seek to the first frame. */ if (!drflac__seek_to_byte(&pFlac->bs, pFlac->firstFramePos)) { return DRFLAC_FALSE; } oggbs->bytesRemainingInPage = 0; - drflac_uint64 runningGranulePosition = 0; - drflac_uint64 runningFrameBytePos = oggbs->currentBytePos; // <-- Points to the OggS identifier. + 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); - return DRFLAC_FALSE; // Never did find that sample... + return DRFLAC_FALSE; /* Never did find that sample... */ } runningFrameBytePos = oggbs->currentBytePos - drflac_ogg__get_page_header_size(&oggbs->currentPageHeader) - oggbs->pageDataSize; if (oggbs->currentPageHeader.granulePosition*pFlac->channels >= sampleIndex) { - break; // The sample is somewhere in the previous page. + break; /* The sample is somewhere in the previous page. */ } - - // At this point we know the sample is not in the previous page. It could possibly be in this page. For simplicity we - // disregard any pages that do not begin a fresh packet. - if ((oggbs->currentPageHeader.headerType & 0x01) == 0) { // <-- Is it a fresh page? + /* + At this point we know the sample is not in the previous page. It could possibly be in this page. For simplicity we + disregard any pages that do not begin a fresh packet. + */ + if ((oggbs->currentPageHeader.headerType & 0x01) == 0) { /* <-- Is it a fresh page? */ if (oggbs->currentPageHeader.segmentTable[0] >= 2) { drflac_uint8 firstBytesInPage[2]; firstBytesInPage[0] = oggbs->pageData[0]; firstBytesInPage[1] = oggbs->pageData[1]; - if ((firstBytesInPage[0] == 0xFF) && (firstBytesInPage[1] & 0xFC) == 0xF8) { // <-- Does the page begin with a frame's sync code? + if ((firstBytesInPage[0] == 0xFF) && (firstBytesInPage[1] & 0xFC) == 0xF8) { /* <-- Does the page begin with a frame's sync code? */ runningGranulePosition = oggbs->currentPageHeader.granulePosition*pFlac->channels; } @@ -4326,11 +5978,12 @@ drflac_bool32 drflac_ogg__seek_to_sample(drflac* pFlac, drflac_uint64 sampleInde } } - - // We found the page that that is closest to the sample, so now we need to find it. The first thing to do is seek to the - // start of that page. In the loop above we checked that it was a fresh page which means this page is also the start of - // a new frame. This property means that after we've seeked to the page we can immediately start looping over frames until - // we find the one containing the target sample. + /* + We found the page that that is closest to the sample, so now we need to find it. The first thing to do is seek to the + start of that page. In the loop above we checked that it was a fresh page which means this page is also the start of + a new frame. This property means that after we've seeked to the page we can immediately start looping over frames until + we find the one containing the target sample. + */ if (!drflac_oggbs__seek_physical(oggbs, runningFrameBytePos, drflac_seek_origin_start)) { return DRFLAC_FALSE; } @@ -4338,66 +5991,75 @@ drflac_bool32 drflac_ogg__seek_to_sample(drflac* pFlac, drflac_uint64 sampleInde return DRFLAC_FALSE; } - - // 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. - drflac_uint64 runningSampleCount = runningGranulePosition; + /* + 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; 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_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. - if (!drflac__read_next_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + /* + 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; + + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { return DRFLAC_FALSE; } - drflac_uint64 firstSampleInFrame = 0; - drflac_uint64 lastSampleInFrame = 0; drflac__get_current_frame_sample_range(pFlac, &firstSampleInFrame, &lastSampleInFrame); - drflac_uint64 sampleCountInThisFrame = (lastSampleInFrame - firstSampleInFrame) + 1; + sampleCountInThisFrame = (lastSampleInFrame - firstSampleInFrame) + 1; if (sampleIndex < (runningSampleCount + sampleCountInThisFrame)) { - // 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_result result = drflac__decode_frame(pFlac); + /* + 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_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. + /* 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) { return DRFLAC_TRUE; } - return drflac_read_s32(pFlac, samplesToDecode, NULL) != 0; // <-- If this fails, something bad has happened (it should never fail). + return drflac__seek_forward_by_samples(pFlac, samplesToDecode) == samplesToDecode; /* <-- 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. + continue; /* CRC mismatch. Pretend this frame never existed. */ } else { return DRFLAC_FALSE; } } } else { - // It's not in this frame. We need to seek past the frame, but check if there was a CRC mismatch. If so, we pretend this - // frame never existed and leave the running sample count untouched. - drflac_result result = drflac__seek_to_next_frame(pFlac); + /* + It's not in this frame. We need to seek past the frame, but check if there was a CRC mismatch. If so, we pretend this + frame never existed and leave the running sample count untouched. + */ + drflac_result result = drflac__seek_to_next_flac_frame(pFlac); if (result == DRFLAC_SUCCESS) { runningSampleCount += sampleCountInThisFrame; } else { if (result == DRFLAC_CRC_MISMATCH) { - continue; // CRC mismatch. Pretend this frame never existed. + continue; /* CRC mismatch. Pretend this frame never existed. */ } else { return DRFLAC_FALSE; } @@ -4409,45 +6071,48 @@ 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) { - // Pre: The bit stream should be sitting just past the 4-byte OggS capture pattern. + drflac_ogg_page_header header; + drflac_uint32 crc32 = DRFLAC_OGG_CAPTURE_PATTERN_CRC32; + drflac_uint32 bytesRead = 0; + + /* Pre Condition: The bit stream should be sitting just past the 4-byte OggS capture pattern. */ (void)relaxed; pInit->container = drflac_container_ogg; pInit->oggFirstBytePos = 0; - // We'll get here if the first 4 bytes of the stream were the OggS capture pattern, however it doesn't necessarily mean the - // stream includes FLAC encoded audio. To check for this we need to scan the beginning-of-stream page markers and check if - // any match the FLAC specification. Important to keep in mind that the stream may be multiplexed. - drflac_ogg_page_header header; - - drflac_uint32 crc32 = DRFLAC_OGG_CAPTURE_PATTERN_CRC32; - drflac_uint32 bytesRead = 0; + /* + We'll get here if the first 4 bytes of the stream were the OggS capture pattern, however it doesn't necessarily mean the + stream includes FLAC encoded audio. To check for this we need to scan the beginning-of-stream page markers and check if + any match the FLAC specification. Important to keep in mind that the stream may be multiplexed. + */ if (drflac_ogg__read_page_header_after_capture_pattern(onRead, pUserData, &header, &bytesRead, &crc32) != DRFLAC_SUCCESS) { return DRFLAC_FALSE; } pInit->runningFilePos += bytesRead; for (;;) { - // Break if we're past the beginning of stream page. + int pageBodySize; + + /* Break if we're past the beginning of stream page. */ if ((header.headerType & 0x02) == 0) { return DRFLAC_FALSE; } - - // Check if it's a FLAC header. - int pageBodySize = drflac_ogg__get_page_body_size(&header); - if (pageBodySize == 51) { // 51 = the lacing value of the FLAC header packet. - // It could be a FLAC page... + /* Check if it's a FLAC header. */ + pageBodySize = drflac_ogg__get_page_body_size(&header); + if (pageBodySize == 51) { /* 51 = the lacing value of the FLAC header packet. */ + /* It could be a FLAC page... */ drflac_uint32 bytesRemainingInPage = pageBodySize; - drflac_uint8 packetType; + if (onRead(pUserData, &packetType, 1) != 1) { return DRFLAC_FALSE; } bytesRemainingInPage -= 1; if (packetType == 0x7F) { - // Increasingly more likely to be a FLAC page... + /* Increasingly more likely to be a FLAC page... */ drflac_uint8 sig[4]; if (onRead(pUserData, sig, 4) != 4) { return DRFLAC_FALSE; @@ -4455,29 +6120,32 @@ drflac_bool32 drflac__init_private__ogg(drflac_init_info* pInit, drflac_read_pro bytesRemainingInPage -= 4; if (sig[0] == 'F' && sig[1] == 'L' && sig[2] == 'A' && sig[3] == 'C') { - // Almost certainly a FLAC page... + /* Almost certainly a FLAC page... */ drflac_uint8 mappingVersion[2]; if (onRead(pUserData, mappingVersion, 2) != 2) { return DRFLAC_FALSE; } if (mappingVersion[0] != 1) { - return DRFLAC_FALSE; // Only supporting version 1.x of the Ogg mapping. + return DRFLAC_FALSE; /* Only supporting version 1.x of the Ogg mapping. */ } - // The next 2 bytes are the non-audio packets, not including this one. We don't care about this because we're going to - // be handling it in a generic way based on the serial number and packet types. + /* + The next 2 bytes are the non-audio packets, not including this one. We don't care about this because we're going to + be handling it in a generic way based on the serial number and packet types. + */ if (!onSeek(pUserData, 2, drflac_seek_origin_current)) { return DRFLAC_FALSE; } - // Expecting the native FLAC signature "fLaC". + /* Expecting the native FLAC signature "fLaC". */ if (onRead(pUserData, sig, 4) != 4) { return DRFLAC_FALSE; } if (sig[0] == 'f' && sig[1] == 'L' && sig[2] == 'a' && sig[3] == 'C') { - // The remaining data in the page should be the STREAMINFO block. + /* The remaining data in the page should be the STREAMINFO block. */ + drflac_streaminfo streaminfo; drflac_uint8 isLastBlock; drflac_uint8 blockType; drflac_uint32 blockSize; @@ -4486,12 +6154,11 @@ drflac_bool32 drflac__init_private__ogg(drflac_init_info* pInit, drflac_read_pro } if (blockType != DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO || blockSize != 34) { - return DRFLAC_FALSE; // Invalid block type. First block must be the STREAMINFO block. + return DRFLAC_FALSE; /* Invalid block type. First block must be the STREAMINFO block. */ } - drflac_streaminfo streaminfo; if (drflac__read_streaminfo(onRead, pUserData, &streaminfo)) { - // Success! + /* Success! */ pInit->hasStreamInfoBlock = DRFLAC_TRUE; pInit->sampleRate = streaminfo.sampleRate; pInit->channels = streaminfo.channels; @@ -4510,26 +6177,26 @@ drflac_bool32 drflac__init_private__ogg(drflac_init_info* pInit, drflac_read_pro } pInit->runningFilePos += pageBodySize; - pInit->oggFirstBytePos = pInit->runningFilePos - 79; // Subtracting 79 will place us right on top of the "OggS" identifier of the FLAC bos page. + pInit->oggFirstBytePos = pInit->runningFilePos - 79; /* Subtracting 79 will place us right on top of the "OggS" identifier of the FLAC bos page. */ pInit->oggSerial = header.serialNumber; pInit->oggBosHeader = header; break; } else { - // Failed to read STREAMINFO block. Aww, so close... + /* Failed to read STREAMINFO block. Aww, so close... */ return DRFLAC_FALSE; } } else { - // Invalid file. + /* Invalid file. */ return DRFLAC_FALSE; } } else { - // Not a FLAC header. Skip it. + /* Not a FLAC header. Skip it. */ if (!onSeek(pUserData, bytesRemainingInPage, drflac_seek_origin_current)) { return DRFLAC_FALSE; } } } else { - // Not a FLAC header. Seek past the entire page and move on to the next. + /* Not a FLAC header. Seek past the entire page and move on to the next. */ if (!onSeek(pUserData, bytesRemainingInPage, drflac_seek_origin_current)) { return DRFLAC_FALSE; } @@ -4543,24 +6210,28 @@ drflac_bool32 drflac__init_private__ogg(drflac_init_info* pInit, drflac_read_pro pInit->runningFilePos += pageBodySize; - // Read the header of the next page. + /* Read the header of the next page. */ if (drflac_ogg__read_page_header(onRead, pUserData, &header, &bytesRead, &crc32) != DRFLAC_SUCCESS) { return DRFLAC_FALSE; } pInit->runningFilePos += bytesRead; } - - // If we get here it means we found a FLAC audio stream. We should be sitting on the first byte of the header of the next page. The next - // packets in the FLAC logical stream contain the metadata. The only thing left to do in the initialization phase for Ogg is to create the - // Ogg bistream object. - pInit->hasMetadataBlocks = DRFLAC_TRUE; // <-- Always have at least VORBIS_COMMENT metadata block. + /* + If we get here it means we found a FLAC audio stream. We should be sitting on the first byte of the header of the next page. The next + packets in the FLAC logical stream contain the metadata. The only thing left to do in the initialization phase for Ogg is to create the + Ogg bistream object. + */ + pInit->hasMetadataBlocks = DRFLAC_TRUE; /* <-- Always have at least VORBIS_COMMENT metadata block. */ return DRFLAC_TRUE; } #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) { + drflac_bool32 relaxed; + drflac_uint8 id[4]; + if (pInit == NULL || onRead == NULL || onSeek == NULL) { return DRFLAC_FALSE; } @@ -4579,27 +6250,28 @@ drflac_bool32 drflac__init_private(drflac_init_info* pInit, drflac_read_proc onR drflac__reset_cache(&pInit->bs); - // If the container is explicitly defined then we can try opening in relaxed mode. - drflac_bool32 relaxed = container != drflac_container_unknown; + /* If the container is explicitly defined then we can try opening in relaxed mode. */ + relaxed = container != drflac_container_unknown; - drflac_uint8 id[4]; - - // Skip over any ID3 tags. + /* Skip over any ID3 tags. */ for (;;) { if (onRead(pUserData, id, 4) != 4) { - return DRFLAC_FALSE; // Ran out of data. + return DRFLAC_FALSE; /* Ran out of data. */ } pInit->runningFilePos += 4; if (id[0] == 'I' && id[1] == 'D' && id[2] == '3') { drflac_uint8 header[6]; + drflac_uint8 flags; + drflac_uint32 headerSize; + if (onRead(pUserData, header, 6) != 6) { - return DRFLAC_FALSE; // Ran out of data. + return DRFLAC_FALSE; /* Ran out of data. */ } pInit->runningFilePos += 6; - drflac_uint8 flags = header[1]; - drflac_uint32 headerSize; + flags = header[1]; + drflac_copy_memory(&headerSize, header+2, 4); headerSize = drflac__unsynchsafe_32(drflac__be2host_32(headerSize)); if (flags & 0x10) { @@ -4607,7 +6279,7 @@ drflac_bool32 drflac__init_private(drflac_init_info* pInit, drflac_read_proc onR } if (!onSeek(pUserData, headerSize, drflac_seek_origin_current)) { - return DRFLAC_FALSE; // Failed to seek past the tag. + return DRFLAC_FALSE; /* Failed to seek past the tag. */ } pInit->runningFilePos += headerSize; } else { @@ -4624,7 +6296,7 @@ drflac_bool32 drflac__init_private(drflac_init_info* pInit, drflac_read_proc onR } #endif - // If we get here it means we likely don't have a header. Try opening in relaxed mode, if applicable. + /* If we get here it means we likely don't have a header. Try opening in relaxed mode, if applicable. */ if (relaxed) { if (container == drflac_container_native) { return drflac__init_private__native(pInit, onRead, onSeek, onMeta, pUserData, pUserDataMD, relaxed); @@ -4636,7 +6308,7 @@ drflac_bool32 drflac__init_private(drflac_init_info* pInit, drflac_read_proc onR #endif } - // Unsupported container. + /* Unsupported container. */ return DRFLAC_FALSE; } @@ -4646,61 +6318,76 @@ void drflac__init_from_info(drflac* pFlac, drflac_init_info* pInit) 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->container = pInit->container; + 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* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, void* pUserDataMD) { + 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* pFlac; + #ifndef DRFLAC_NO_CPUID - // CPU support first. + /* CPU support first. */ drflac__init_cpu_caps(); #endif - drflac_init_info init; if (!drflac__init_private(&init, onRead, onSeek, onMeta, container, pUserData, pUserDataMD)) { return NULL; } - // 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. - drflac_uint32 allocationSize = sizeof(drflac); + /* + 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. + */ + allocationSize = sizeof(drflac); - // The allocation size for decoded frames depends on the number of 32-bit integers that fit inside the largest SIMD vector - // we are supporting. - drflac_uint32 wholeSIMDVectorCountPerChannel; - if ((init.maxBlockSize % (DRFLAC_MAX_SIMD_VECTOR_SIZE / sizeof(drflac_int32))) == 0) { - wholeSIMDVectorCountPerChannel = (init.maxBlockSize / (DRFLAC_MAX_SIMD_VECTOR_SIZE / sizeof(drflac_int32))); + /* + 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))); } else { - wholeSIMDVectorCountPerChannel = (init.maxBlockSize / (DRFLAC_MAX_SIMD_VECTOR_SIZE / sizeof(drflac_int32))) + 1; + wholeSIMDVectorCountPerChannel = ((init.maxBlockSize+DRFLAC_LEADING_SAMPLES) / (DRFLAC_MAX_SIMD_VECTOR_SIZE / sizeof(drflac_int32))) + 1; } - drflac_uint32 decodedSamplesAllocationSize = wholeSIMDVectorCountPerChannel * DRFLAC_MAX_SIMD_VECTOR_SIZE * init.channels; + decodedSamplesAllocationSize = wholeSIMDVectorCountPerChannel * DRFLAC_MAX_SIMD_VECTOR_SIZE * init.channels; allocationSize += decodedSamplesAllocationSize; - allocationSize += DRFLAC_MAX_SIMD_VECTOR_SIZE; // Allocate extra bytes to ensure we have enough for alignment. + allocationSize += DRFLAC_MAX_SIMD_VECTOR_SIZE; /* Allocate extra bytes to ensure we have enough for alignment. */ #ifndef DR_FLAC_NO_OGG - // There's additional data required for Ogg streams. - drflac_uint32 oggbsAllocationSize = 0; + /* There's additional data required for Ogg streams. */ + oggbsAllocationSize = 0; if (init.container == drflac_container_ogg) { oggbsAllocationSize = sizeof(drflac_oggbs); allocationSize += oggbsAllocationSize; } - drflac_oggbs oggbs; drflac_zero_memory(&oggbs, sizeof(oggbs)); if (init.container == drflac_container_ogg) { oggbs.onRead = onRead; @@ -4714,12 +6401,14 @@ drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_p } #endif - // This part is a bit awkward. We need to load the seektable so that it can be referenced in-memory, but I want the drflac object to - // consist of only a single heap allocation. To this, the size of the seek table needs to be known, which we determine when reading - // and decoding the metadata. - drflac_uint64 firstFramePos = 42; // <-- We know we are at byte 42 at this point. - drflac_uint64 seektablePos = 0; - drflac_uint32 seektableSize = 0; + /* + This part is a bit awkward. We need to load the seektable so that it can be referenced in-memory, but I want the drflac object to + consist of only a single heap allocation. To this, the size of the seek table needs to be known, which we determine when reading + and decoding the metadata. + */ + firstFramePos = 42; /* <-- We know we are at byte 42 at this point. */ + seektablePos = 0; + seektableSize = 0; if (init.hasMetadataBlocks) { drflac_read_proc onReadOverride = onRead; drflac_seek_proc onSeekOverride = onSeek; @@ -4741,7 +6430,7 @@ drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_p } - drflac* pFlac = (drflac*)DRFLAC_MALLOC(allocationSize); + pFlac = (drflac*)DRFLAC_MALLOC(allocationSize); drflac__init_from_info(pFlac, &init); pFlac->pDecodedSamples = (drflac_int32*)drflac_align((size_t)pFlac->pExtraData, DRFLAC_MAX_SIMD_VECTOR_SIZE); @@ -4750,7 +6439,7 @@ drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_p drflac_oggbs* pInternalOggbs = (drflac_oggbs*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize + seektableSize); *pInternalOggbs = oggbs; - // The Ogg bistream needs to be layered on top of the original bitstream. + /* The Ogg bistream needs to be layered on top of the original bitstream. */ pFlac->bs.onRead = drflac__on_read_ogg; pFlac->bs.onSeek = drflac__on_seek_ogg; pFlac->bs.pUserData = (void*)pInternalOggbs; @@ -4760,7 +6449,7 @@ drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_p pFlac->firstFramePos = 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. + /* 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 if (init.container == drflac_container_ogg) { @@ -4770,32 +6459,34 @@ drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_p else #endif { - // If we have a seektable we need to load it now, making sure we move back to where we were previously. + /* If we have a seektable we need to load it now, making sure we move back to where we were previously. */ if (seektablePos != 0) { pFlac->seekpointCount = seektableSize / sizeof(*pFlac->pSeekpoints); pFlac->pSeekpoints = (drflac_seekpoint*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize); - // Seek to the seektable, then just read directly into our seektable buffer. + /* 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. - for (drflac_uint32 iSeekpoint = 0; iSeekpoint < pFlac->seekpointCount; ++iSeekpoint) { + /* 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); } } else { - // Failed to read the seektable. Pretend we don't have one. + /* Failed to read the seektable. Pretend we don't have one. */ pFlac->pSeekpoints = NULL; pFlac->seekpointCount = 0; } - // We need to seek back to where we were. If this fails it's a critical error. + /* 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); return NULL; } } else { - // Failed to seek to the seektable. Ominous sign, but for now we can just pretend we don't have one. + /* Failed to seek to the seektable. Ominous sign, but for now we can just pretend we don't have one. */ pFlac->pSeekpoints = NULL; pFlac->seekpointCount = 0; } @@ -4803,19 +6494,20 @@ 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 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 { - drflac_result result = drflac__decode_frame(pFlac); + drflac_result result = drflac__decode_flac_frame(pFlac); if (result == DRFLAC_SUCCESS) { break; } else { if (result == DRFLAC_CRC_MISMATCH) { - if (!drflac__read_next_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { DRFLAC_FREE(pFlac); return NULL; } @@ -4834,9 +6526,6 @@ drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_p #ifndef DR_FLAC_NO_STDIO -typedef void* drflac_file; - -#if defined(DR_FLAC_NO_WIN32_IO) || !defined(_WIN32) #include static size_t drflac__on_read_stdio(void* pUserData, void* bufferOut, size_t bytesToRead) @@ -4846,15 +6535,15 @@ 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 || (offset == 0 && origin == drflac_seek_origin_start)); + drflac_assert(offset >= 0); /* <-- Never seek backwards. */ return fseek((FILE*)pUserData, offset, (origin == drflac_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; } -static drflac_file drflac__open_file_handle(const char* filename) +static FILE* drflac__fopen(const char* filename) { FILE* pFile; -#ifdef _MSC_VER +#if defined(_MSC_VER) && _MSC_VER >= 1400 if (fopen_s(&pFile, filename, "rb") != 0) { return NULL; } @@ -4865,65 +6554,23 @@ static drflac_file drflac__open_file_handle(const char* filename) } #endif - return (drflac_file)pFile; + return pFile; } -static void drflac__close_file_handle(drflac_file file) -{ - fclose((FILE*)file); -} -#else -#include - -// This doesn't seem to be defined for VC6. -#ifndef INVALID_SET_FILE_POINTER -#define INVALID_SET_FILE_POINTER ((DWORD)-1) -#endif - -static size_t drflac__on_read_stdio(void* pUserData, void* bufferOut, size_t bytesToRead) -{ - drflac_assert(bytesToRead < 0xFFFFFFFF); // dr_flac will never request huge amounts of data at a time. This is a safe assertion. - - DWORD bytesRead; - ReadFile((HANDLE)pUserData, bufferOut, (DWORD)bytesToRead, &bytesRead, NULL); - - return (size_t)bytesRead; -} - -static drflac_bool32 drflac__on_seek_stdio(void* pUserData, int offset, drflac_seek_origin origin) -{ - drflac_assert(offset > 0 || (offset == 0 && origin == drflac_seek_origin_start)); - - return SetFilePointer((HANDLE)pUserData, offset, NULL, (origin == drflac_seek_origin_current) ? FILE_CURRENT : FILE_BEGIN) != INVALID_SET_FILE_POINTER; -} - -static drflac_file drflac__open_file_handle(const char* filename) -{ - HANDLE hFile = CreateFileA(filename, FILE_GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (hFile == INVALID_HANDLE_VALUE) { - return NULL; - } - - return (drflac_file)hFile; -} - -static void drflac__close_file_handle(drflac_file file) -{ - CloseHandle((HANDLE)file); -} -#endif - drflac* drflac_open_file(const char* filename) { - drflac_file file = drflac__open_file_handle(filename); - if (file == NULL) { + drflac* pFlac; + FILE* pFile; + + pFile = drflac__fopen(filename); + if (pFile == NULL) { return NULL; } - drflac* pFlac = drflac_open(drflac__on_read_stdio, drflac__on_seek_stdio, (void*)file); + pFlac = drflac_open(drflac__on_read_stdio, drflac__on_seek_stdio, (void*)pFile); if (pFlac == NULL) { - drflac__close_file_handle(file); + fclose(pFile); return NULL; } @@ -4932,28 +6579,33 @@ drflac* drflac_open_file(const char* filename) drflac* drflac_open_file_with_metadata(const char* filename, drflac_meta_proc onMeta, void* pUserData) { - drflac_file file = drflac__open_file_handle(filename); - if (file == NULL) { + drflac* pFlac; + FILE* pFile; + + pFile = drflac__fopen(filename); + if (pFile == NULL) { return NULL; } - drflac* pFlac = drflac_open_with_metadata_private(drflac__on_read_stdio, drflac__on_seek_stdio, onMeta, drflac_container_unknown, (void*)file, pUserData); + pFlac = drflac_open_with_metadata_private(drflac__on_read_stdio, drflac__on_seek_stdio, onMeta, drflac_container_unknown, (void*)pFile, pUserData); if (pFlac == NULL) { - drflac__close_file_handle(file); + fclose(pFile); return pFlac; } return pFlac; } -#endif //DR_FLAC_NO_STDIO +#endif /* DR_FLAC_NO_STDIO */ static size_t drflac__on_read_memory(void* pUserData, void* bufferOut, size_t bytesToRead) { drflac__memory_stream* memoryStream = (drflac__memory_stream*)pUserData; + size_t bytesRemaining; + drflac_assert(memoryStream != NULL); drflac_assert(memoryStream->dataSize >= memoryStream->currentReadPos); - size_t bytesRemaining = memoryStream->dataSize - memoryStream->currentReadPos; + bytesRemaining = memoryStream->dataSize - memoryStream->currentReadPos; if (bytesToRead > bytesRemaining) { bytesToRead = bytesRemaining; } @@ -4969,21 +6621,25 @@ static size_t drflac__on_read_memory(void* pUserData, void* bufferOut, size_t by static drflac_bool32 drflac__on_seek_memory(void* pUserData, int offset, drflac_seek_origin origin) { drflac__memory_stream* memoryStream = (drflac__memory_stream*)pUserData; + drflac_assert(memoryStream != NULL); - drflac_assert(offset > 0 || (offset == 0 && origin == drflac_seek_origin_start)); - drflac_assert(offset <= (drflac_int64)memoryStream->dataSize); + drflac_assert(offset >= 0); /* <-- Never seek backwards. */ + + if (offset > (drflac_int64)memoryStream->dataSize) { + return DRFLAC_FALSE; + } if (origin == drflac_seek_origin_current) { if (memoryStream->currentReadPos + offset <= memoryStream->dataSize) { memoryStream->currentReadPos += offset; } else { - memoryStream->currentReadPos = memoryStream->dataSize; // Trying to seek too far forward. + return DRFLAC_FALSE; /* Trying to seek too far forward. */ } } else { if ((drflac_uint32)offset <= memoryStream->dataSize) { memoryStream->currentReadPos = offset; } else { - memoryStream->currentReadPos = memoryStream->dataSize; // Trying to seek too far forward. + return DRFLAC_FALSE; /* Trying to seek too far forward. */ } } @@ -4993,17 +6649,19 @@ static drflac_bool32 drflac__on_seek_memory(void* pUserData, int offset, drflac_ drflac* drflac_open_memory(const void* data, size_t dataSize) { drflac__memory_stream memoryStream; + drflac* pFlac; + memoryStream.data = (const unsigned char*)data; memoryStream.dataSize = dataSize; memoryStream.currentReadPos = 0; - drflac* pFlac = drflac_open(drflac__on_read_memory, drflac__on_seek_memory, &memoryStream); + pFlac = drflac_open(drflac__on_read_memory, drflac__on_seek_memory, &memoryStream); if (pFlac == NULL) { return NULL; } pFlac->memoryStream = memoryStream; - // This is an awful hack... + /* This is an awful hack... */ #ifndef DR_FLAC_NO_OGG if (pFlac->container == drflac_container_ogg) { @@ -5022,17 +6680,19 @@ drflac* drflac_open_memory(const void* data, size_t dataSize) drflac* drflac_open_memory_with_metadata(const void* data, size_t dataSize, drflac_meta_proc onMeta, void* pUserData) { drflac__memory_stream memoryStream; + drflac* pFlac; + memoryStream.data = (const unsigned char*)data; memoryStream.dataSize = dataSize; memoryStream.currentReadPos = 0; - drflac* 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); if (pFlac == NULL) { return NULL; } pFlac->memoryStream = memoryStream; - // This is an awful hack... + /* This is an awful hack... */ #ifndef DR_FLAC_NO_OGG if (pFlac->container == drflac_container_ogg) { @@ -5075,19 +6735,22 @@ void drflac_close(drflac* pFlac) } #ifndef DR_FLAC_NO_STDIO - // If we opened the file with drflac_open_file() we will want to close the file handle. We can know whether or not drflac_open_file() - // was used by looking at the callbacks. + /* + If we opened the file with drflac_open_file() we will want to close the file handle. We can know whether or not drflac_open_file() + was used by looking at the callbacks. + */ if (pFlac->bs.onRead == drflac__on_read_stdio) { - drflac__close_file_handle((drflac_file)pFlac->bs.pUserData); + fclose((FILE*)pFlac->bs.pUserData); } #ifndef DR_FLAC_NO_OGG - // Need to clean up Ogg streams a bit differently due to the way the bit streaming is chained. + /* 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_assert(pFlac->bs.onRead == drflac__on_read_ogg); drflac_oggbs* oggbs = (drflac_oggbs*)pFlac->_oggbs; + drflac_assert(pFlac->bs.onRead == drflac__on_read_ogg); + if (oggbs->onRead == drflac__on_read_stdio) { - drflac__close_file_handle((drflac_file)oggbs->pUserData); + fclose((FILE*)oggbs->pUserData); } } #endif @@ -5099,30 +6762,29 @@ void drflac_close(drflac* pFlac) drflac_uint64 drflac__read_s32__misaligned(drflac* pFlac, drflac_uint64 samplesToRead, drflac_int32* bufferOut) { unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); + drflac_uint64 samplesRead; - // We should never be calling this when the number of samples to read is >= the sample count. + /* 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); - - drflac_uint64 samplesRead = 0; + 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; + switch (pFlac->currentFrame.header.channelAssignment) { case DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE: { if (channelIndex == 0) { - decodedSample = pFlac->currentFrame.subframes[channelIndex].pDecodedSamples[nextSampleInFrame]; + decodedSample = (int)((drflac_uint32)pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex + 0].wastedBitsPerSample); } else { - int side = pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame]; - int left = pFlac->currentFrame.subframes[channelIndex - 1].pDecodedSamples[nextSampleInFrame]; + 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; @@ -5130,11 +6792,11 @@ drflac_uint64 drflac__read_s32__misaligned(drflac* pFlac, drflac_uint64 samplesT case DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE: { if (channelIndex == 0) { - int side = pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame]; - int right = pFlac->currentFrame.subframes[channelIndex + 1].pDecodedSamples[nextSampleInFrame]; + 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 = pFlac->currentFrame.subframes[channelIndex].pDecodedSamples[nextSampleInFrame]; + decodedSample = (int)((drflac_uint32)pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex + 0].wastedBitsPerSample); } } break; @@ -5143,14 +6805,14 @@ drflac_uint64 drflac__read_s32__misaligned(drflac* pFlac, drflac_uint64 samplesT int mid; int side; if (channelIndex == 0) { - mid = pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame]; - side = pFlac->currentFrame.subframes[channelIndex + 1].pDecodedSamples[nextSampleInFrame]; + 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); mid = (((unsigned int)mid) << 1) | (side & 0x01); decodedSample = (mid + side) >> 1; } else { - mid = pFlac->currentFrame.subframes[channelIndex - 1].pDecodedSamples[nextSampleInFrame]; - side = pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame]; + 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; @@ -5160,12 +6822,11 @@ drflac_uint64 drflac__read_s32__misaligned(drflac* pFlac, drflac_uint64 samplesT case DRFLAC_CHANNEL_ASSIGNMENT_INDEPENDENT: default: { - decodedSample = pFlac->currentFrame.subframes[channelIndex].pDecodedSamples[nextSampleInFrame]; + decodedSample = (int)((drflac_uint32)pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex + 0].wastedBitsPerSample); } break; } - - decodedSample <<= ((32 - pFlac->bitsPerSample) + pFlac->currentFrame.subframes[channelIndex].wastedBitsPerSample); + decodedSample = (int)((drflac_uint32)decodedSample << (32 - pFlac->bitsPerSample)); if (bufferOut) { *bufferOut++ = decodedSample; @@ -5179,34 +6840,11 @@ drflac_uint64 drflac__read_s32__misaligned(drflac* pFlac, drflac_uint64 samplesT return samplesRead; } -drflac_uint64 drflac__seek_forward_by_samples(drflac* pFlac, drflac_uint64 samplesToRead) -{ - drflac_uint64 samplesRead = 0; - while (samplesToRead > 0) { - if (pFlac->currentFrame.samplesRemaining == 0) { - if (!drflac__read_and_decode_next_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; - } else { - samplesRead += pFlac->currentFrame.samplesRemaining; - samplesToRead -= pFlac->currentFrame.samplesRemaining; - pFlac->currentFrame.samplesRemaining = 0; - } - } - } - - pFlac->currentSample += samplesRead; - return samplesRead; -} - drflac_uint64 drflac_read_s32(drflac* pFlac, drflac_uint64 samplesToRead, drflac_int32* bufferOut) { - // Note that is allowed to be null, in which case this will act like a seek. + drflac_uint64 samplesRead; + + /* Note that is allowed to be null, in which case this will act like a seek. */ if (pFlac == NULL || samplesToRead == 0) { return 0; } @@ -5215,22 +6853,24 @@ drflac_uint64 drflac_read_s32(drflac* pFlac, drflac_uint64 samplesToRead, drflac return drflac__seek_forward_by_samples(pFlac, samplesToRead); } - - drflac_uint64 samplesRead = 0; + samplesRead = 0; while (samplesToRead > 0) { - // If we've run out of samples in this frame, go to the next. + /* If we've run out of samples in this frame, go to the next. */ if (pFlac->currentFrame.samplesRemaining == 0) { - if (!drflac__read_and_decode_next_frame(pFlac)) { - break; // Couldn't read the next frame, so just break from the loop and return. + 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. - + /* 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; + if (misalignedSampleCount > 0) { drflac_uint64 misalignedSamplesRead = drflac__read_s32__misaligned(pFlac, misalignedSampleCount, bufferOut); samplesRead += misalignedSamplesRead; @@ -5241,87 +6881,95 @@ drflac_uint64 drflac_read_s32(drflac* pFlac, drflac_uint64 samplesToRead, drflac } - drflac_uint64 alignedSampleCountPerChannel = samplesToRead / channelCount; + alignedSampleCountPerChannel = samplesToRead / channelCount; if (alignedSampleCountPerChannel > pFlac->currentFrame.samplesRemaining / channelCount) { alignedSampleCountPerChannel = pFlac->currentFrame.samplesRemaining / channelCount; } - drflac_uint64 firstAlignedSampleInFrame = samplesReadFromFrameSoFar / channelCount; - unsigned int unusedBitsPerSample = 32 - pFlac->bitsPerSample; + firstAlignedSampleInFrame = samplesReadFromFrameSoFar / channelCount; + unusedBitsPerSample = 32 - pFlac->bitsPerSample; switch (pFlac->currentFrame.header.channelAssignment) { case DRFLAC_CHANNEL_ASSIGNMENT_LEFT_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 (drflac_uint64 i = 0; i < alignedSampleCountPerChannel; ++i) { - int left = pDecodedSamples0[i]; - int side = pDecodedSamples1[i]; + 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 << (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample); - bufferOut[i*2+1] = right << (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample); + 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 (drflac_uint64 i = 0; i < alignedSampleCountPerChannel; ++i) { - int side = pDecodedSamples0[i]; - int right = pDecodedSamples1[i]; + 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 << (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample); - bufferOut[i*2+1] = right << (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample); + 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 (drflac_uint64 i = 0; i < alignedSampleCountPerChannel; ++i) { - int side = pDecodedSamples1[i]; - int mid = (((drflac_uint32)pDecodedSamples0[i]) << 1) | (side & 0x01); + 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] = ((mid + side) >> 1) << (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample); - bufferOut[i*2+1] = ((mid - side) >> 1) << (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample); + 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 + if (pFlac->currentFrame.header.channelAssignment == 1) /* 1 = Stereo */ { - // Stereo optimized inner loop unroll. + /* 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; - for (drflac_uint64 i = 0; i < alignedSampleCountPerChannel; ++i) { - bufferOut[i*2+0] = pDecodedSamples0[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample); - bufferOut[i*2+1] = pDecodedSamples1[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample); + 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 { - // Generic interleaving. - for (drflac_uint64 i = 0; i < alignedSampleCountPerChannel; ++i) { - for (unsigned int j = 0; j < channelCount; ++j) { - bufferOut[(i*channelCount)+j] = (pFlac->currentFrame.subframes[j].pDecodedSamples[firstAlignedSampleInFrame + i]) << (unusedBitsPerSample + pFlac->currentFrame.subframes[j].wastedBitsPerSample); + /* 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_uint64 alignedSamplesRead = alignedSampleCountPerChannel * channelCount; + alignedSamplesRead = alignedSampleCountPerChannel * channelCount; samplesRead += alignedSamplesRead; samplesReadFromFrameSoFar += alignedSamplesRead; bufferOut += alignedSamplesRead; @@ -5330,7 +6978,7 @@ drflac_uint64 drflac_read_s32(drflac* pFlac, drflac_uint64 samplesToRead, drflac pFlac->currentFrame.samplesRemaining -= (unsigned int)alignedSamplesRead; - // At this point we may still have some excess samples left to read. + /* 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) { @@ -5351,20 +6999,47 @@ drflac_uint64 drflac_read_s32(drflac* pFlac, drflac_uint64 samplesToRead, drflac return samplesRead; } +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. + /* 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. + break; /* Reached the end. */ } - // s32 -> s16 - for (drflac_uint64 i = 0; i < samplesJustRead; ++i) { + /* s32 -> s16 */ + for (i = 0; i < samplesJustRead; ++i) { pBufferOut[i] = (drflac_int16)(samples32[i] >> 16); } @@ -5373,23 +7048,69 @@ drflac_uint64 drflac_read_s16(drflac* pFlac, drflac_uint64 samplesToRead, drflac 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. + /* 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. + break; /* Reached the end. */ } - // s32 -> f32 - for (drflac_uint64 i = 0; i < samplesJustRead; ++i) { + /* s32 -> f32 */ + for (i = 0; i < samplesJustRead; ++i) { pBufferOut[i] = (float)(samples32[i] / 2147483648.0); } @@ -5398,17 +7119,717 @@ drflac_uint64 drflac_read_f32(drflac* pFlac, drflac_uint64 samplesToRead, float* 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) +{ + 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; + + pOutputSamples[i*2+0] = (float)(left / 2147483648.0); + pOutputSamples[i*2+1] = (float)(right / 2147483648.0); + } +} +#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) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + + 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_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_int32 right0 = left0 - side0; + drflac_int32 right1 = left1 - side1; + drflac_int32 right2 = left2 - side2; + drflac_int32 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; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + int left = pInputSamples0[i] << shift0; + int side = pInputSamples1[i] << shift1; + int right = left - side; + + pOutputSamples[i*2+0] = (float)(left * factor); + pOutputSamples[i*2+1] = (float)(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_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 frameCount4; + __m128 factor; + int shift0; + int shift1; + drflac_uint64 i; + + 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; + + 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(inputSample0, shift0); + __m128i side = _mm_slli_epi32(inputSample1, 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]; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + int left = pInputSamples0[i] << shift0; + int side = pInputSamples1[i] << shift1; + int right = left - side; + + pOutputSamples[i*2+0] = (float)(left / 8388608.0f); + pOutputSamples[i*2+1] = (float)(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_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_left_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_f32__decode_left_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_f32__decode_left_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#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) +{ + 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; + + pOutputSamples[i*2+0] = (float)(left / 2147483648.0); + pOutputSamples[i*2+1] = (float)(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) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + + 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_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_int32 left0 = right0 + side0; + drflac_int32 left1 = right1 + side1; + drflac_int32 left2 = right2 + side2; + drflac_int32 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; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + int side = pInputSamples0[i] << shift0; + int right = pInputSamples1[i] << shift1; + int left = right + side; + + pOutputSamples[i*2+0] = (float)(left * factor); + pOutputSamples[i*2+1] = (float)(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) +{ + drflac_uint64 frameCount4; + __m128 factor; + int shift0; + int shift1; + drflac_uint64 i; + + 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; + + 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 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]; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + int side = pInputSamples0[i] << shift0; + int right = pInputSamples1[i] << shift1; + int left = right + side; + + pOutputSamples[i*2+0] = (float)(left / 8388608.0f); + pOutputSamples[i*2+1] = (float)(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_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_right_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_f32__decode_right_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_f32__decode_right_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#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) +{ + 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); + + pOutputSamples[i*2+0] = (float)((((mid + side) >> 1) << (unusedBitsPerSample)) / 2147483648.0); + pOutputSamples[i*2+1] = (float)((((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) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + + 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; + + 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; + + 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; + + 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); + + 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] = (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); + } + } else { + for (i = 0; i < frameCount4; ++i) { + int temp0L; + int temp1L; + int temp2L; + int temp3L; + int temp0R; + int temp1R; + int temp2R; + int 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; + + 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; + + 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); + + temp0L = ((mid0 + side0) >> 1); + temp1L = ((mid1 + side1) >> 1); + temp2L = ((mid2 + side2) >> 1); + temp3L = ((mid3 + side3) >> 1); + + temp0R = ((mid0 - side0) >> 1); + temp1R = ((mid1 - side1) >> 1); + temp2R = ((mid2 - side2) >> 1); + temp3R = ((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); + } + } + + 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); + + pOutputSamples[i*2+0] = (float)((((mid + side) >> 1) << unusedBitsPerSample) * factor); + pOutputSamples[i*2+1] = (float)((((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) +{ + drflac_uint64 i; + drflac_uint64 frameCount4; + float factor; + int shift; + __m128 factor128; + + drflac_assert(pFlac->bitsPerSample <= 24); + + frameCount4 = frameCount >> 2; + + factor = 1.0f / 8388608.0f; + factor128 = _mm_set1_ps(1.0f / 8388608.0f); + + shift = unusedBitsPerSample - 8; + if (shift == 0) { + for (i = 0; i < frameCount4; ++i) { + __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); + + __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))); + + 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))); + + 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]; + } + + 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); + + pOutputSamples[i*2+0] = (float)(((mid + side) >> 1) * factor); + pOutputSamples[i*2+1] = (float)(((mid - side) >> 1) * factor); + } + } else { + for (i = 0; i < frameCount4; ++i) { + __m128i inputSample0; + __m128i inputSample1; + __m128i mid; + __m128i side; + __m128i tempL; + __m128i tempR; + __m128 leftf; + __m128 rightf; + + inputSample0 = _mm_loadu_si128((const __m128i*)pInputSamples0 + i); + inputSample1 = _mm_loadu_si128((const __m128i*)pInputSamples1 + i); + + 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))); + + 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); + + 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]; + } + + 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); + + pOutputSamples[i*2+0] = (float)((((mid + side) >> 1) << shift) * factor); + pOutputSamples[i*2+1] = (float)((((mid - side) >> 1) << shift) * factor); + } + } +} +#endif + + +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) +{ +#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 +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_f32__decode_mid_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_f32__decode_mid_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + +#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) +{ + 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); + } +} +#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) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + + 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; + + 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; + + 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); + } + + 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); + } +} + +#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) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + + 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; + + 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 i32L = _mm_slli_epi32(inputSample0, shift0); + __m128i i32R = _mm_slli_epi32(inputSample1, shift1); + + __m128 f32L = _mm_mul_ps(_mm_cvtepi32_ps(i32L), factor128); + __m128 f32R = _mm_mul_ps(_mm_cvtepi32_ps(i32R), 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]; + } + + 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); + } +} +#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_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_independent_stereo__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_f32__decode_independent_stereo__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_f32__decode_independent_stereo__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + +drflac_uint64 drflac_read_pcm_frames_f32(drflac* pFlac, drflac_uint64 framesToRead, float* pBufferOut) +{ + drflac_uint64 framesRead; + + if (pFlac == NULL || framesToRead == 0) { + return 0; + } + + if (pBufferOut == NULL) { + return drflac__seek_forward_by_pcm_frames(pFlac, framesToRead); + } + + 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 (!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; + drflac_uint64 frameCountThisIteration = framesToRead; + drflac_uint64 samplesReadThisIteration; + + if (frameCountThisIteration > pFlac->currentFrame.samplesRemaining / channelCount) { + frameCountThisIteration = pFlac->currentFrame.samplesRemaining / channelCount; + } + + if (channelCount == 2) { + const drflac_int32* pDecodedSamples0 = pFlac->currentFrame.subframes[0].pDecodedSamples + iFirstPCMFrame; + const drflac_int32* pDecodedSamples1 = pFlac->currentFrame.subframes[1].pDecodedSamples + iFirstPCMFrame; + + switch (pFlac->currentFrame.header.channelAssignment) + { + case DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE: + { + drflac_read_pcm_frames_f32__decode_left_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE: + { + 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); + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_INDEPENDENT: + default: + { + drflac_read_pcm_frames_f32__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] = (float)(((pFlac->currentFrame.subframes[j].pDecodedSamples[iFirstPCMFrame + i]) << (unusedBitsPerSample + pFlac->currentFrame.subframes[j].wastedBitsPerSample)) / 2147483648.0); + } + } + } + + samplesReadThisIteration = frameCountThisIteration * channelCount; + framesRead += frameCountThisIteration; + framesReadFromPacketSoFar += frameCountThisIteration; + pBufferOut += samplesReadThisIteration; + framesToRead -= frameCountThisIteration; + pFlac->currentSample += samplesReadThisIteration; + pFlac->currentFrame.samplesRemaining -= (unsigned int)samplesReadThisIteration; + } + } + + return framesRead; +} + drflac_bool32 drflac_seek_to_sample(drflac* pFlac, drflac_uint64 sampleIndex) { 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 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; } @@ -5419,14 +7840,14 @@ drflac_bool32 drflac_seek_to_sample(drflac* pFlac, drflac_uint64 sampleIndex) } else { drflac_bool32 wasSuccessful = DRFLAC_FALSE; - // Clamp the sample to the end. + /* 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 the target sample and the current sample are in the same frame we just move the position forward. */ if (sampleIndex > pFlac->currentSample) { - // Forward. + /* Forward. */ drflac_uint32 offset = (drflac_uint32)(sampleIndex - pFlac->currentSample); if (pFlac->currentFrame.samplesRemaining > offset) { pFlac->currentFrame.samplesRemaining -= offset; @@ -5434,7 +7855,7 @@ drflac_bool32 drflac_seek_to_sample(drflac* pFlac, drflac_uint64 sampleIndex) return DRFLAC_TRUE; } } else { - // Backward. + /* 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); @@ -5445,17 +7866,19 @@ drflac_bool32 drflac_seek_to_sample(drflac* pFlac, drflac_uint64 sampleIndex) } } - // 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 + /* + 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 +#endif { - // First try seeking via the seek table. If this fails, fall back to a brute force seek which is much slower. + /* 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); @@ -5467,42 +7890,118 @@ drflac_bool32 drflac_seek_to_sample(drflac* pFlac, drflac_uint64 sampleIndex) } } +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) { + return DRFLAC_FALSE; + } + + if (pcmFrameIndex == 0) { + pFlac->currentSample = 0; + return drflac__seek_to_first_frame(pFlac); + } else { + drflac_bool32 wasSuccessful = DRFLAC_FALSE; + + /* Clamp the sample to the end. */ + if (pcmFrameIndex >= pFlac->totalPCMFrameCount) { + pcmFrameIndex = pFlac->totalPCMFrameCount - 1; + } + + /* 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) { + /* 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; + 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; + 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, pcmFrameIndex*pFlac->channels); + } + 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); + } + } + + pFlac->currentSample = pcmFrameIndex*pFlac->channels; + return wasSuccessful; + } +} -//// High Level APIs //// -// I couldn't figure out where SIZE_MAX was defined for VC6. If anybody knows, let me know. -#if defined(_MSC_VER) && _MSC_VER <= 1200 -#ifdef DRFLAC_64BIT -#define SIZE_MAX ((drflac_uint64)0xFFFFFFFFFFFFFFFF) +/* High Level APIs */ + +#if defined(SIZE_MAX) + #define DRFLAC_SIZE_MAX SIZE_MAX #else -#define SIZE_MAX 0xFFFFFFFF -#endif + #if defined(DRFLAC_64BIT) + #define DRFLAC_SIZE_MAX ((drflac_uint64)0xFFFFFFFFFFFFFFFF) + #else + #define DRFLAC_SIZE_MAX 0xFFFFFFFF + #endif #endif -// Using a macro as the definition of the drflac__full_decode_and_close_*() API family. Sue me. -#define DRFLAC_DEFINE_FULL_DECODE_AND_CLOSE(extension, type) \ -static type* drflac__full_decode_and_close_ ## extension (drflac* pFlac, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalSampleCountOut)\ + +/* Using a macro as the definition of the drflac__full_decode_and_close_*() API family. Sue me. */ +#define DRFLAC_DEFINE_FULL_READ_AND_CLOSE(extension, type) \ +static type* drflac__full_read_and_close_ ## extension (drflac* pFlac, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut)\ { \ + type* pSampleData = NULL; \ + drflac_uint64 totalPCMFrameCount; \ + \ drflac_assert(pFlac != NULL); \ \ - type* pSampleData = NULL; \ - drflac_uint64 totalSampleCount = pFlac->totalSampleCount; \ + totalPCMFrameCount = pFlac->totalPCMFrameCount; \ \ - if (totalSampleCount == 0) { \ + if (totalPCMFrameCount == 0) { \ type buffer[4096]; \ - \ + drflac_uint64 pcmFramesRead; \ size_t sampleDataBufferSize = sizeof(buffer); \ + \ pSampleData = (type*)DRFLAC_MALLOC(sampleDataBufferSize); \ if (pSampleData == NULL) { \ goto on_error; \ } \ \ - drflac_uint64 samplesRead; \ - while ((samplesRead = (drflac_uint64)drflac_read_##extension(pFlac, sizeof(buffer)/sizeof(buffer[0]), buffer)) > 0) { \ - if (((totalSampleCount + samplesRead) * sizeof(type)) > sampleDataBufferSize) { \ + 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; \ + \ sampleDataBufferSize *= 2; \ - type* pNewSampleData = (type*)DRFLAC_REALLOC(pSampleData, sampleDataBufferSize); \ + pNewSampleData = (type*)DRFLAC_REALLOC(pSampleData, sampleDataBufferSize); \ if (pNewSampleData == NULL) { \ DRFLAC_FREE(pSampleData); \ goto on_error; \ @@ -5511,16 +8010,16 @@ static type* drflac__full_decode_and_close_ ## extension (drflac* pFlac, unsigne pSampleData = pNewSampleData; \ } \ \ - drflac_copy_memory(pSampleData + totalSampleCount, buffer, (size_t)(samplesRead*sizeof(type))); \ - totalSampleCount += samplesRead; \ + 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 + totalSampleCount, (size_t)(sampleDataBufferSize - totalSampleCount*sizeof(type))); \ + drflac_zero_memory(pSampleData + (totalPCMFrameCount*pFlac->channels), (size_t)(sampleDataBufferSize - totalPCMFrameCount*pFlac->channels*sizeof(type))); \ } else { \ - drflac_uint64 dataSize = totalSampleCount * sizeof(type); \ - if (dataSize > SIZE_MAX) { \ + drflac_uint64 dataSize = totalPCMFrameCount*pFlac->channels*sizeof(type); \ + if (dataSize > DRFLAC_SIZE_MAX) { \ goto on_error; /* The decoded data is too big. */ \ } \ \ @@ -5529,12 +8028,12 @@ static type* drflac__full_decode_and_close_ ## extension (drflac* pFlac, unsigne goto on_error; \ } \ \ - totalSampleCount = drflac_read_##extension(pFlac, pFlac->totalSampleCount, pSampleData); \ + totalPCMFrameCount = drflac_read_pcm_frames_##extension(pFlac, pFlac->totalPCMFrameCount, pSampleData); \ } \ \ if (sampleRateOut) *sampleRateOut = pFlac->sampleRate; \ if (channelsOut) *channelsOut = pFlac->channels; \ - if (totalSampleCountOut) *totalSampleCountOut = totalSampleCount; \ + if (totalPCMFrameCountOut) *totalPCMFrameCountOut = totalPCMFrameCount; \ \ drflac_close(pFlac); \ return pSampleData; \ @@ -5544,141 +8043,533 @@ on_error: return NULL; \ } -DRFLAC_DEFINE_FULL_DECODE_AND_CLOSE(s32, drflac_int32) -DRFLAC_DEFINE_FULL_DECODE_AND_CLOSE(s16, drflac_int16) -DRFLAC_DEFINE_FULL_DECODE_AND_CLOSE(f32, float) +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_decode_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount) +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) { - // Safety. - if (sampleRate) *sampleRate = 0; - if (channels) *channels = 0; - if (totalSampleCount) *totalSampleCount = 0; + drflac* pFlac; - drflac* pFlac = drflac_open(onRead, onSeek, pUserData); + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalPCMFrameCountOut) { + *totalPCMFrameCountOut = 0; + } + + pFlac = drflac_open(onRead, onSeek, pUserData); if (pFlac == NULL) { return NULL; } - return drflac__full_decode_and_close_s32(pFlac, channels, sampleRate, totalSampleCount); + return drflac__full_read_and_close_s32(pFlac, channelsOut, sampleRateOut, totalPCMFrameCountOut); } -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) +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) { - // Safety. - if (sampleRate) *sampleRate = 0; - if (channels) *channels = 0; - if (totalSampleCount) *totalSampleCount = 0; + unsigned int channels; + unsigned int sampleRate; + drflac_uint64 totalPCMFrameCount; + drflac_int32* pResult; - drflac* pFlac = drflac_open(onRead, onSeek, pUserData); - if (pFlac == NULL) { + 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; } - return drflac__full_decode_and_close_s16(pFlac, channels, sampleRate, totalSampleCount); + if (channelsOut) { + *channelsOut = channels; + } + if (sampleRateOut) { + *sampleRateOut = sampleRate; + } + if (totalSampleCountOut) { + *totalSampleCountOut = totalPCMFrameCount * channels; + } + + return pResult; } -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) -{ - // Safety. - if (sampleRate) *sampleRate = 0; - if (channels) *channels = 0; - if (totalSampleCount) *totalSampleCount = 0; - drflac* pFlac = drflac_open(onRead, onSeek, pUserData); + +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* pFlac; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalPCMFrameCountOut) { + *totalPCMFrameCountOut = 0; + } + + pFlac = drflac_open(onRead, onSeek, pUserData); if (pFlac == NULL) { return NULL; } - return drflac__full_decode_and_close_f32(pFlac, channels, sampleRate, totalSampleCount); + 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* pFlac; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalPCMFrameCountOut) { + *totalPCMFrameCountOut = 0; + } + + pFlac = drflac_open(onRead, onSeek, pUserData); + if (pFlac == NULL) { + return NULL; + } + + 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_and_decode_file_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount) +drflac_int32* drflac_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount) { - if (sampleRate) *sampleRate = 0; - if (channels) *channels = 0; - if (totalSampleCount) *totalSampleCount = 0; + drflac* pFlac; - drflac* pFlac = drflac_open_file(filename); + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalPCMFrameCount) { + *totalPCMFrameCount = 0; + } + + pFlac = drflac_open_file(filename); if (pFlac == NULL) { return NULL; } - return drflac__full_decode_and_close_s32(pFlac, channels, sampleRate, totalSampleCount); + return drflac__full_read_and_close_s32(pFlac, channels, sampleRate, totalPCMFrameCount); } -drflac_int16* drflac_open_and_decode_file_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount) +drflac_int32* drflac_open_and_decode_file_s32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalSampleCountOut) { - if (sampleRate) *sampleRate = 0; - if (channels) *channels = 0; - if (totalSampleCount) *totalSampleCount = 0; + unsigned int channels; + unsigned int sampleRate; + drflac_uint64 totalPCMFrameCount; + drflac_int32* pResult; - drflac* pFlac = drflac_open_file(filename); - if (pFlac == NULL) { + 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; } - return drflac__full_decode_and_close_s16(pFlac, channels, sampleRate, totalSampleCount); + if (channelsOut) { + *channelsOut = channels; + } + if (sampleRateOut) { + *sampleRateOut = sampleRate; + } + if (totalSampleCountOut) { + *totalSampleCountOut = totalPCMFrameCount * channels; + } + + return pResult; } -float* drflac_open_and_decode_file_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount) -{ - if (sampleRate) *sampleRate = 0; - if (channels) *channels = 0; - if (totalSampleCount) *totalSampleCount = 0; - drflac* pFlac = drflac_open_file(filename); +drflac_int16* drflac_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount) +{ + drflac* pFlac; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalPCMFrameCount) { + *totalPCMFrameCount = 0; + } + + pFlac = drflac_open_file(filename); if (pFlac == NULL) { return NULL; } - return drflac__full_decode_and_close_f32(pFlac, channels, sampleRate, totalSampleCount); + 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* pFlac; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalPCMFrameCount) { + *totalPCMFrameCount = 0; + } + + pFlac = drflac_open_file(filename); + 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_and_decode_memory_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount) +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) { - if (sampleRate) *sampleRate = 0; - if (channels) *channels = 0; - if (totalSampleCount) *totalSampleCount = 0; + drflac* pFlac; - drflac* pFlac = drflac_open_memory(data, dataSize); + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalPCMFrameCount) { + *totalPCMFrameCount = 0; + } + + pFlac = drflac_open_memory(data, dataSize); if (pFlac == NULL) { return NULL; } - return drflac__full_decode_and_close_s32(pFlac, channels, sampleRate, totalSampleCount); + return drflac__full_read_and_close_s32(pFlac, channels, sampleRate, totalPCMFrameCount); } -drflac_int16* drflac_open_and_decode_memory_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount) +drflac_int32* drflac_open_and_decode_memory_s32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalSampleCountOut) { - if (sampleRate) *sampleRate = 0; - if (channels) *channels = 0; - if (totalSampleCount) *totalSampleCount = 0; + unsigned int channels; + unsigned int sampleRate; + drflac_uint64 totalPCMFrameCount; + drflac_int32* pResult; - drflac* pFlac = drflac_open_memory(data, dataSize); + 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* pFlac; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalPCMFrameCount) { + *totalPCMFrameCount = 0; + } + + pFlac = drflac_open_memory(data, dataSize); if (pFlac == NULL) { return NULL; } - return drflac__full_decode_and_close_s16(pFlac, channels, sampleRate, totalSampleCount); + return drflac__full_read_and_close_s16(pFlac, channels, sampleRate, totalPCMFrameCount); } -float* drflac_open_and_decode_memory_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount) +drflac_int16* drflac_open_and_decode_memory_s16(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalSampleCountOut) { - if (sampleRate) *sampleRate = 0; - if (channels) *channels = 0; - if (totalSampleCount) *totalSampleCount = 0; + unsigned int channels; + unsigned int sampleRate; + drflac_uint64 totalPCMFrameCount; + drflac_int16* pResult; - drflac* pFlac = drflac_open_memory(data, dataSize); + 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* pFlac; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalPCMFrameCount) { + *totalPCMFrameCount = 0; + } + + pFlac = drflac_open_memory(data, dataSize); if (pFlac == NULL) { return NULL; } - return drflac__full_decode_and_close_f32(pFlac, channels, sampleRate, totalSampleCount); + 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) +{ + 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_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); @@ -5687,208 +8578,356 @@ void drflac_free(void* pSampleDataReturnedByOpenAndDecode) -void drflac_init_vorbis_comment_iterator(drflac_vorbis_comment_iterator* pIter, drflac_uint32 commentCount, const char* pComments) +void drflac_init_vorbis_comment_iterator(drflac_vorbis_comment_iterator* pIter, drflac_uint32 commentCount, const void* pComments) { if (pIter == NULL) { return; } pIter->countRemaining = commentCount; - pIter->pRunningData = pComments; + pIter->pRunningData = (const char*)pComments; } const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator* pIter, drflac_uint32* pCommentLengthOut) { - // Safety. - if (pCommentLengthOut) *pCommentLengthOut = 0; + drflac_int32 length; + const char* pComment; + + /* Safety. */ + if (pCommentLengthOut) { + *pCommentLengthOut = 0; + } if (pIter == NULL || pIter->countRemaining == 0 || pIter->pRunningData == NULL) { return NULL; } - drflac_uint32 length = drflac__le2host_32(*(drflac_uint32*)pIter->pRunningData); + length = drflac__le2host_32(*(const drflac_uint32*)pIter->pRunningData); pIter->pRunningData += 4; - const char* pComment = pIter->pRunningData; + pComment = pIter->pRunningData; pIter->pRunningData += length; pIter->countRemaining -= 1; - if (pCommentLengthOut) *pCommentLengthOut = length; + if (pCommentLengthOut) { + *pCommentLengthOut = length; + } + return pComment; } -#endif //DR_FLAC_IMPLEMENTATION -// REVISION HISTORY -// -// v0.9.7 - 2018-07-05 -// - Fix a warning. -// -// v0.9.6 - 2018-06-29 -// - Fix some typos. -// -// v0.9.5 - 2018-06-23 -// - Fix some warnings. -// -// v0.9.4 - 2018-06-14 -// - Optimizations to seeking. -// - Clean up. -// -// v0.9.3 - 2018-05-22 -// - Bug fix. -// -// v0.9.2 - 2018-05-12 -// - Fix a compilation error due to a missing break statement. -// -// v0.9.1 - 2018-04-29 -// - Fix compilation error with Clang. -// -// v0.9 - 2018-04-24 -// - Fix Clang build. -// - Start using major.minor.revision versioning. -// -// v0.8g - 2018-04-19 -// - Fix build on non-x86/x64 architectures. -// -// v0.8f - 2018-02-02 -// - Stop pretending to support changing rate/channels mid stream. -// -// v0.8e - 2018-02-01 -// - Fix a crash when the block size of a frame is larger than the maximum block size defined by the FLAC stream. -// - Fix a crash the the Rice partition order is invalid. -// -// v0.8d - 2017-09-22 -// - Add support for decoding streams with ID3 tags. ID3 tags are just skipped. -// -// v0.8c - 2017-09-07 -// - Fix warning on non-x86/x64 architectures. -// -// v0.8b - 2017-08-19 -// - Fix build on non-x86/x64 architectures. -// -// v0.8a - 2017-08-13 -// - A small optimization for the Clang build. -// -// v0.8 - 2017-08-12 -// - API CHANGE: Rename dr_* types to drflac_*. -// - Optimizations. This brings dr_flac back to about the same class of efficiency as the reference implementation. -// - Add support for custom implementations of malloc(), realloc(), etc. -// - Add CRC checking to Ogg encapsulated streams. -// - Fix VC++ 6 build. This is only for the C++ compiler. The C compiler is not currently supported. -// - Bug fixes. -// -// v0.7 - 2017-07-23 -// - Add support for opening a stream without a header block. To do this, use drflac_open_relaxed() / drflac_open_with_metadata_relaxed(). -// -// v0.6 - 2017-07-22 -// - Add support for recovering from invalid frames. With this change, dr_flac will simply skip over invalid frames as if they -// never existed. Frames are checked against their sync code, the CRC-8 of the frame header and the CRC-16 of the whole frame. -// -// v0.5 - 2017-07-16 -// - Fix typos. -// - Change drflac_bool* types to unsigned. -// - Add CRC checking. This makes dr_flac slower, but can be disabled with #define DR_FLAC_NO_CRC. -// -// v0.4f - 2017-03-10 -// - Fix a couple of bugs with the bitstreaming code. -// -// v0.4e - 2017-02-17 -// - Fix some warnings. -// -// v0.4d - 2016-12-26 -// - Add support for 32-bit floating-point PCM decoding. -// - Use drflac_int*/drflac_uint* sized types to improve compiler support. -// - Minor improvements to documentation. -// -// v0.4c - 2016-12-26 -// - Add support for signed 16-bit integer PCM decoding. -// -// v0.4b - 2016-10-23 -// - A minor change to drflac_bool8 and drflac_bool32 types. -// -// v0.4a - 2016-10-11 -// - Rename drBool32 to drflac_bool32 for styling consistency. -// -// v0.4 - 2016-09-29 -// - API/ABI CHANGE: Use fixed size 32-bit booleans instead of the built-in bool type. -// - API CHANGE: Rename drflac_open_and_decode*() to drflac_open_and_decode*_s32(). -// - API CHANGE: Swap the order of "channels" and "sampleRate" parameters in drflac_open_and_decode*(). Rationale for this is to -// keep it consistent with drflac_audio. -// -// v0.3f - 2016-09-21 -// - Fix a warning with GCC. -// -// v0.3e - 2016-09-18 -// - Fixed a bug where GCC 4.3+ was not getting properly identified. -// - Fixed a few typos. -// - Changed date formats to ISO 8601 (YYYY-MM-DD). -// -// v0.3d - 2016-06-11 -// - Minor clean up. -// -// v0.3c - 2016-05-28 -// - Fixed compilation error. -// -// v0.3b - 2016-05-16 -// - Fixed Linux/GCC build. -// - Updated documentation. -// -// v0.3a - 2016-05-15 -// - Minor fixes to documentation. -// -// v0.3 - 2016-05-11 -// - Optimizations. Now at about parity with the reference implementation on 32-bit builds. -// - Lots of clean up. -// -// v0.2b - 2016-05-10 -// - Bug fixes. -// -// v0.2a - 2016-05-10 -// - Made drflac_open_and_decode() more robust. -// - Removed an unused debugging variable -// -// v0.2 - 2016-05-09 -// - Added support for Ogg encapsulation. -// - API CHANGE. Have the onSeek callback take a third argument which specifies whether or not the seek -// should be relative to the start or the current position. Also changes the seeking rules such that -// seeking offsets will never be negative. -// - Have drflac_open_and_decode() fail gracefully if the stream has an unknown total sample count. -// -// v0.1b - 2016-05-07 -// - Properly close the file handle in drflac_open_file() and family when the decoder fails to initialize. -// - Removed a stale comment. -// -// v0.1a - 2016-05-05 -// - Minor formatting changes. -// - Fixed a warning on the GCC build. -// -// v0.1 - 2016-05-03 -// - Initial versioned release. + + +void drflac_init_cuesheet_track_iterator(drflac_cuesheet_track_iterator* pIter, drflac_uint32 trackCount, const void* pTrackData) +{ + if (pIter == NULL) { + return; + } + + pIter->countRemaining = trackCount; + pIter->pRunningData = (const char*)pTrackData; +} + +drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterator* pIter, drflac_cuesheet_track* pCuesheetTrack) +{ + drflac_cuesheet_track cuesheetTrack; + const char* pRunningData; + drflac_uint64 offsetHi; + drflac_uint64 offsetLo; + + if (pIter == NULL || pIter->countRemaining == 0 || pIter->pRunningData == NULL) { + return DRFLAC_FALSE; + } + + pRunningData = pIter->pRunningData; + + offsetHi = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + 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; + cuesheetTrack.isAudio = (pRunningData[0] & 0x80) != 0; + cuesheetTrack.preEmphasis = (pRunningData[0] & 0x40) != 0; pRunningData += 14; + cuesheetTrack.indexCount = pRunningData[0]; pRunningData += 1; + cuesheetTrack.pIndexPoints = (const drflac_cuesheet_track_index*)pRunningData; pRunningData += cuesheetTrack.indexCount * sizeof(drflac_cuesheet_track_index); + + pIter->pRunningData = pRunningData; + pIter->countRemaining -= 1; + + if (pCuesheetTrack) { + *pCuesheetTrack = cuesheetTrack; + } + + return DRFLAC_TRUE; +} + +#if defined(__GNUC__) + #pragma GCC diagnostic pop +#endif +#endif /* DR_FLAC_IMPLEMENTATION */ /* +REVISION HISTORY +================ +v0.11.10 - 2019-06-26 + - Fix a compiler error. + +v0.11.9 - 2019-06-16 + - Silence some ThreadSanitizer warnings. + +v0.11.8 - 2019-05-21 + - Fix warnings. + +v0.11.7 - 2019-05-06 + - C89 fixes. + +v0.11.6 - 2019-05-05 + - Add support for C89. + - Fix a compiler warning when CRC is disabled. + - Change license to choice of public domain or MIT-0. + +v0.11.5 - 2019-04-19 + - Fix a compiler error with GCC. + +v0.11.4 - 2019-04-17 + - Fix some warnings with GCC when compiling with -std=c99. + +v0.11.3 - 2019-04-07 + - Silence warnings with GCC. + +v0.11.2 - 2019-03-10 + - Fix a warning. + +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 + 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. + - API_CHANGE: Deprecated drflac_seek_to_sample() and replaced with drflac_seek_to_pcm_frame(). Same rules as + the changes to drflac_read_*() apply. + - API CHANGE: Deprecated drflac_open_and_decode_*() and replaced with drflac_open_*_and_read_*(). Same rules as + the changes to drflac_read_*() apply. + - Optimizations. + +v0.10.0 - 2018-09-11 + - Remove the DR_FLAC_NO_WIN32_IO option and the Win32 file IO functionality. If you need to use Win32 file IO you + need to do it yourself via the callback API. + - Fix the clang build. + - Fix undefined behavior. + - Fix errors with CUESHEET metdata blocks. + - Add an API for iterating over each cuesheet track in the CUESHEET metadata block. This works the same way as the + Vorbis comment API. + - Other miscellaneous bug fixes, mostly relating to invalid FLAC streams. + - Minor optimizations. + +v0.9.11 - 2018-08-29 + - Fix a bug with sample reconstruction. + +v0.9.10 - 2018-08-07 + - Improve 64-bit detection. + +v0.9.9 - 2018-08-05 + - Fix C++ build on older versions of GCC. + +v0.9.8 - 2018-07-24 + - Fix compilation errors. + +v0.9.7 - 2018-07-05 + - Fix a warning. + +v0.9.6 - 2018-06-29 + - Fix some typos. + +v0.9.5 - 2018-06-23 + - Fix some warnings. + +v0.9.4 - 2018-06-14 + - Optimizations to seeking. + - Clean up. + +v0.9.3 - 2018-05-22 + - Bug fix. + +v0.9.2 - 2018-05-12 + - Fix a compilation error due to a missing break statement. + +v0.9.1 - 2018-04-29 + - Fix compilation error with Clang. + +v0.9 - 2018-04-24 + - Fix Clang build. + - Start using major.minor.revision versioning. + +v0.8g - 2018-04-19 + - Fix build on non-x86/x64 architectures. + +v0.8f - 2018-02-02 + - Stop pretending to support changing rate/channels mid stream. + +v0.8e - 2018-02-01 + - Fix a crash when the block size of a frame is larger than the maximum block size defined by the FLAC stream. + - Fix a crash the the Rice partition order is invalid. + +v0.8d - 2017-09-22 + - Add support for decoding streams with ID3 tags. ID3 tags are just skipped. + +v0.8c - 2017-09-07 + - Fix warning on non-x86/x64 architectures. + +v0.8b - 2017-08-19 + - Fix build on non-x86/x64 architectures. + +v0.8a - 2017-08-13 + - A small optimization for the Clang build. + +v0.8 - 2017-08-12 + - API CHANGE: Rename dr_* types to drflac_*. + - Optimizations. This brings dr_flac back to about the same class of efficiency as the reference implementation. + - Add support for custom implementations of malloc(), realloc(), etc. + - Add CRC checking to Ogg encapsulated streams. + - Fix VC++ 6 build. This is only for the C++ compiler. The C compiler is not currently supported. + - Bug fixes. + +v0.7 - 2017-07-23 + - Add support for opening a stream without a header block. To do this, use drflac_open_relaxed() / drflac_open_with_metadata_relaxed(). + +v0.6 - 2017-07-22 + - Add support for recovering from invalid frames. With this change, dr_flac will simply skip over invalid frames as if they + never existed. Frames are checked against their sync code, the CRC-8 of the frame header and the CRC-16 of the whole frame. + +v0.5 - 2017-07-16 + - Fix typos. + - Change drflac_bool* types to unsigned. + - Add CRC checking. This makes dr_flac slower, but can be disabled with #define DR_FLAC_NO_CRC. + +v0.4f - 2017-03-10 + - Fix a couple of bugs with the bitstreaming code. + +v0.4e - 2017-02-17 + - Fix some warnings. + +v0.4d - 2016-12-26 + - Add support for 32-bit floating-point PCM decoding. + - Use drflac_int* and drflac_uint* sized types to improve compiler support. + - Minor improvements to documentation. + +v0.4c - 2016-12-26 + - Add support for signed 16-bit integer PCM decoding. + +v0.4b - 2016-10-23 + - A minor change to drflac_bool8 and drflac_bool32 types. + +v0.4a - 2016-10-11 + - Rename drBool32 to drflac_bool32 for styling consistency. + +v0.4 - 2016-09-29 + - API/ABI CHANGE: Use fixed size 32-bit booleans instead of the built-in bool type. + - API CHANGE: Rename drflac_open_and_decode*() to drflac_open_and_decode*_s32(). + - API CHANGE: Swap the order of "channels" and "sampleRate" parameters in drflac_open_and_decode*(). Rationale for this is to + keep it consistent with drflac_audio. + +v0.3f - 2016-09-21 + - Fix a warning with GCC. + +v0.3e - 2016-09-18 + - Fixed a bug where GCC 4.3+ was not getting properly identified. + - Fixed a few typos. + - Changed date formats to ISO 8601 (YYYY-MM-DD). + +v0.3d - 2016-06-11 + - Minor clean up. + +v0.3c - 2016-05-28 + - Fixed compilation error. + +v0.3b - 2016-05-16 + - Fixed Linux/GCC build. + - Updated documentation. + +v0.3a - 2016-05-15 + - Minor fixes to documentation. + +v0.3 - 2016-05-11 + - Optimizations. Now at about parity with the reference implementation on 32-bit builds. + - Lots of clean up. + +v0.2b - 2016-05-10 + - Bug fixes. + +v0.2a - 2016-05-10 + - Made drflac_open_and_decode() more robust. + - Removed an unused debugging variable + +v0.2 - 2016-05-09 + - Added support for Ogg encapsulation. + - API CHANGE. Have the onSeek callback take a third argument which specifies whether or not the seek + should be relative to the start or the current position. Also changes the seeking rules such that + seeking offsets will never be negative. + - Have drflac_open_and_decode() fail gracefully if the stream has an unknown total sample count. + +v0.1b - 2016-05-07 + - Properly close the file handle in drflac_open_file() and family when the decoder fails to initialize. + - Removed a stale comment. + +v0.1a - 2016-05-05 + - Minor formatting changes. + - Fixed a warning on the GCC build. + +v0.1 - 2016-05-03 + - Initial versioned release. +*/ + +/* +This software is available as a choice of the following licenses. Choose +whichever you prefer. + +=============================================================================== +ALTERNATIVE 1 - 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. +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. +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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to + +=============================================================================== +ALTERNATIVE 2 - MIT No Attribution +=============================================================================== +Copyright 2018 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 +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. + +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. */ diff --git a/raylib/external/dr_mp3.h b/raylib/external/dr_mp3.h index 070f0c1..0ecd0d3 100644 --- a/raylib/external/dr_mp3.h +++ b/raylib/external/dr_mp3.h @@ -1,57 +1,61 @@ -// MP3 audio decoder. Public domain. See "unlicense" statement at the end of this file. -// dr_mp3 - v0.4.0 - 2018-xx-xx -// -// 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. +/* +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 -// USAGE -// ===== -// dr_mp3 is a single-file library. To use it, do something like the following in one .c file. -// #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: -// -// drmp3 mp3; -// if (!drmp3_init_file(&mp3, "MySong.mp3", NULL)) { -// // Failed to open file -// } -// -// ... -// -// drmp3_uint64 framesRead = drmp3_read_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. -// -// You do 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_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_decode_f32(), drmp3_open_and_decode_memory_f32() and -// drmp3_open_and_decode_file_f32(). -// -// -// OPTIONS -// ======= -// #define these options before including this file. -// -// #define DR_MP3_NO_STDIO -// Disable drmp3_init_file(), etc. -// -// #define DR_MP3_NO_SIMD -// Disable SIMD optimizations. +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. +*/ + +/* +USAGE +===== +dr_mp3 is a single-file library. To use it, do something like the following in one .c file. + #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: + + drmp3 mp3; + if (!drmp3_init_file(&mp3, "MySong.mp3", NULL)) { + // Failed to open file + } + + ... + + 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. + +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(). + + +OPTIONS +======= +#define these options before including this file. + +#define DR_MP3_NO_STDIO + Disable drmp3_init_file(), etc. + +#define DR_MP3_NO_SIMD + Disable SIMD optimizations. +*/ #ifndef dr_mp3_h #define dr_mp3_h @@ -90,9 +94,20 @@ typedef drmp3_uint32 drmp3_bool32; #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 +#else +#ifdef __GNUC__ +#define DRMP3_INLINE __inline__ __attribute__((always_inline)) +#else +#define DRMP3_INLINE +#endif +#endif -// Low Level Push API -// ================== +/* +Low Level Push API +================== +*/ typedef struct { int frame_bytes, channels, hz, layer, bitrate_kbps; @@ -105,23 +120,30 @@ typedef struct unsigned char header[4], reserv_buf[511]; } drmp3dec; -// Initializes a low level decoder. +/* Initializes a low level decoder. */ void drmp3dec_init(drmp3dec *dec); -// Reads a frame from a low level decoder. +/* 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); -// Helper for converting between f32 and s16. +/* Helper for converting between f32 and s16. */ void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples); - -// Main API (Pull API) -// =================== +/* +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 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 { @@ -144,7 +166,7 @@ typedef struct 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_uint32 cacheSizeInFrames; /* The number of frames to read from the client at a time. */ } drmp3_src_config; struct drmp3_src @@ -153,12 +175,12 @@ struct drmp3_src drmp3_src_read_proc onRead; void* pUserData; float bin[256]; - drmp3_src_cache cache; // <-- For simplifying and optimizing client -> memory reading. + drmp3_src_cache cache; /* <-- For simplifying and optimizing client -> memory reading. */ union { struct { - float alpha; + double alpha; drmp3_bool32 isPrevFramesLoaded : 1; drmp3_bool32 isNextFramesLoaded : 1; } linear; @@ -171,28 +193,40 @@ typedef enum drmp3_seek_origin_current } drmp3_seek_origin; -// Callback for when data is read. Return value is the number of bytes actually read. -// -// pUserData [in] The user data that was passed to drmp3_init(), drmp3_open() and family. -// pBufferOut [out] The output buffer. -// bytesToRead [in] The number of bytes to read. -// -// Returns the number of bytes actually read. -// -// 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 struct +{ + drmp3_uint64 seekPosInBytes; /* Points to the first byte of an MP3 frame. */ + drmp3_uint64 pcmFrameIndex; /* The index of the PCM frame this seek point targets. */ + drmp3_uint16 mp3FramesToDiscard; /* The number of whole MP3 frames to be discarded before pcmFramesToDiscard. */ + drmp3_uint16 pcmFramesToDiscard; /* The number of leading samples to read and discard. These are discarded after mp3FramesToDiscard. */ +} drmp3_seek_point; + +/* +Callback for when data is read. Return value is the number of bytes actually read. + +pUserData [in] The user data that was passed to drmp3_init(), drmp3_open() and family. +pBufferOut [out] The output buffer. +bytesToRead [in] The number of bytes to read. + +Returns the number of bytes actually read. + +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 (* drmp3_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 drmp3_init(), drmp3_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. -// -// Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which -// will be either drmp3_seek_origin_start or drmp3_seek_origin_current. +/* +Callback for when data needs to be seeked. + +pUserData [in] The user data that was passed to drmp3_init(), drmp3_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. + +Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which +will be either drmp3_seek_origin_start or drmp3_seek_origin_current. +*/ typedef drmp3_bool32 (* drmp3_seek_proc)(void* pUserData, int offset, drmp3_seek_origin origin); typedef struct @@ -210,13 +244,16 @@ typedef struct drmp3_read_proc onRead; drmp3_seek_proc onSeek; void* pUserData; - 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 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; drmp3_uint32 pcmFramesRemainingInMP3Frame; - 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_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; drmp3_uint8* pData; @@ -226,94 +263,155 @@ typedef struct const drmp3_uint8* pData; size_t dataSize; size_t currentReadPos; - } memory; // Only used for decoders that were opened against a block of memory. + } memory; /* Only used for decoders that were opened against a block of memory. */ } drmp3; -// Initializes an MP3 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. -// -// Returns true if successful; false otherwise. -// -// Close the loader with drmp3_uninit(). -// -// See also: drmp3_init_file(), drmp3_init_memory(), drmp3_uninit() +/* +Initializes an MP3 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. + +Returns true if successful; false otherwise. + +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); -// Initializes an MP3 decoder from a 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 drmp3 object. -// -// The buffer should contain the contents of the entire MP3 file. +/* +Initializes an MP3 decoder from a 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 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); #ifndef DR_MP3_NO_STDIO -// Initializes an MP3 decoder from a file. -// -// This holds the internal FILE object until drmp3_uninit() is called. Keep this in mind if you're caching drmp3 -// objects because the operating system may restrict the number of file handles an application can have open at -// any given time. +/* +Initializes an MP3 decoder from a file. + +This holds the internal FILE object until drmp3_uninit() is called. Keep this in mind if you're caching drmp3 +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); #endif -// Uninitializes an MP3 decoder. +/* +Uninitializes an MP3 decoder. +*/ 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. +/* +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); -// Seeks to a specific frame. -// -// Note that this is _not_ an MP3 frame, but rather a PCM frame. +/* +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); + +/* +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); -// 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. +/* +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); -// 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. +/* +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); +/* +Calculates the total number of MP3 and PCM frames in the MP3 stream. Cannot be used for infinite streams such as internet +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); + +/* +Calculates the seekpoints based on PCM frames. This is slow. + +pSeekpoint count is a pointer to a uint32 containing the seekpoint count. On input it contains the desired count. +On output it contains the actual count. The reason for this design is that the client may request too many +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); + +/* +Binds a seek table to the decoder. + +This does _not_ make a copy of pSeekPoints - it only references it. It is up to the application to ensure this +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); -// 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. -// -// Free the returned pointer with drmp3_free(). +/* +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. + +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); + 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); + #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); #endif -// Frees any memory that was allocated by a public drmp3 API. +/* +Frees any memory that was allocated by a public drmp3 API. +*/ void drmp3_free(void* p); #ifdef __cplusplus } #endif -#endif // dr_mp3_h +#endif /* dr_mp3_h */ -///////////////////////////////////////////////////// -// -// IMPLEMENTATION -// -///////////////////////////////////////////////////// +/************************************************************************************************************************************************************ + ************************************************************************************************************************************************************ + + IMPLEMENTATION + + ************************************************************************************************************************************************************ + ************************************************************************************************************************************************************/ #ifdef DR_MP3_IMPLEMENTATION #include #include -#include -#include // For INT_MAX +#include /* For INT_MAX */ -// Disable SIMD when compiling with TCC for now. +/* Disable SIMD when compiling with TCC for now. */ #if defined(__TINYC__) #define DR_MP3_NO_SIMD #endif @@ -365,7 +463,7 @@ void drmp3_free(void* p); #define DR_MP3_ONLY_SIMD #endif -#if (defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))) || ((defined(__i386__) || defined(__x86_64__)) && defined(__SSE2__)) +#if ((defined(_MSC_VER) && _MSC_VER >= 1400) && (defined(_M_IX86) || defined(_M_X64))) || ((defined(__i386__) || defined(__x86_64__)) && defined(__SSE2__)) #if defined(_MSC_VER) #include #endif @@ -780,8 +878,8 @@ static int drmp3_L3_read_side_info(drmp3_bs *bs, drmp3_L3_gr_info *gr, const drm unsigned tables, scfsi = 0; int main_data_begin, part_23_sum = 0; - int sr_idx = DRMP3_HDR_GET_MY_SAMPLE_RATE(hdr); sr_idx -= (sr_idx != 0); int gr_count = DRMP3_HDR_IS_MONO(hdr) ? 1 : 2; + int sr_idx = DRMP3_HDR_GET_MY_SAMPLE_RATE(hdr); sr_idx -= (sr_idx != 0); if (DRMP3_HDR_TEST_MPEG1(hdr)) { @@ -1045,41 +1143,72 @@ static void drmp3_L3_huffman(float *dst, drmp3_bs *bs, const drmp3_L3_gr_info *g int sfb_cnt = gr_info->region_count[ireg++]; const drmp3_int16 *codebook = tabs + tabindex[tab_num]; int linbits = g_linbits[tab_num]; - do + if (linbits) { - np = *sfb++ / 2; - pairs_to_decode = DRMP3_MIN(big_val_cnt, np); - one = *scf++; do { - int j, w = 5; - int leaf = codebook[DRMP3_PEEK_BITS(w)]; - while (leaf < 0) + np = *sfb++ / 2; + pairs_to_decode = DRMP3_MIN(big_val_cnt, np); + one = *scf++; + do { - DRMP3_FLUSH_BITS(w); - w = leaf & 7; - leaf = codebook[DRMP3_PEEK_BITS(w) - (leaf >> 3)]; - } - DRMP3_FLUSH_BITS(leaf >> 8); - - for (j = 0; j < 2; j++, dst++, leaf >>= 4) - { - int lsb = leaf & 0x0F; - if (lsb == 15 && linbits) + int j, w = 5; + int leaf = codebook[DRMP3_PEEK_BITS(w)]; + while (leaf < 0) { - lsb += DRMP3_PEEK_BITS(linbits); - DRMP3_FLUSH_BITS(linbits); - DRMP3_CHECK_BITS; - *dst = one*drmp3_L3_pow_43(lsb)*((int32_t)bs_cache < 0 ? -1: 1); - } else - { - *dst = g_drmp3_pow43[16 + lsb - 16*(bs_cache >> 31)]*one; + DRMP3_FLUSH_BITS(w); + w = leaf & 7; + leaf = codebook[DRMP3_PEEK_BITS(w) - (leaf >> 3)]; } - DRMP3_FLUSH_BITS(lsb ? 1 : 0); - } - DRMP3_CHECK_BITS; - } while (--pairs_to_decode); - } while ((big_val_cnt -= np) > 0 && --sfb_cnt >= 0); + DRMP3_FLUSH_BITS(leaf >> 8); + + for (j = 0; j < 2; j++, dst++, leaf >>= 4) + { + int lsb = leaf & 0x0F; + if (lsb == 15) + { + lsb += DRMP3_PEEK_BITS(linbits); + DRMP3_FLUSH_BITS(linbits); + DRMP3_CHECK_BITS; + *dst = one*drmp3_L3_pow_43(lsb)*((drmp3_int32)bs_cache < 0 ? -1: 1); + } else + { + *dst = g_drmp3_pow43[16 + lsb - 16*(bs_cache >> 31)]*one; + } + DRMP3_FLUSH_BITS(lsb ? 1 : 0); + } + DRMP3_CHECK_BITS; + } while (--pairs_to_decode); + } while ((big_val_cnt -= np) > 0 && --sfb_cnt >= 0); + } else + { + do + { + np = *sfb++ / 2; + pairs_to_decode = DRMP3_MIN(big_val_cnt, np); + one = *scf++; + do + { + int j, w = 5; + int leaf = codebook[DRMP3_PEEK_BITS(w)]; + while (leaf < 0) + { + DRMP3_FLUSH_BITS(w); + w = leaf & 7; + leaf = codebook[DRMP3_PEEK_BITS(w) - (leaf >> 3)]; + } + DRMP3_FLUSH_BITS(leaf >> 8); + + for (j = 0; j < 2; j++, dst++, leaf >>= 4) + { + int lsb = leaf & 0x0F; + *dst = g_drmp3_pow43[16 + lsb - 16*(bs_cache >> 31)]*one; + DRMP3_FLUSH_BITS(lsb ? 1 : 0); + } + DRMP3_CHECK_BITS; + } while (--pairs_to_decode); + } while ((big_val_cnt -= np) > 0 && --sfb_cnt >= 0); + } } for (np = 1 - big_val_cnt;; dst += 4) @@ -1654,9 +1783,10 @@ typedef drmp3_int16 drmp3d_sample_t; static drmp3_int16 drmp3d_scale_pcm(float sample) { + drmp3_int16 s; if (sample >= 32766.5) return (drmp3_int16) 32767; if (sample <= -32767.5) return (drmp3_int16)-32768; - drmp3_int16 s = (drmp3_int16)(sample + .5f); + s = (drmp3_int16)(sample + .5f); s -= (s < 0); /* away from zero, to be compliant */ return (drmp3_int16)s; } @@ -1964,11 +2094,6 @@ int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes info->layer = 4 - DRMP3_HDR_GET_LAYER(hdr); info->bitrate_kbps = drmp3_hdr_bitrate_kbps(hdr); - if (!pcm) - { - return drmp3_hdr_frame_samples(hdr); - } - drmp3_bs_init(bs_frame, hdr + DRMP3_HDR_SIZE, frame_size - DRMP3_HDR_SIZE); if (DRMP3_HDR_IS_CRC(hdr)) { @@ -1984,7 +2109,7 @@ int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes return 0; } success = drmp3_L3_restore_reservoir(dec, bs_frame, &scratch, main_data_begin); - if (success) + if (success && pcm != NULL) { for (igr = 0; igr < (DRMP3_HDR_TEST_MPEG1(hdr) ? 2 : 1); igr++, pcm = DRMP3_OFFSET_PTR(pcm, sizeof(drmp3d_sample_t)*576*info->channels)) { @@ -2000,6 +2125,11 @@ int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes return 0; #else drmp3_L12_scale_info sci[1]; + + if (pcm == NULL) { + return drmp3_hdr_frame_samples(hdr); + } + drmp3_L12_read_scale_info(hdr, bs_frame, sci); memset(scratch.grbuf[0], 0, 576*2*sizeof(float)); @@ -2021,6 +2151,7 @@ int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes } #endif } + return success*drmp3_hdr_frame_samples(dec->header); } @@ -2033,14 +2164,14 @@ void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples) int aligned_count = num_samples & ~7; for(; i < aligned_count; i+=8) { - static const drmp3_f4 g_scale = { 32768.0f, 32768.0f, 32768.0f, 32768.0f }; - drmp3_f4 a = DRMP3_VMUL(DRMP3_VLD(&in[i ]), g_scale); - drmp3_f4 b = DRMP3_VMUL(DRMP3_VLD(&in[i+4]), g_scale); + 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 - static const drmp3_f4 g_max = { 32767.0f, 32767.0f, 32767.0f, 32767.0f }; - static const drmp3_f4 g_min = { -32768.0f, -32768.0f, -32768.0f, -32768.0f }; - __m128i pcm8 = _mm_packs_epi32(_mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(a, g_max), g_min)), - _mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(b, g_max), g_min))); + 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); @@ -2085,11 +2216,11 @@ void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples) -/////////////////////////////////////////////////////////////////////////////// -// -// Main Public API -// -/////////////////////////////////////////////////////////////////////////////// +/************************************************************************************************************************************************************ + + Main Public API + + ************************************************************************************************************************************************************/ #if defined(SIZE_MAX) #define DRMP3_SIZE_MAX SIZE_MAX @@ -2101,16 +2232,13 @@ void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples) #endif #endif -// Options. -#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 +/* Options. */ +#ifndef DRMP3_SEEK_LEADING_MP3_FRAMES +#define DRMP3_SEEK_LEADING_MP3_FRAMES 2 #endif -// Standard library stuff. +/* Standard library stuff. */ #ifndef DRMP3_ASSERT #include #define DRMP3_ASSERT(expression) assert(expression) @@ -2143,16 +2271,17 @@ void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples) #define drmp3_max(x, y) (((x) > (y)) ? (x) : (y)) #define drmp3_min(x, y) (((x) < (y)) ? (x) : (y)) -#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_DATA_CHUNK_SIZE 16384 /* The size in bytes of each chunk of data to read from the MP3 stream. minimp3 recommends 16K. */ -static inline float drmp3_mix_f32(float x, float y, float a) +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) { - for (drmp3_uint32 i = 0; i < channels; ++i) { + drmp3_uint32 i; + for (i = 0; i < channels; ++i) { pOut[i] = drmp3_mix_f32(pInA[i], pInB[i], factor); } } @@ -2169,17 +2298,20 @@ void drmp3_src_cache_init(drmp3_src* pSRC, drmp3_src_cache* pCache) 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); - drmp3_uint32 channels = pCache->pSRC->config.channels; + channels = pCache->pSRC->config.channels; - drmp3_uint64 totalFramesRead = 0; while (frameCount > 0) { - // If there's anything in memory go ahead and copy that over first. + /* 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) { @@ -2196,14 +2328,14 @@ drmp3_uint64 drmp3_src_cache_read_frames(drmp3_src_cache* pCache, drmp3_uint64 f } - // At this point there are still more frames to read from the client, so we'll need to reload the cache with fresh data. + /* 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; - drmp3_uint32 framesToReadFromClient = drmp3_countof(pCache->pCachedFrames) / pCache->pSRC->config.channels; + framesToReadFromClient = drmp3_countof(pCache->pCachedFrames) / pCache->pSRC->config.channels; if (framesToReadFromClient > pCache->pSRC->config.cacheSizeInFrames) { framesToReadFromClient = pCache->pSRC->config.cacheSizeInFrames; } @@ -2211,7 +2343,7 @@ drmp3_uint64 drmp3_src_cache_read_frames(drmp3_src_cache* pCache, drmp3_uint64 f 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. + /* Get out of this loop if nothing was able to be retrieved. */ if (pCache->cachedFrameCount == 0) { break; } @@ -2226,11 +2358,19 @@ drmp3_uint64 drmp3_src_read_frames_linear(drmp3_src* pSRC, drmp3_uint64 frameCou drmp3_bool32 drmp3_src_init(const drmp3_src_config* pConfig, drmp3_src_read_proc onRead, void* pUserData, drmp3_src* pSRC) { - if (pSRC == NULL) return DRMP3_FALSE; + 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; + if (pConfig == NULL || onRead == NULL) { + return DRMP3_FALSE; + } + + if (pConfig->channels == 0 || pConfig->channels > 2) { + return DRMP3_FALSE; + } pSRC->config = *pConfig; pSRC->onRead = onRead; @@ -2246,9 +2386,11 @@ drmp3_bool32 drmp3_src_init(const drmp3_src_config* pConfig, drmp3_src_read_proc drmp3_bool32 drmp3_src_set_input_sample_rate(drmp3_src* pSRC, drmp3_uint32 sampleRateIn) { - if (pSRC == NULL) return DRMP3_FALSE; + if (pSRC == NULL) { + return DRMP3_FALSE; + } - // Must have a sample rate of > 0. + /* Must have a sample rate of > 0. */ if (sampleRateIn == 0) { return DRMP3_FALSE; } @@ -2259,9 +2401,11 @@ drmp3_bool32 drmp3_src_set_input_sample_rate(drmp3_src* pSRC, drmp3_uint32 sampl drmp3_bool32 drmp3_src_set_output_sample_rate(drmp3_src* pSRC, drmp3_uint32 sampleRateOut) { - if (pSRC == NULL) return DRMP3_FALSE; + if (pSRC == NULL) { + return DRMP3_FALSE; + } - // Must have a sample rate of > 0. + /* Must have a sample rate of > 0. */ if (sampleRateOut == 0) { return DRMP3_FALSE; } @@ -2272,16 +2416,20 @@ drmp3_bool32 drmp3_src_set_output_sample_rate(drmp3_src* pSRC, drmp3_uint32 samp drmp3_uint64 drmp3_src_read_frames_ex(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush) { - if (pSRC == NULL || frameCount == 0 || pFramesOut == NULL) return 0; + drmp3_src_algorithm algorithm; - drmp3_src_algorithm algorithm = pSRC->config.algorithm; + if (pSRC == NULL || frameCount == 0 || pFramesOut == NULL) { + return 0; + } - // Always use passthrough if the sample rates are the same. + 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... + /* 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); @@ -2301,19 +2449,22 @@ drmp3_uint64 drmp3_src_read_frames_passthrough(drmp3_src* pSRC, drmp3_uint64 fra drmp3_assert(frameCount > 0); drmp3_assert(pFramesOut != NULL); - (void)flush; // Passthrough need not care about flushing. + (void)flush; /* Passthrough need not care about flushing. */ return pSRC->onRead(pSRC, frameCount, pFramesOut, pSRC->pUserData); } drmp3_uint64 drmp3_src_read_frames_linear(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush) { + 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. + /* For linear SRC, the bin is only 2 frames: 1 prior, 1 future. */ - // Load the bin if necessary. + /* 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) { @@ -2329,31 +2480,38 @@ drmp3_uint64 drmp3_src_read_frames_linear(drmp3_src* pSRC, drmp3_uint64 frameCou pSRC->algo.linear.isNextFramesLoaded = DRMP3_TRUE; } - float factor = (float)pSRC->config.sampleRateIn / pSRC->config.sampleRateOut; + factor = (double)pSRC->config.sampleRateIn / pSRC->config.sampleRateOut; - drmp3_uint64 totalFramesRead = 0; + totalFramesRead = 0; while (frameCount > 0) { - // The bin is where the previous and next frames are located. + drmp3_uint32 i; + drmp3_uint32 framesToReadFromClient; + + /* The bin is where the previous and next frames are located. */ float* pPrevFrame = pSRC->bin; float* pNextFrame = pSRC->bin + pSRC->config.channels; - drmp3_blend_f32((float*)pFramesOut, pPrevFrame, pNextFrame, pSRC->algo.linear.alpha, pSRC->config.channels); + drmp3_blend_f32((float*)pFramesOut, pPrevFrame, pNextFrame, (float)pSRC->algo.linear.alpha, pSRC->config.channels); pSRC->algo.linear.alpha += factor; - // The new alpha value is how we determine whether or not we need to read fresh frames. - drmp3_uint32 framesToReadFromClient = (drmp3_uint32)pSRC->algo.linear.alpha; + /* 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; - for (drmp3_uint32 i = 0; i < framesToReadFromClient; ++i) { - for (drmp3_uint32 j = 0; j < pSRC->config.channels; ++j) { + for (i = 0; i < framesToReadFromClient; ++i) { + drmp3_uint64 framesRead; + drmp3_uint32 j; + + for (j = 0; j < pSRC->config.channels; ++j) { pPrevFrame[j] = pNextFrame[j]; } - drmp3_uint64 framesRead = drmp3_src_cache_read_frames(&pSRC->cache, 1, pNextFrame); + framesRead = drmp3_src_cache_read_frames(&pSRC->cache, 1, pNextFrame); if (framesRead == 0) { - for (drmp3_uint32 j = 0; j < pSRC->config.channels; ++j) { - pNextFrame[j] = 0; + drmp3_uint32 k; + for (k = 0; k < pSRC->config.channels; ++k) { + pNextFrame[k] = 0; } if (pSRC->algo.linear.isNextFramesLoaded) { @@ -2372,7 +2530,7 @@ drmp3_uint64 drmp3_src_read_frames_linear(drmp3_src* pSRC, drmp3_uint64 frameCou frameCount -= 1; totalFramesRead += 1; - // If there's no frames available we need to get out of this loop. + /* 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; } @@ -2384,152 +2542,93 @@ drmp3_uint64 drmp3_src_read_frames_linear(drmp3_src* pSRC, drmp3_uint64 frameCou static size_t drmp3__on_read(drmp3* pMP3, void* pBufferOut, size_t bytesToRead) { - return pMP3->onRead(pMP3->pUserData, pBufferOut, bytesToRead); + size_t bytesRead = pMP3->onRead(pMP3->pUserData, pBufferOut, bytesToRead); + pMP3->streamCursor += bytesRead; + return bytesRead; } static drmp3_bool32 drmp3__on_seek(drmp3* pMP3, int offset, drmp3_seek_origin origin) { drmp3_assert(offset >= 0); - return pMP3->onSeek(pMP3->pUserData, offset, origin); -} - -static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPCMFrames) -{ - drmp3_assert(pMP3 != NULL); - drmp3_assert(pMP3->onRead != NULL); - - if (pMP3->atEnd) { - return 0; + if (!pMP3->onSeek(pMP3->pUserData, offset, origin)) { + return DRMP3_FALSE; } - drmp3_uint32 pcmFramesRead = 0; - do { - // 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) { - if (pMP3->dataCapacity < DRMP3_DATA_CHUNK_SIZE) { - pMP3->dataCapacity = DRMP3_DATA_CHUNK_SIZE; - drmp3_uint8* pNewData = (drmp3_uint8*)drmp3_realloc(pMP3->pData, pMP3->dataCapacity); - if (pNewData == NULL) { - return 0; // Out of memory. - } + if (origin == drmp3_seek_origin_start) { + pMP3->streamCursor = (drmp3_uint64)offset; + } else { + pMP3->streamCursor += offset; + } - pMP3->pData = pNewData; + return DRMP3_TRUE; +} + +static drmp3_bool32 drmp3__on_seek_64(drmp3* pMP3, drmp3_uint64 offset, drmp3_seek_origin origin) +{ + if (offset <= 0x7FFFFFFF) { + return drmp3__on_seek(pMP3, (int)offset, origin); + } + + + /* Getting here "offset" is too large for a 32-bit integer. We just keep seeking forward until we hit the offset. */ + if (!drmp3__on_seek(pMP3, 0x7FFFFFFF, drmp3_seek_origin_start)) { + return DRMP3_FALSE; + } + + offset -= 0x7FFFFFFF; + while (offset > 0) { + if (offset <= 0x7FFFFFFF) { + if (!drmp3__on_seek(pMP3, (int)offset, drmp3_seek_origin_current)) { + return DRMP3_FALSE; } - - size_t bytesRead = drmp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); - if (bytesRead == 0) { - if (pMP3->dataSize == 0) { - pMP3->atEnd = DRMP3_TRUE; - return 0; // No data. - } - } - - pMP3->dataSize += bytesRead; - } - - if (pMP3->dataSize > INT_MAX) { - pMP3->atEnd = DRMP3_TRUE; - return 0; // File too big. - } - - drmp3dec_frame_info info; - pcmFramesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->pData, (int)pMP3->dataSize, pPCMFrames, &info); // <-- Safe size_t -> int conversion thanks to the check above. - if (pcmFramesRead != 0) { - size_t leftoverDataSize = (pMP3->dataSize - (size_t)info.frame_bytes); - for (size_t i = 0; i < leftoverDataSize; ++i) { - pMP3->pData[i] = pMP3->pData[i + (size_t)info.frame_bytes]; - } - - pMP3->dataSize = leftoverDataSize; - pMP3->pcmFramesConsumedInMP3Frame = 0; - pMP3->pcmFramesRemainingInMP3Frame = pcmFramesRead; - pMP3->mp3FrameChannels = info.channels; - pMP3->mp3FrameSampleRate = info.hz; - drmp3_src_set_input_sample_rate(&pMP3->src, pMP3->mp3FrameSampleRate); - break; + offset = 0; } else { - // Need more data. minimp3 recommends doing data submission in 16K chunks. - if (pMP3->dataCapacity == pMP3->dataSize) { - // No room. Expand. - pMP3->dataCapacity += DRMP3_DATA_CHUNK_SIZE; - drmp3_uint8* pNewData = (drmp3_uint8*)drmp3_realloc(pMP3->pData, pMP3->dataCapacity); - if (pNewData == NULL) { - return 0; // Out of memory. - } - - pMP3->pData = pNewData; + if (!drmp3__on_seek(pMP3, 0x7FFFFFFF, drmp3_seek_origin_current)) { + return DRMP3_FALSE; } - - // Fill in a chunk. - size_t bytesRead = drmp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); - if (bytesRead == 0) { - pMP3->atEnd = DRMP3_TRUE; - return 0; // Error reading more data. - } - - pMP3->dataSize += bytesRead; + offset -= 0x7FFFFFFF; } - } while (DRMP3_TRUE); - - return pcmFramesRead; -} - -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); -} - -static drmp3_uint32 drmp3_seek_next_frame(drmp3* pMP3) -{ - drmp3_assert(pMP3 != NULL); - - drmp3_uint32 pcmFrameCount = drmp3_decode_next_frame_ex(pMP3, NULL); - if (pcmFrameCount == 0) { - return 0; } - // We have essentially just skipped past the frame, so just set the remaining samples to 0. - pMP3->currentPCMFrame += pcmFrameCount; - pMP3->pcmFramesConsumedInMP3Frame = pcmFrameCount; - pMP3->pcmFramesRemainingInMP3Frame = 0; - - return pcmFrameCount; + 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; - drmp3_assert(pMP3 != NULL); - drmp3_assert(pMP3->onRead != NULL); - 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. + /* 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. + /* Mono -> Mono. */ pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f; } else { - // Mono -> Stereo. + /* Mono -> Stereo. */ pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f; pFramesOutF[1] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f; } } else { if (pMP3->channels == 1) { - // Stereo -> Mono + /* 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 + /* Stereo -> Stereo */ pFramesOutF[0] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0] / 32768.0f; pFramesOutF[1] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1] / 32768.0f; } @@ -2537,22 +2636,22 @@ static drmp3_uint64 drmp3_read_src(drmp3_src* pSRC, drmp3_uint64 frameCount, voi #else if (pMP3->mp3FrameChannels == 1) { if (pMP3->channels == 1) { - // Mono -> Mono. + /* Mono -> Mono. */ pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame]; } else { - // Mono -> Stereo. + /* Mono -> Stereo. */ pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame]; pFramesOutF[1] = frames[pMP3->pcmFramesConsumedInMP3Frame]; } } else { if (pMP3->channels == 1) { - // Stereo -> Mono + /* 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 + /* Stereo -> Stereo */ pFramesOutF[0] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0]; pFramesOutF[1] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1]; } @@ -2572,8 +2671,10 @@ static drmp3_uint64 drmp3_read_src(drmp3_src* pSRC, drmp3_uint64 frameCount, voi 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. + /* + 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; } @@ -2582,42 +2683,8 @@ static drmp3_uint64 drmp3_read_src(drmp3_src* pSRC, drmp3_uint64 frameCount, voi return totalFramesRead; } -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_src(drmp3* pMP3) { - 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. - drmp3_config config; - if (pConfig != NULL) { - config = *pConfig; - } else { - drmp3_zero_object(&config); - } - - pMP3->channels = config.outputChannels; - if (pMP3->channels == 0) { - pMP3->channels = DR_MP3_DEFAULT_CHANNELS; - } - - // Cannot have more than 2 channels. - if (pMP3->channels > 2) { - pMP3->channels = 2; - } - - pMP3->sampleRate = config.outputSampleRate; - if (pMP3->sampleRate == 0) { - pMP3->sampleRate = DR_MP3_DEFAULT_SAMPLE_RATE; - } - - pMP3->onRead = onRead; - pMP3->onSeek = onSeek; - pMP3->pUserData = pUserData; - - // We need a sample rate converter for converting the sample rate from the MP3 frames to the requested output sample rate. drmp3_src_config srcConfig; drmp3_zero_object(&srcConfig); srcConfig.sampleRateIn = DR_MP3_DEFAULT_SAMPLE_RATE; @@ -2628,11 +2695,190 @@ drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek 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) +{ + drmp3_uint32 pcmFramesRead = 0; + + drmp3_assert(pMP3 != NULL); + drmp3_assert(pMP3->onRead != NULL); + + if (pMP3->atEnd) { + return 0; + } + + do { + 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) { + size_t bytesRead; + + if (pMP3->dataCapacity < DRMP3_DATA_CHUNK_SIZE) { + drmp3_uint8* pNewData; + + pMP3->dataCapacity = DRMP3_DATA_CHUNK_SIZE; + pNewData = (drmp3_uint8*)drmp3_realloc(pMP3->pData, pMP3->dataCapacity); + if (pNewData == NULL) { + return 0; /* Out of memory. */ + } + + pMP3->pData = pNewData; + } + + bytesRead = drmp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); + if (bytesRead == 0) { + if (pMP3->dataSize == 0) { + pMP3->atEnd = DRMP3_TRUE; + return 0; /* No data. */ + } + } + + pMP3->dataSize += bytesRead; + } + + if (pMP3->dataSize > INT_MAX) { + pMP3->atEnd = DRMP3_TRUE; + 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. */ + + /* 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; + } + + /* + 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 = 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) { + size_t bytesRead; + + /* Need more data. minimp3 recommends doing data submission in 16K chunks. */ + if (pMP3->dataCapacity == pMP3->dataSize) { + drmp3_uint8* pNewData; + + /* No room. Expand. */ + pMP3->dataCapacity += DRMP3_DATA_CHUNK_SIZE; + pNewData = (drmp3_uint8*)drmp3_realloc(pMP3->pData, pMP3->dataCapacity); + if (pNewData == NULL) { + return 0; /* Out of memory. */ + } + + pMP3->pData = pNewData; + } + + /* Fill in a chunk. */ + bytesRead = drmp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); + if (bytesRead == 0) { + pMP3->atEnd = DRMP3_TRUE; + return 0; /* Error reading more data. */ + } + + pMP3->dataSize += bytesRead; + } + } while (DRMP3_TRUE); + + return pcmFramesRead; +} + +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); +} + +#if 0 +static drmp3_uint32 drmp3_seek_next_frame(drmp3* pMP3) +{ + drmp3_uint32 pcmFrameCount; + + drmp3_assert(pMP3 != NULL); + + pcmFrameCount = drmp3_decode_next_frame_ex(pMP3, NULL); + if (pcmFrameCount == 0) { + return 0; + } + + /* We have essentially just skipped past the frame, so just set the remaining samples to 0. */ + pMP3->currentPCMFrame += pcmFrameCount; + pMP3->pcmFramesConsumedInMP3Frame = pcmFrameCount; + pMP3->pcmFramesRemainingInMP3Frame = 0; + + return pcmFrameCount; +} +#endif + +drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_config* pConfig) +{ + drmp3_config config; + + 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; + + /* + 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); + } - // Decode the first frame to confirm that it is indeed a valid MP3 stream. + /* Decode the first frame to confirm that it is indeed a valid MP3 stream. */ if (!drmp3_decode_next_frame(pMP3)) { drmp3_uninit(pMP3); - return DRMP3_FALSE; // Not a valid MP3 stream. + return DRMP3_FALSE; /* Not a valid MP3 stream. */ } return DRMP3_TRUE; @@ -2652,10 +2898,12 @@ drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onS static size_t drmp3__on_read_memory(void* pUserData, void* pBufferOut, size_t bytesToRead) { drmp3* pMP3 = (drmp3*)pUserData; + size_t bytesRemaining; + drmp3_assert(pMP3 != NULL); drmp3_assert(pMP3->memory.dataSize >= pMP3->memory.currentReadPos); - size_t bytesRemaining = pMP3->memory.dataSize - pMP3->memory.currentReadPos; + bytesRemaining = pMP3->memory.dataSize - pMP3->memory.currentReadPos; if (bytesToRead > bytesRemaining) { bytesToRead = bytesRemaining; } @@ -2671,26 +2919,27 @@ static size_t drmp3__on_read_memory(void* pUserData, void* pBufferOut, size_t by static drmp3_bool32 drmp3__on_seek_memory(void* pUserData, int byteOffset, drmp3_seek_origin origin) { drmp3* pMP3 = (drmp3*)pUserData; + drmp3_assert(pMP3 != NULL); if (origin == drmp3_seek_origin_current) { if (byteOffset > 0) { if (pMP3->memory.currentReadPos + byteOffset > pMP3->memory.dataSize) { - byteOffset = (int)(pMP3->memory.dataSize - pMP3->memory.currentReadPos); // Trying to seek too far forward. + byteOffset = (int)(pMP3->memory.dataSize - pMP3->memory.currentReadPos); /* Trying to seek too far forward. */ } } else { if (pMP3->memory.currentReadPos < (size_t)-byteOffset) { - byteOffset = -(int)pMP3->memory.currentReadPos; // Trying to seek too far backwards. + byteOffset = -(int)pMP3->memory.currentReadPos; /* Trying to seek too far backwards. */ } } - // This will never underflow thanks to the clamps above. + /* This will never underflow thanks to the clamps above. */ pMP3->memory.currentReadPos += byteOffset; } else { if ((drmp3_uint32)byteOffset <= pMP3->memory.dataSize) { pMP3->memory.currentReadPos = byteOffset; } else { - pMP3->memory.currentReadPos = pMP3->memory.dataSize; // Trying to seek too far forward. + pMP3->memory.currentReadPos = pMP3->memory.dataSize; /* Trying to seek too far forward. */ } } @@ -2765,21 +3014,22 @@ void drmp3_uninit(drmp3* pMP3) drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut) { + drmp3_uint64 totalFramesRead = 0; + if (pMP3 == NULL || pMP3->onRead == NULL) { return 0; } - drmp3_uint64 totalFramesRead = 0; - 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; } - drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadRightNow, temp); + framesJustRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadRightNow, temp); if (framesJustRead == 0) { break; } @@ -2795,22 +3045,144 @@ drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, f return totalFramesRead; } -drmp3_bool32 drmp3_seek_to_start_of_stream(drmp3* pMP3) +drmp3_uint64 drmp3_read_pcm_frames_s16(drmp3* pMP3, drmp3_uint64 framesToRead, drmp3_int16* pBufferOut) { - drmp3_assert(pMP3 != NULL); - drmp3_assert(pMP3->onSeek != NULL); + float tempF32[4096]; + drmp3_uint64 pcmFramesJustRead; + drmp3_uint64 totalPCMFramesRead = 0; - // Seek to the start of the stream to begin with. - if (!drmp3__on_seek(pMP3, 0, drmp3_seek_origin_start)) { - return DRMP3_FALSE; + if (pMP3 == NULL || pMP3->onRead == NULL) { + return 0; } - // Clear any cached data. + /* 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; + } + + 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; +} + +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) +{ + 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)) { + return DRMP3_FALSE; + } + + /* Clear any cached data. */ + drmp3_reset(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) +{ + 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. + */ + 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. */ + 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); + if (framesRead != frameOffset) { + return DRMP3_FALSE; + } +#endif return DRMP3_TRUE; } @@ -2823,107 +3195,208 @@ drmp3_bool32 drmp3_seek_to_pcm_frame__brute_force(drmp3* pMP3, drmp3_uint64 fram return DRMP3_TRUE; } - // If we're moving foward we just read from where we're at. Otherwise we need to move back to the start of - // the stream and read from the beginning. - drmp3_uint64 framesToReadAndDiscard; - if (frameIndex >= pMP3->currentPCMFrame) { - // Moving foward. - framesToReadAndDiscard = frameIndex - pMP3->currentPCMFrame; - } else { - // Moving backward. Move to the start of the stream and then move forward. - framesToReadAndDiscard = frameIndex; + /* + If we're moving foward we just read from where we're at. Otherwise we need to move back to the start of + the stream and read from the beginning. + */ + if (frameIndex < pMP3->currentPCMFrame) { + /* Moving backward. Move to the start of the stream and then move forward. */ if (!drmp3_seek_to_start_of_stream(pMP3)) { return DRMP3_FALSE; } } - // 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. - drmp3_uint64 maxFramesToReadAndDiscard = DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME * 3; + drmp3_assert(frameIndex >= pMP3->currentPCMFrame); + return drmp3_seek_forward_by_pcm_frames__brute_force(pMP3, (frameIndex - pMP3->currentPCMFrame)); +} - // First get rid of anything that's still sitting in the buffer. - if (framesToReadAndDiscard > maxFramesToReadAndDiscard && framesToReadAndDiscard > pMP3->pcmFramesRemainingInMP3Frame) { - framesToReadAndDiscard -= pMP3->pcmFramesRemainingInMP3Frame; - pMP3->currentPCMFrame += pMP3->pcmFramesRemainingInMP3Frame; - pMP3->pcmFramesConsumedInMP3Frame += pMP3->pcmFramesRemainingInMP3Frame; - pMP3->pcmFramesRemainingInMP3Frame = 0; - } +drmp3_bool32 drmp3_find_closest_seek_point(drmp3* pMP3, drmp3_uint64 frameIndex, drmp3_uint32* pSeekPointIndex) +{ + drmp3_uint32 iSeekPoint; - // Now get rid of leading whole frames. - while (framesToReadAndDiscard > maxFramesToReadAndDiscard) { - drmp3_uint32 pcmFramesSeeked = drmp3_seek_next_frame(pMP3); - if (pcmFramesSeeked == 0) { - break; - } + drmp3_assert(pSeekPointIndex != NULL); - framesToReadAndDiscard -= pcmFramesSeeked; - } + *pSeekPointIndex = 0; - // The last step is to read-and-discard any remaining PCM frames to make it sample-exact. - drmp3_uint64 framesRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadAndDiscard, NULL); - if (framesRead != framesToReadAndDiscard) { + if (frameIndex < pMP3->pSeekPoints[0].pcmFrameIndex) { return DRMP3_FALSE; } + /* Linear search for simplicity to begin with while I'm getting this thing working. Once it's all working change this to a binary search. */ + for (iSeekPoint = 0; iSeekPoint < pMP3->seekPointCount; ++iSeekPoint) { + if (pMP3->pSeekPoints[iSeekPoint].pcmFrameIndex > frameIndex) { + break; /* Found it. */ + } + + *pSeekPointIndex = iSeekPoint; + } + return DRMP3_TRUE; } +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); + + /* 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)) { + seekPoint = pMP3->pSeekPoints[priorSeekPointIndex]; + } else { + seekPoint.seekPosInBytes = 0; + seekPoint.pcmFrameIndex = 0; + seekPoint.mp3FramesToDiscard = 0; + seekPoint.pcmFramesToDiscard = 0; + } + + /* First thing to do is seek to the first byte of the relevant MP3 frame. */ + if (!drmp3__on_seek_64(pMP3, seekPoint.seekPosInBytes, drmp3_seek_origin_start)) { + return DRMP3_FALSE; /* Failed to seek. */ + } + + /* Clear any cached data. */ + drmp3_reset(pMP3); + + /* Whole MP3 frames need to be discarded first. */ + for (iMP3Frame = 0; iMP3Frame < seekPoint.mp3FramesToDiscard; ++iMP3Frame) { + drmp3_uint32 pcmFramesReadPreSRC; + drmp3d_sample_t* pPCMFrames; + + /* Pass in non-null for the last frame because we want to ensure the sample rate converter is preloaded correctly. */ + pPCMFrames = NULL; + if (iMP3Frame == seekPoint.mp3FramesToDiscard-1) { + 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) { + return DRMP3_FALSE; + } + } + + /* 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. + */ + leftoverFrames = frameIndex - pMP3->currentPCMFrame; + return drmp3_seek_forward_by_pcm_frames__brute_force(pMP3, leftoverFrames); +} + drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex) { if (pMP3 == NULL || pMP3->onSeek == NULL) { return DRMP3_FALSE; } - // We currently only support brute force seeking. - return drmp3_seek_to_pcm_frame__brute_force(pMP3, frameIndex); + if (frameIndex == 0) { + return drmp3_seek_to_start_of_stream(pMP3); + } + + /* Use the seek table if we have one. */ + if (pMP3->pSeekPoints != NULL && pMP3->seekPointCount > 0) { + return drmp3_seek_to_pcm_frame__seek_table(pMP3, frameIndex); + } else { + return drmp3_seek_to_pcm_frame__brute_force(pMP3, frameIndex); + } } -drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3) +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 0; + return DRMP3_FALSE; } - // The way this works is we move back to the start of the stream, iterate over each MP3 frame and calculate the frame count based - // on our output sample rate, the seek back to the PCM frame we were sitting on before calling this function. + /* + The way this works is we move back to the start of the stream, iterate over each MP3 frame and calculate the frame count based + on our output sample rate, the seek back to the PCM frame we were sitting on before calling this function. + */ - // The stream must support seeking for this to work. + /* The stream must support seeking for this to work. */ if (pMP3->onSeek == NULL) { - return 0; + return DRMP3_FALSE; } - // We'll need to seek back to where we were, so grab the PCM frame we're currently sitting on so we can restore later. - drmp3_uint64 currentPCMFrame = pMP3->currentPCMFrame; + /* We'll need to seek back to where we were, so grab the PCM frame we're currently sitting on so we can restore later. */ + currentPCMFrame = pMP3->currentPCMFrame; if (!drmp3_seek_to_start_of_stream(pMP3)) { - return 0; + return DRMP3_FALSE; } - drmp3_uint64 totalPCMFrameCount = 0; - float totalPCMFrameCountFractionalPart = 0; // <-- With resampling there will be a fractional part to each MP3 frame that we need to accumulate. + 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 = drmp3_decode_next_frame_ex(pMP3, NULL); // <-- Passing in NULL here will prevent decoding of the MP3 frame which should save time. + drmp3_uint32 pcmFramesInCurrentMP3FrameIn; + float srcRatio; + float pcmFramesInCurrentMP3FrameOutF; + drmp3_uint32 pcmFramesInCurrentMP3FrameOut; + + pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL, DRMP3_FALSE); if (pcmFramesInCurrentMP3FrameIn == 0) { break; } - float srcRatio = (float)pMP3->mp3FrameSampleRate / (float)pMP3->sampleRate; + srcRatio = (float)pMP3->mp3FrameSampleRate / (float)pMP3->sampleRate; drmp3_assert(srcRatio > 0); - float pcmFramesInCurrentMP3FrameOutF = totalPCMFrameCountFractionalPart + (pcmFramesInCurrentMP3FrameIn / srcRatio); - drmp3_uint32 pcmFramesInCurrentMP3FrameOut = (drmp3_uint32)pcmFramesInCurrentMP3FrameOutF; + pcmFramesInCurrentMP3FrameOutF = totalPCMFrameCountFractionalPart + (pcmFramesInCurrentMP3FrameIn / srcRatio); + pcmFramesInCurrentMP3FrameOut = (drmp3_uint32)pcmFramesInCurrentMP3FrameOutF; totalPCMFrameCountFractionalPart = pcmFramesInCurrentMP3FrameOutF - pcmFramesInCurrentMP3FrameOut; totalPCMFrameCount += pcmFramesInCurrentMP3FrameOut; + totalMP3FrameCount += 1; } - // Finally, we need to seek back to where we were. + /* Finally, we need to seek back to where we were. */ if (!drmp3_seek_to_start_of_stream(pMP3)) { - return 0; + return DRMP3_FALSE; } if (!drmp3_seek_to_pcm_frame(pMP3, currentPCMFrame)) { + return DRMP3_FALSE; + } + + if (pMP3FrameCount != NULL) { + *pMP3FrameCount = totalMP3FrameCount; + } + if (pPCMFrameCount != NULL) { + *pPCMFrameCount = totalPCMFrameCount; + } + + return DRMP3_TRUE; +} + +drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3) +{ + drmp3_uint64 totalPCMFrameCount; + if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, NULL, &totalPCMFrameCount)) { return 0; } @@ -2932,56 +3405,204 @@ drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3) drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3) { - if (pMP3 == NULL) { - return 0; - } - - // This works the same way as drmp3_get_pcm_frame_count() - move to the start, count MP3 frames, move back to the previous position. - - // The stream must support seeking for this to work. - if (pMP3->onSeek == NULL) { - return 0; - } - - // We'll need to seek back to where we were, so grab the PCM frame we're currently sitting on so we can restore later. - drmp3_uint64 currentPCMFrame = pMP3->currentPCMFrame; - - if (!drmp3_seek_to_start_of_stream(pMP3)) { - return 0; - } - - drmp3_uint64 totalMP3FrameCount = 0; - for (;;) { - drmp3_uint32 pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL); - if (pcmFramesInCurrentMP3FrameIn == 0) { - break; - } - - totalMP3FrameCount += 1; - } - - // Finally, we need to seek back to where we were. - if (!drmp3_seek_to_start_of_stream(pMP3)) { - return 0; - } - - if (!drmp3_seek_to_pcm_frame(pMP3, currentPCMFrame)) { + drmp3_uint64 totalMP3FrameCount; + if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, &totalMP3FrameCount, NULL)) { return 0; } return totalMP3FrameCount; } +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); + + pcmFrameCountOutF = *pRunningPCMFrameCountFractionalPart + (pcmFrameCountIn / srcRatio); + pcmFrameCountOut = (drmp3_uint32)pcmFrameCountOutF; + *pRunningPCMFrameCountFractionalPart = pcmFrameCountOutF - pcmFrameCountOut; + *pRunningPCMFrameCount += pcmFrameCountOut; +} + +typedef struct +{ + drmp3_uint64 bytePos; + 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_uint32 seekPointCount; + drmp3_uint64 currentPCMFrame; + drmp3_uint64 totalMP3FrameCount; + drmp3_uint64 totalPCMFrameCount; + + if (pMP3 == NULL || pSeekPointCount == NULL || pSeekPoints == NULL) { + return DRMP3_FALSE; /* Invalid args. */ + } + + seekPointCount = *pSeekPointCount; + if (seekPointCount == 0) { + return DRMP3_FALSE; /* The client has requested no seek points. Consider this to be invalid arguments since the client has probably not intended this. */ + } + + /* We'll need to seek back to the current sample after calculating the seekpoints so we need to go ahead and grab the current location at the top. */ + currentPCMFrame = pMP3->currentPCMFrame; + + /* We never do more than the total number of MP3 frames and we limit it to 32-bits. */ + if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, &totalMP3FrameCount, &totalPCMFrameCount)) { + return DRMP3_FALSE; + } + + /* If there's less than DRMP3_SEEK_LEADING_MP3_FRAMES+1 frames we just report 1 seek point which will be the very start of the stream. */ + if (totalMP3FrameCount < DRMP3_SEEK_LEADING_MP3_FRAMES+1) { + seekPointCount = 1; + pSeekPoints[0].seekPosInBytes = 0; + pSeekPoints[0].pcmFrameIndex = 0; + pSeekPoints[0].mp3FramesToDiscard = 0; + pSeekPoints[0].pcmFramesToDiscard = 0; + } else { + drmp3_uint64 pcmFramesBetweenSeekPoints; + drmp3__seeking_mp3_frame_info mp3FrameInfo[DRMP3_SEEK_LEADING_MP3_FRAMES+1]; + drmp3_uint64 runningPCMFrameCount = 0; + float runningPCMFrameCountFractionalPart = 0; + drmp3_uint64 nextTargetPCMFrame; + drmp3_uint32 iMP3Frame; + drmp3_uint32 iSeekPoint; + + if (seekPointCount > totalMP3FrameCount-1) { + seekPointCount = (drmp3_uint32)totalMP3FrameCount-1; + } + + pcmFramesBetweenSeekPoints = totalPCMFrameCount / (seekPointCount+1); + + /* + Here is where we actually calculate the seek points. We need to start by moving the start of the stream. We then enumerate over each + MP3 frame. + */ + if (!drmp3_seek_to_start_of_stream(pMP3)) { + return DRMP3_FALSE; + } + + /* + We need to cache the byte positions of the previous MP3 frames. As a new MP3 frame is iterated, we cycle the byte positions in this + array. The value in the first item in this array is the byte position that will be reported in the next seek point. + */ + + /* We need to initialize the array of MP3 byte positions for the leading MP3 frames. */ + for (iMP3Frame = 0; iMP3Frame < DRMP3_SEEK_LEADING_MP3_FRAMES+1; ++iMP3Frame) { + 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); + 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); + if (pcmFramesInCurrentMP3FrameIn == 0) { + return DRMP3_FALSE; /* This should never happen. */ + } + + drmp3__accumulate_running_pcm_frame_count(pMP3, pcmFramesInCurrentMP3FrameIn, &runningPCMFrameCount, &runningPCMFrameCountFractionalPart); + } + + /* + At this point we will have extracted the byte positions of the leading MP3 frames. We can now start iterating over each seek point and + calculate them. + */ + nextTargetPCMFrame = 0; + for (iSeekPoint = 0; iSeekPoint < seekPointCount; ++iSeekPoint) { + nextTargetPCMFrame += pcmFramesBetweenSeekPoints; + + for (;;) { + if (nextTargetPCMFrame < runningPCMFrameCount) { + /* The next seek point is in the current MP3 frame. */ + pSeekPoints[iSeekPoint].seekPosInBytes = mp3FrameInfo[0].bytePos; + pSeekPoints[iSeekPoint].pcmFrameIndex = nextTargetPCMFrame; + pSeekPoints[iSeekPoint].mp3FramesToDiscard = DRMP3_SEEK_LEADING_MP3_FRAMES; + pSeekPoints[iSeekPoint].pcmFramesToDiscard = (drmp3_uint16)(nextTargetPCMFrame - mp3FrameInfo[DRMP3_SEEK_LEADING_MP3_FRAMES-1].pcmFrameIndex); + break; + } else { + size_t i; + drmp3_uint32 pcmFramesInCurrentMP3FrameIn; + + /* + 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) { + 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; + + /* + 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); + if (pcmFramesInCurrentMP3FrameIn == 0) { + pSeekPoints[iSeekPoint].seekPosInBytes = mp3FrameInfo[0].bytePos; + pSeekPoints[iSeekPoint].pcmFrameIndex = nextTargetPCMFrame; + pSeekPoints[iSeekPoint].mp3FramesToDiscard = DRMP3_SEEK_LEADING_MP3_FRAMES; + pSeekPoints[iSeekPoint].pcmFramesToDiscard = (drmp3_uint16)(nextTargetPCMFrame - mp3FrameInfo[DRMP3_SEEK_LEADING_MP3_FRAMES-1].pcmFrameIndex); + break; + } + + drmp3__accumulate_running_pcm_frame_count(pMP3, pcmFramesInCurrentMP3FrameIn, &runningPCMFrameCount, &runningPCMFrameCountFractionalPart); + } + } + } + + /* Finally, we need to seek back to where we were. */ + if (!drmp3_seek_to_start_of_stream(pMP3)) { + return DRMP3_FALSE; + } + if (!drmp3_seek_to_pcm_frame(pMP3, currentPCMFrame)) { + return DRMP3_FALSE; + } + } + + *pSeekPointCount = seekPointCount; + return DRMP3_TRUE; +} + +drmp3_bool32 drmp3_bind_seek_table(drmp3* pMP3, drmp3_uint32 seekPointCount, drmp3_seek_point* pSeekPoints) +{ + if (pMP3 == NULL) { + return DRMP3_FALSE; + } + + if (seekPointCount == 0 || pSeekPoints == NULL) { + /* Unbinding. */ + pMP3->seekPointCount = 0; + pMP3->pSeekPoints = NULL; + } else { + /* Binding. */ + pMP3->seekPointCount = seekPointCount; + pMP3->pSeekPoints = pSeekPoints; + } + + return DRMP3_TRUE; +} + float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) { - drmp3_assert(pMP3 != NULL); - drmp3_uint64 totalFramesRead = 0; drmp3_uint64 framesCapacity = 0; float* pFrames = NULL; - float temp[4096]; + + drmp3_assert(pMP3 != NULL); + for (;;) { drmp3_uint64 framesToReadRightNow = drmp3_countof(temp) / pMP3->channels; drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadRightNow, temp); @@ -2989,19 +3610,22 @@ float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_ break; } - // Reallocate the output buffer if there's not enough room. + /* Reallocate the output buffer if there's not enough room. */ if (framesCapacity < totalFramesRead + framesJustRead) { + drmp3_uint64 newFramesBufferSize; + float* pNewFrames; + framesCapacity *= 2; if (framesCapacity < totalFramesRead + framesJustRead) { framesCapacity = totalFramesRead + framesJustRead; } - drmp3_uint64 newFramesBufferSize = framesCapacity*pMP3->channels*sizeof(float); + newFramesBufferSize = framesCapacity*pMP3->channels*sizeof(float); if (newFramesBufferSize > DRMP3_SIZE_MAX) { break; } - float* pNewFrames = (float*)drmp3_realloc(pFrames, (size_t)newFramesBufferSize); + pNewFrames = (float*)drmp3_realloc(pFrames, (size_t)newFramesBufferSize); if (pNewFrames == NULL) { drmp3_free(pFrames); break; @@ -3013,7 +3637,7 @@ float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_ 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. + /* If the number of frames we asked for is less that what we actually read it means we've reached the end. */ if (framesJustRead != framesToReadRightNow) { break; } @@ -3026,10 +3650,77 @@ float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_ drmp3_uninit(pMP3); - if (pTotalFrameCount) *pTotalFrameCount = totalFramesRead; + if (pTotalFrameCount) { + *pTotalFrameCount = totalFramesRead; + } + return pFrames; } +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); + + for (;;) { + drmp3_uint64 framesToReadRightNow = drmp3_countof(temp) / pMP3->channels; + drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_s16(pMP3, framesToReadRightNow, temp); + if (framesJustRead == 0) { + break; + } + + /* Reallocate the output buffer if there's not enough room. */ + if (framesCapacity < totalFramesRead + framesJustRead) { + drmp3_uint64 newFramesBufferSize; + drmp3_int16* pNewFrames; + + framesCapacity *= 2; + if (framesCapacity < totalFramesRead + framesJustRead) { + framesCapacity = totalFramesRead + framesJustRead; + } + + newFramesBufferSize = framesCapacity*pMP3->channels*sizeof(drmp3_int16); + if (newFramesBufferSize > DRMP3_SIZE_MAX) { + break; + } + + pNewFrames = (drmp3_int16*)drmp3_realloc(pFrames, (size_t)newFramesBufferSize); + if (pNewFrames == NULL) { + drmp3_free(pFrames); + break; + } + + pFrames = pNewFrames; + } + + 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. */ + if (framesJustRead != framesToReadRightNow) { + break; + } + } + + if (pConfig != NULL) { + pConfig->outputChannels = pMP3->channels; + pConfig->outputSampleRate = pMP3->sampleRate; + } + + drmp3_uninit(pMP3); + + if (pTotalFrameCount) { + *pTotalFrameCount = totalFramesRead; + } + + return pFrames; +} + + float* drmp3_open_and_read_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) { drmp3 mp3; @@ -3040,6 +3731,17 @@ float* drmp3_open_and_read_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, v 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 mp3; + if (!drmp3_init(&mp3, onRead, onSeek, pUserData, pConfig)) { + return NULL; + } + + return drmp3__full_read_and_close_s16(&mp3, pConfig, pTotalFrameCount); +} + + float* drmp3_open_memory_and_read_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) { drmp3 mp3; @@ -3050,6 +3752,17 @@ float* drmp3_open_memory_and_read_f32(const void* pData, size_t dataSize, drmp3_ 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 mp3; + if (!drmp3_init_memory(&mp3, pData, dataSize, pConfig)) { + return NULL; + } + + return drmp3__full_read_and_close_s16(&mp3, pConfig, pTotalFrameCount); +} + + #ifndef DR_MP3_NO_STDIO float* drmp3_open_file_and_read_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) { @@ -3060,6 +3773,16 @@ float* drmp3_open_file_and_read_f32(const char* filePath, drmp3_config* pConfig, 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 mp3; + if (!drmp3_init_file(&mp3, filePath, pConfig)) { + return NULL; + } + + return drmp3__full_read_and_close_s16(&mp3, pConfig, pTotalFrameCount); +} #endif void drmp3_free(void* p) @@ -3069,127 +3792,181 @@ void drmp3_free(void* p) #endif /*DR_MP3_IMPLEMENTATION*/ - -// DIFFERENCES BETWEEN minimp3 AND dr_mp3 -// ====================================== -// - First, keep in mind that minimp3 (https://github.com/lieff/minimp3) is where all the real work was done. All of the -// code relating to the actual decoding remains mostly unmodified, apart from some namespacing changes. -// - dr_mp3 adds a pulling style API which allows you to deliver raw data via callbacks. So, rather than pushing data -// to the decoder, the decoder _pulls_ data from your callbacks. -// - In addition to callbacks, a decoder can be initialized from a block of memory and a file. -// - The dr_mp3 pull API reads PCM frames rather than whole MP3 frames. -// - dr_mp3 adds convenience APIs for opening and decoding entire files in one go. -// - dr_mp3 is fully namespaced, including the implementation section, which is more suitable when compiling projects -// as a single translation unit (aka unity builds). At the time of writing this, a unity build is not possible when -// using minimp3 in conjunction with stb_vorbis. dr_mp3 addresses this. - - -// REVISION HISTORY -// ================ -// -// v0.4.0 - 2018-xx-xx -// - 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 -// - Add drmp3_get_pcm_frame_count(). -// - Add drmp3_get_mp3_frame_count(). -// - Improve seeking performance. -// -// v0.3.2 - 2018-09-11 -// - Fix a couple of memory leaks. -// - Bring up to date with minimp3. -// -// v0.3.1 - 2018-08-25 -// - Fix C++ build. -// -// v0.3.0 - 2018-08-25 -// - Bring up to date with minimp3. This has a minor API change: the "pcm" parameter of drmp3dec_decode_frame() has -// been changed from short* to void* because it can now output both s16 and f32 samples, depending on whether or -// not the DR_MP3_FLOAT_OUTPUT option is set. -// -// v0.2.11 - 2018-08-08 -// - Fix a bug where the last part of a file is not read. -// -// v0.2.10 - 2018-08-07 -// - Improve 64-bit detection. -// -// v0.2.9 - 2018-08-05 -// - Fix C++ build on older versions of GCC. -// - Bring up to date with minimp3. -// -// v0.2.8 - 2018-08-02 -// - Fix compilation errors with older versions of GCC. -// -// v0.2.7 - 2018-07-13 -// - Bring up to date with minimp3. -// -// v0.2.6 - 2018-07-12 -// - Bring up to date with minimp3. -// -// v0.2.5 - 2018-06-22 -// - Bring up to date with minimp3. -// -// v0.2.4 - 2018-05-12 -// - Bring up to date with minimp3. -// -// v0.2.3 - 2018-04-29 -// - Fix TCC build. -// -// v0.2.2 - 2018-04-28 -// - Fix bug when opening a decoder from memory. -// -// v0.2.1 - 2018-04-27 -// - Efficiency improvements when the decoder reaches the end of the stream. -// -// v0.2 - 2018-04-21 -// - Bring up to date with minimp3. -// - Start using major.minor.revision versioning. -// -// v0.1d - 2018-03-30 -// - Bring up to date with minimp3. -// -// v0.1c - 2018-03-11 -// - Fix C++ build error. -// -// v0.1b - 2018-03-07 -// - Bring up to date with minimp3. -// -// v0.1a - 2018-02-28 -// - Fix compilation error on GCC/Clang. -// - Fix some warnings. -// -// v0.1 - 2018-02-xx -// - Initial versioned release. - +/* +DIFFERENCES BETWEEN minimp3 AND dr_mp3 +====================================== +- First, keep in mind that minimp3 (https://github.com/lieff/minimp3) is where all the real work was done. All of the + code relating to the actual decoding remains mostly unmodified, apart from some namespacing changes. +- dr_mp3 adds a pulling style API which allows you to deliver raw data via callbacks. So, rather than pushing data + to the decoder, the decoder _pulls_ data from your callbacks. +- In addition to callbacks, a decoder can be initialized from a block of memory and a file. +- The dr_mp3 pull API reads PCM frames rather than whole MP3 frames. +- dr_mp3 adds convenience APIs for opening and decoding entire files in one go. +- dr_mp3 is fully namespaced, including the implementation section, which is more suitable when compiling projects + as a single translation unit (aka unity builds). At the time of writing this, a unity build is not possible when + using minimp3 in conjunction with stb_vorbis. dr_mp3 addresses this. +*/ /* +REVISION HISTORY +================ +v0.4.7 - 2019-07-28 + - Fix a compiler error. + +v0.4.6 - 2019-06-14 + - Fix a compiler error. + +v0.4.5 - 2019-06-06 + - Bring up to date with minimp3. + +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. + - 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 + - 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. + +v0.4.2 - 2019-02-21 + - Fix a warning. + +v0.4.1 - 2018-12-30 + - Fix a warning. + +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 + - Add drmp3_get_pcm_frame_count(). + - Add drmp3_get_mp3_frame_count(). + - Improve seeking performance. + +v0.3.2 - 2018-09-11 + - Fix a couple of memory leaks. + - Bring up to date with minimp3. + +v0.3.1 - 2018-08-25 + - Fix C++ build. + +v0.3.0 - 2018-08-25 + - Bring up to date with minimp3. This has a minor API change: the "pcm" parameter of drmp3dec_decode_frame() has + been changed from short* to void* because it can now output both s16 and f32 samples, depending on whether or + not the DR_MP3_FLOAT_OUTPUT option is set. + +v0.2.11 - 2018-08-08 + - Fix a bug where the last part of a file is not read. + +v0.2.10 - 2018-08-07 + - Improve 64-bit detection. + +v0.2.9 - 2018-08-05 + - Fix C++ build on older versions of GCC. + - Bring up to date with minimp3. + +v0.2.8 - 2018-08-02 + - Fix compilation errors with older versions of GCC. + +v0.2.7 - 2018-07-13 + - Bring up to date with minimp3. + +v0.2.6 - 2018-07-12 + - Bring up to date with minimp3. + +v0.2.5 - 2018-06-22 + - Bring up to date with minimp3. + +v0.2.4 - 2018-05-12 + - Bring up to date with minimp3. + +v0.2.3 - 2018-04-29 + - Fix TCC build. + +v0.2.2 - 2018-04-28 + - Fix bug when opening a decoder from memory. + +v0.2.1 - 2018-04-27 + - Efficiency improvements when the decoder reaches the end of the stream. + +v0.2 - 2018-04-21 + - Bring up to date with minimp3. + - Start using major.minor.revision versioning. + +v0.1d - 2018-03-30 + - Bring up to date with minimp3. + +v0.1c - 2018-03-11 + - Fix C++ build error. + +v0.1b - 2018-03-07 + - Bring up to date with minimp3. + +v0.1a - 2018-02-28 + - Fix compilation error on GCC/Clang. + - Fix some warnings. + +v0.1 - 2018-02-xx + - Initial versioned release. +*/ + +/* +This software is available as a choice of the following licenses. Choose +whichever you prefer. + +=============================================================================== +ALTERNATIVE 1 - 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. +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. +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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to + +=============================================================================== +ALTERNATIVE 2 - MIT No Attribution +=============================================================================== +Copyright 2018 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 +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. + +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. */ /* diff --git a/raylib/external/dr_wav.h b/raylib/external/dr_wav.h index b071d00..7c48ac4 100644 --- a/raylib/external/dr_wav.h +++ b/raylib/external/dr_wav.h @@ -1,115 +1,154 @@ -// WAV audio loader and writer. Public domain. See "unlicense" statement at the end of this file. -// dr_wav - v0.8.1 - 2018-06-29 -// -// David Reid - mackron@gmail.com +/* +WAV audio loader and writer. Choice of public domain or MIT-0. See license statements at the end of this file. +dr_wav - v0.9.2 - 2019-05-21 -// USAGE -// -// This is a single-file library. To use it, do something like the following in one .c file. -// #define DR_WAV_IMPLEMENTATION -// #include "dr_wav.h" -// -// You can then #include this file in other parts of the program as you would with any other header file. Do something -// like the following to read audio data: -// -// drwav wav; -// if (!drwav_init_file(&wav, "my_song.wav")) { -// // Error opening WAV file. -// } -// -// drwav_int32* pDecodedInterleavedSamples = malloc(wav.totalSampleCount * sizeof(drwav_int32)); -// size_t numberOfSamplesActuallyDecoded = drwav_read_s32(&wav, wav.totalSampleCount, pDecodedInterleavedSamples); -// -// ... -// -// drwav_uninit(&wav); -// -// You can also use drwav_open() to allocate and initialize the loader for you: -// -// drwav* pWav = drwav_open_file("my_song.wav"); -// if (pWav == NULL) { -// // Error opening WAV file. -// } -// -// ... -// -// drwav_close(pWav); -// -// If you just want to quickly open and read the audio data in a single operation you can do something like this: -// -// unsigned int channels; -// unsigned int sampleRate; -// drwav_uint64 totalSampleCount; -// float* pSampleData = drwav_open_and_read_file_s32("my_song.wav", &channels, &sampleRate, &totalSampleCount); -// if (pSampleData == NULL) { -// // Error opening and reading WAV file. -// } -// -// ... -// -// drwav_free(pSampleData); -// -// 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 audio data in its internal format (see notes below for supported formats): -// -// size_t samplesRead = drwav_read(&wav, wav.totalSampleCount, pDecodedInterleavedSamples); -// -// You can also read the raw bytes of audio data, which could be useful if dr_wav does not have native support for -// a particular data format: -// -// size_t bytesRead = drwav_read_raw(&wav, bytesToRead, pRawDataBuffer); -// -// -// dr_wav has seamless support the Sony Wave64 format. The decoder will automatically detect it and it should Just Work -// without any manual intervention. -// -// -// dr_wav can also be used to output WAV files. This does not currently support compressed formats. To use this, look at -// drwav_open_write(), drwav_open_file_write(), etc. Use drwav_write() to write samples, or drwav_write_raw() to write -// raw data in the "data" chunk. -// -// drwav_data_format format; -// 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.channels = 2; -// format.sampleRate = 44100; -// format.bitsPerSample = 16; -// drwav* pWav = drwav_open_file_write("data/recording.wav", &format); -// -// ... -// -// drwav_uint64 samplesWritten = drwav_write(pWav, sampleCount, pSamples); -// -// -// -// OPTIONS -// #define these options before including this file. -// -// #define DR_WAV_NO_CONVERSION_API -// Disables conversion APIs such as drwav_read_f32() and drwav_s16_to_f32(). -// -// #define DR_WAV_NO_STDIO -// Disables drwav_open_file(), drwav_open_file_write(), etc. -// -// -// -// QUICK NOTES -// - Samples are always interleaved. -// - The default read function does not do any data conversion. Use drwav_read_f32() to read and convert audio data -// to IEEE 32-bit floating point samples, drwav_read_s32() to read samples as signed 32-bit PCM and drwav_read_s16() -// to read samples as signed 16-bit PCM. Tested and supported internal formats include the following: -// - Unsigned 8-bit PCM -// - Signed 12-bit PCM -// - Signed 16-bit PCM -// - Signed 24-bit PCM -// - Signed 32-bit PCM -// - IEEE 32-bit floating point -// - IEEE 64-bit floating point -// - A-law and u-law -// - Microsoft ADPCM -// - IMA ADPCM (DVI, format code 0x11) -// - dr_wav will try to read the WAV file as best it can, even if it's not strictly conformant to the WAV format. +David Reid - mackron@gmail.com +*/ +/* +DEPRECATED APIS +=============== +Version 0.9.0 deprecated the per-sample reading and seeking APIs and replaced them with versions that work on the resolution +of a PCM frame instead. For example, given a stereo WAV file, previously you would pass 2 to drwav_read_f32() to read one +PCM frame, whereas now you would pass in 1 to drwav_read_pcm_frames_f32(). The old APIs would return the number of samples +read, whereas now it will return the number of PCM frames. Below is a list of APIs that have been deprecated and their +replacements. + + drwav_read() -> drwav_read_pcm_frames() + drwav_read_s16() -> drwav_read_pcm_frames_s16() + drwav_read_f32() -> drwav_read_pcm_frames_f32() + drwav_read_s32() -> drwav_read_pcm_frames_s32() + drwav_seek_to_sample() -> drwav_seek_to_pcm_frame() + drwav_write() -> drwav_write_pcm_frames() + drwav_open_and_read_s16() -> drwav_open_and_read_pcm_frames_s16() + drwav_open_and_read_f32() -> drwav_open_and_read_pcm_frames_f32() + drwav_open_and_read_s32() -> drwav_open_and_read_pcm_frames_s32() + drwav_open_file_and_read_s16() -> drwav_open_file_and_read_pcm_frames_s16() + drwav_open_file_and_read_f32() -> drwav_open_file_and_read_pcm_frames_f32() + drwav_open_file_and_read_s32() -> drwav_open_file_and_read_pcm_frames_s32() + drwav_open_memory_and_read_s16() -> drwav_open_memory_and_read_pcm_frames_s16() + drwav_open_memory_and_read_f32() -> drwav_open_memory_and_read_pcm_frames_f32() + drwav_open_memory_and_read_s32() -> drwav_open_memory_and_read_pcm_frames_s32() + drwav::totalSampleCount -> drwav::totalPCMFrameCount + +Rationale: + 1) Most programs will want to read in multiples of the channel count which demands a per-frame reading API. Per-sample + reading just adds complexity and maintenance costs for no practical benefit. + 2) This is consistent with my other decoders - dr_flac and dr_mp3. + +These APIs will be removed completely in version 0.10.0. You can continue to use drwav_read_raw() if you need per-sample +reading. +*/ + +/* +USAGE +===== +This is a single-file library. To use it, do something like the following in one .c file. + #define DR_WAV_IMPLEMENTATION + #include "dr_wav.h" + +You can then #include this file in other parts of the program as you would with any other header file. Do something +like the following to read audio data: + + drwav wav; + if (!drwav_init_file(&wav, "my_song.wav")) { + // Error opening WAV file. + } + + drwav_int32* pDecodedInterleavedSamples = malloc(wav.totalPCMFrameCount * wav.channels * sizeof(drwav_int32)); + size_t numberOfSamplesActuallyDecoded = drwav_read_pcm_frames_s32(&wav, wav.totalPCMFrameCount, pDecodedInterleavedSamples); + + ... + + drwav_uninit(&wav); + +You can also use drwav_open() to allocate and initialize the loader for you: + + drwav* pWav = drwav_open_file("my_song.wav"); + if (pWav == NULL) { + // Error opening WAV file. + } + + ... + + drwav_close(pWav); + +If you just want to quickly open and read the audio data in a single operation you can do something like this: + + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalPCMFrameCount; + float* pSampleData = drwav_open_file_and_read_pcm_frames_f32("my_song.wav", &channels, &sampleRate, &totalPCMFrameCount); + if (pSampleData == NULL) { + // Error opening and reading WAV file. + } + + ... + + drwav_free(pSampleData); + +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 audio data in its internal format (see notes below for supported formats): + + size_t samplesRead = drwav_read_pcm_frames(&wav, wav.totalPCMFrameCount, pDecodedInterleavedSamples); + +You can also read the raw bytes of audio data, which could be useful if dr_wav does not have native support for +a particular data format: + + size_t bytesRead = drwav_read_raw(&wav, bytesToRead, pRawDataBuffer); + + +dr_wav can also be used to output WAV files. This does not currently support compressed formats. To use this, look at +drwav_open_write(), drwav_open_file_write(), etc. Use drwav_write_pcm_frames() to write samples, or drwav_write_raw() +to write raw data in the "data" chunk. + + drwav_data_format format; + 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.channels = 2; + format.sampleRate = 44100; + format.bitsPerSample = 16; + drwav* pWav = drwav_open_file_write("data/recording.wav", &format); + + ... + + drwav_uint64 samplesWritten = drwav_write_pcm_frames(pWav, frameCount, pSamples); + + +dr_wav has seamless support the Sony Wave64 format. The decoder will automatically detect it and it should Just Work +without any manual intervention. + + +OPTIONS +======= +#define these options before including this file. + +#define DR_WAV_NO_CONVERSION_API + Disables conversion APIs such as drwav_read_pcm_frames_f32() and drwav_s16_to_f32(). + +#define DR_WAV_NO_STDIO + Disables drwav_open_file(), drwav_open_file_write(), etc. + + + +QUICK NOTES +=========== +- Samples are always interleaved. +- The default read function does not do any data conversion. Use drwav_read_pcm_frames_f32(), drwav_read_pcm_frames_s32() + and drwav_read_pcm_frames_s16() to read and convert audio data to 32-bit floating point, signed 32-bit integer and + signed 16-bit integer samples respectively. Tested and supported internal formats include the following: + - Unsigned 8-bit PCM + - Signed 12-bit PCM + - Signed 16-bit PCM + - Signed 24-bit PCM + - Signed 32-bit PCM + - IEEE 32-bit floating point + - IEEE 64-bit floating point + - A-law and u-law + - Microsoft ADPCM + - IMA ADPCM (DVI, format code 0x11) +- dr_wav will try to read the WAV file as best it can, even if it's not strictly conformant to the WAV format. +*/ #ifndef dr_wav_h #define dr_wav_h @@ -145,7 +184,7 @@ typedef drwav_uint32 drwav_bool32; extern "C" { #endif -// Common data formats. +/* Common data formats. */ #define DR_WAVE_FORMAT_PCM 0x1 #define DR_WAVE_FORMAT_ADPCM 0x2 #define DR_WAVE_FORMAT_IEEE_FLOAT 0x3 @@ -154,6 +193,14 @@ extern "C" { #define DR_WAVE_FORMAT_DVI_ADPCM 0x11 #define DR_WAVE_FORMAT_EXTENSIBLE 0xFFFE +/* Constants. */ +#ifndef DRWAV_MAX_SMPL_LOOPS +#define DRWAV_MAX_SMPL_LOOPS 1 +#endif + +/* Flags to pass into drwav_init_ex(), etc. */ +#define DRWAV_SEQUENTIAL 0x00000001 + typedef enum { drwav_seek_origin_start, @@ -166,42 +213,84 @@ typedef enum drwav_container_w64 } drwav_container; -// Callback for when data is read. Return value is the number of bytes actually read. -// -// pUserData [in] The user data that was passed to drwav_init(), drwav_open() and family. -// pBufferOut [out] The output buffer. -// bytesToRead [in] The number of bytes to read. -// -// Returns the number of bytes actually read. -// -// 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 struct +{ + union + { + drwav_uint8 fourcc[4]; + drwav_uint8 guid[16]; + } id; + + /* The size in bytes of the chunk. */ + drwav_uint64 sizeInBytes; + + /* + RIFF = 2 byte alignment. + W64 = 8 byte alignment. + */ + unsigned int paddingSize; +} drwav_chunk_header; + +/* +Callback for when data is read. Return value is the number of bytes actually read. + +pUserData [in] The user data that was passed to drwav_init(), drwav_open() and family. +pBufferOut [out] The output buffer. +bytesToRead [in] The number of bytes to read. + +Returns the number of bytes actually read. + +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 (* drwav_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead); -// Callback for when data is written. Returns value is the number of bytes actually written. -// -// pUserData [in] The user data that was passed to drwav_init_write(), drwav_open_write() and family. -// pData [out] A pointer to the data to write. -// bytesToWrite [in] The number of bytes to write. -// -// Returns the number of bytes actually written. -// -// If the return value differs from bytesToWrite, it indicates an error. +/* +Callback for when data is written. Returns value is the number of bytes actually written. + +pUserData [in] The user data that was passed to drwav_init_write(), drwav_open_write() and family. +pData [out] A pointer to the data to write. +bytesToWrite [in] The number of bytes to write. + +Returns the number of bytes actually written. + +If the return value differs from bytesToWrite, it indicates an error. +*/ typedef size_t (* drwav_write_proc)(void* pUserData, const void* pData, size_t bytesToWrite); -// Callback for when data needs to be seeked. -// -// pUserData [in] The user data that was passed to drwav_init(), drwav_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. -// -// Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which -// will be either drwav_seek_origin_start or drwav_seek_origin_current. +/* +Callback for when data needs to be seeked. + +pUserData [in] The user data that was passed to drwav_init(), drwav_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. + +Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which +will be either drwav_seek_origin_start or drwav_seek_origin_current. +*/ typedef drwav_bool32 (* drwav_seek_proc)(void* pUserData, int offset, drwav_seek_origin origin); -// Structure for internal use. Only used for loaders opened with drwav_open_memory(). +/* +Callback for when drwav_init_ex/drwav_open_ex finds a chunk. + +pChunkUserData [in] The user data that was passed to the pChunkUserData parameter of drwav_init_ex(), drwav_open_ex() and family. +onRead [in] A pointer to the function to call when reading. +onSeek [in] A pointer to the function to call when seeking. +pReadSeekUserData [in] The user data that was passed to the pReadSeekUserData parameter of drwav_init_ex(), drwav_open_ex() and family. +pChunkHeader [in] A pointer to an object containing basic header information about the chunk. Use this to identify the chunk. + +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. + +You must not attempt to read beyond the boundary of the chunk. +*/ +typedef drwav_uint64 (* drwav_chunk_proc)(void* pChunkUserData, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pReadSeekUserData, const drwav_chunk_header* pChunkHeader); + +/* Structure for internal use. Only used for loaders opened with drwav_open_memory(). */ typedef struct { const drwav_uint8* data; @@ -209,7 +298,7 @@ typedef struct size_t currentReadPos; } drwav__memory_stream; -// Structure for internal use. Only used for writers opened with drwav_open_memory_write(). +/* Structure for internal use. Only used for writers opened with drwav_open_memory_write(). */ typedef struct { void** ppData; @@ -221,8 +310,8 @@ typedef struct typedef struct { - drwav_container container; // RIFF, W64. - drwav_uint32 format; // DR_WAVE_FORMAT_* + drwav_container container; /* RIFF, W64. */ + drwav_uint32 format; /* DR_WAVE_FORMAT_* */ drwav_uint32 channels; drwav_uint32 sampleRate; drwav_uint32 bitsPerSample; @@ -230,473 +319,591 @@ typedef struct typedef struct { - // The format tag exactly as specified in the wave file's "fmt" chunk. This can be used by applications - // that require support for data formats not natively supported by dr_wav. + /* + The format tag exactly as specified in the wave file's "fmt" chunk. This can be used by applications + that require support for data formats not natively supported by dr_wav. + */ drwav_uint16 formatTag; - // The number of channels making up the audio data. When this is set to 1 it is mono, 2 is stereo, etc. + /* The number of channels making up the audio data. When this is set to 1 it is mono, 2 is stereo, etc. */ drwav_uint16 channels; - // The sample rate. Usually set to something like 44100. + /* The sample rate. Usually set to something like 44100. */ drwav_uint32 sampleRate; - // Average bytes per second. You probably don't need this, but it's left here for informational purposes. + /* Average bytes per second. You probably don't need this, but it's left here for informational purposes. */ drwav_uint32 avgBytesPerSec; - // Block align. This is equal to the number of channels * bytes per sample. + /* Block align. This is equal to the number of channels * bytes per sample. */ drwav_uint16 blockAlign; - // Bits per sample. + /* Bits per sample. */ drwav_uint16 bitsPerSample; - // The size of the extended data. Only used internally for validation, but left here for informational purposes. + /* The size of the extended data. Only used internally for validation, but left here for informational purposes. */ drwav_uint16 extendedSize; - // The number of valid bits per sample. When is equal to WAVE_FORMAT_EXTENSIBLE, - // is always rounded up to the nearest multiple of 8. This variable contains information about exactly how - // many bits a valid per sample. Mainly used for informational purposes. + /* + The number of valid bits per sample. When is equal to WAVE_FORMAT_EXTENSIBLE, + is always rounded up to the nearest multiple of 8. This variable contains information about exactly how + many bits a valid per sample. Mainly used for informational purposes. + */ drwav_uint16 validBitsPerSample; - // The channel mask. Not used at the moment. + /* The channel mask. Not used at the moment. */ drwav_uint32 channelMask; - // The sub-format, exactly as specified by the wave file. + /* The sub-format, exactly as specified by the wave file. */ drwav_uint8 subFormat[16]; } drwav_fmt; typedef struct { - // A pointer to the function to call when more data is needed. + drwav_uint32 cuePointId; + drwav_uint32 type; + drwav_uint32 start; + drwav_uint32 end; + drwav_uint32 fraction; + drwav_uint32 playCount; +} drwav_smpl_loop; + + typedef struct +{ + drwav_uint32 manufacturer; + drwav_uint32 product; + drwav_uint32 samplePeriod; + drwav_uint32 midiUnityNotes; + drwav_uint32 midiPitchFraction; + drwav_uint32 smpteFormat; + drwav_uint32 smpteOffset; + drwav_uint32 numSampleLoops; + drwav_uint32 samplerData; + drwav_smpl_loop loops[DRWAV_MAX_SMPL_LOOPS]; +} drwav_smpl; + +typedef struct +{ + /* A pointer to the function to call when more data is needed. */ drwav_read_proc onRead; - // A pointer to the function to call when data needs to be written. Only used when the drwav object is opened in write mode. + /* A pointer to the function to call when data needs to be written. Only used when the drwav object is opened in write mode. */ drwav_write_proc onWrite; - // A pointer to the function to call when the wav file needs to be seeked. + /* A pointer to the function to call when the wav file needs to be seeked. */ drwav_seek_proc onSeek; - // The user data to pass to callbacks. + /* The user data to pass to callbacks. */ void* pUserData; - // Whether or not the WAV file is formatted as a standard RIFF file or W64. + /* Whether or not the WAV file is formatted as a standard RIFF file or W64. */ drwav_container container; - // Structure containing format information exactly as specified by the wav file. + /* Structure containing format information exactly as specified by the wav file. */ drwav_fmt fmt; - // The sample rate. Will be set to something like 44100. + /* The sample rate. Will be set to something like 44100. */ drwav_uint32 sampleRate; - // The number of channels. This will be set to 1 for monaural streams, 2 for stereo, etc. + /* The number of channels. This will be set to 1 for monaural streams, 2 for stereo, etc. */ drwav_uint16 channels; - // The bits per sample. Will be set to something like 16, 24, etc. + /* The bits per sample. Will be set to something like 16, 24, etc. */ drwav_uint16 bitsPerSample; - // The number of bytes per sample. - drwav_uint16 bytesPerSample; - - // Equal to fmt.formatTag, or the value specified by fmt.subFormat if fmt.formatTag is equal to 65534 (WAVE_FORMAT_EXTENSIBLE). + /* Equal to fmt.formatTag, or the value specified by fmt.subFormat if fmt.formatTag is equal to 65534 (WAVE_FORMAT_EXTENSIBLE). */ drwav_uint16 translatedFormatTag; - // The total number of samples making up the audio data. Use * to calculate - // the required size of a buffer to hold the entire audio data. - drwav_uint64 totalSampleCount; + /* The total number of PCM frames making up the audio data. */ + drwav_uint64 totalPCMFrameCount; - // The size in bytes of the data chunk. + /* 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 byte of the data chunk. This is used for seeking. */ drwav_uint64 dataChunkDataPos; - // The number of bytes remaining in the data chunk. + /* The number of bytes remaining in the data chunk. */ drwav_uint64 bytesRemaining; - // Only used in sequential write mode. Keeps track of the desired size of the "data" chunk at the point of initialization time. Always - // set to 0 for non-sequential writes and when the drwav object is opened in read mode. Used for validation. + /* + Only used in sequential write mode. Keeps track of the desired size of the "data" chunk at the point of initialization time. Always + set to 0 for non-sequential writes and when the drwav object is opened in read mode. Used for validation. + */ drwav_uint64 dataChunkDataSizeTargetWrite; - // Keeps track of whether or not the wav writer was initialized in sequential mode. + /* Keeps track of whether or not the wav writer was initialized in sequential mode. */ drwav_bool32 isSequentialWrite; - // A hack to avoid a DRWAV_MALLOC() when opening a decoder with drwav_open_memory(). + /* smpl chunk. */ + drwav_smpl smpl; + + + /* A hack to avoid a DRWAV_MALLOC() when opening a decoder with drwav_open_memory(). */ drwav__memory_stream memoryStream; drwav__memory_stream_write memoryStreamWrite; - // Generic data for compressed formats. This data is shared across all block-compressed formats. + /* Generic data for compressed formats. This data is shared across all block-compressed formats. */ struct { - drwav_uint64 iCurrentSample; // The index of the next sample that will be read by drwav_read_*(). This is used with "totalSampleCount" to ensure we don't read excess samples at the end of the last block. + drwav_uint64 iCurrentSample; /* The index of the next sample that will be read by drwav_read_*(). This is used with "totalSampleCount" to ensure we don't read excess samples at the end of the last block. */ } compressed; - // Microsoft ADPCM specific data. + /* Microsoft ADPCM specific data. */ struct { drwav_uint32 bytesRemainingInBlock; drwav_uint16 predictor[2]; drwav_int32 delta[2]; - drwav_int32 cachedSamples[4]; // Samples are stored in this cache during decoding. + drwav_int32 cachedSamples[4]; /* Samples are stored in this cache during decoding. */ drwav_uint32 cachedSampleCount; - drwav_int32 prevSamples[2][2]; // The previous 2 samples for each channel (2 channels at most). + drwav_int32 prevSamples[2][2]; /* The previous 2 samples for each channel (2 channels at most). */ } msadpcm; - // IMA ADPCM specific data. + /* IMA ADPCM specific data. */ struct { drwav_uint32 bytesRemainingInBlock; drwav_int32 predictor[2]; drwav_int32 stepIndex[2]; - drwav_int32 cachedSamples[16]; // Samples are stored in this cache during decoding. + drwav_int32 cachedSamples[16]; /* Samples are stored in this cache during decoding. */ drwav_uint32 cachedSampleCount; } ima; + + + drwav_uint64 totalSampleCount; /* <-- DEPRECATED. Will be removed in a future version. */ } drwav; -// Initializes a pre-allocated drwav object. -// -// 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. -// -// Returns true if successful; false otherwise. -// -// Close the loader with drwav_uninit(). -// -// This is the lowest level function for initializing a WAV file. You can also use drwav_init_file() and drwav_init_memory() -// to open the stream from a file or from a block of memory respectively. -// -// If you want dr_wav to manage the memory allocation for you, consider using drwav_open() instead. This will allocate -// a drwav object on the heap and return a pointer to it. -// -// See also: drwav_init_file(), drwav_init_memory(), drwav_uninit() -drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData); +/* +Initializes a pre-allocated drwav object. -// Initializes a pre-allocated drwav object for writing. -// -// onWrite [in] The function to call when data needs to be written. -// onSeek [in] The function to call when the write position needs to move. -// pUserData [in, optional] A pointer to application defined data that will be passed to onWrite and onSeek. -// -// Returns true if successful; false otherwise. -// -// Close the writer with drwav_uninit(). -// -// This is the lowest level function for initializing a WAV file. You can also use drwav_init_file() and drwav_init_memory() -// to open the stream from a file or from a block of memory respectively. -// -// If the total sample count is known, you can use drwav_init_write_sequential(). This avoids the need for dr_wav to perform -// a post-processing step for storing the total sample count and the size of the data chunk which requires a backwards seek. -// -// If you want dr_wav to manage the memory allocation for you, consider using drwav_open() instead. This will allocate -// a drwav object on the heap and return a pointer to it. -// -// See also: drwav_init_file_write(), drwav_init_memory_write(), drwav_uninit() +pWav [out] A pointer to the drwav object being initialized. +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. +onChunk [in, optional] The function to call when a chunk is enumerated at initialized time. +pUserData, pReadSeekUserData [in, optional] A pointer to application defined data that will be passed to onRead and onSeek. +pChunkUserData [in, optional] A pointer to application defined data that will be passed to onChunk. +flags [in, optional] A set of flags for controlling how things are loaded. + +Returns true if successful; false otherwise. + +Close the loader with drwav_uninit(). + +This is the lowest level function for initializing a WAV file. You can also use drwav_init_file() and drwav_init_memory() +to open the stream from a file or from a block of memory respectively. + +If you want dr_wav to manage the memory allocation for you, consider using drwav_open() instead. This will allocate +a drwav object on the heap and return a pointer to it. + +Possible values for flags: + DRWAV_SEQUENTIAL: Never perform a backwards seek while loading. This disables the chunk callback and will cause this function + to return as soon as the data chunk is found. Any chunks after the data chunk will be ignored. + +drwav_init() is equivalent to "drwav_init_ex(pWav, onRead, onSeek, NULL, pUserData, NULL, 0);". + +The onChunk callback is not called for the WAVE or FMT chunks. The contents of the FMT chunk can be read from pWav->fmt +after the function returns. + +See also: drwav_init_file(), drwav_init_memory(), drwav_uninit() +*/ +drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData); +drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags); + +/* +Initializes a pre-allocated drwav object for writing. + +onWrite [in] The function to call when data needs to be written. +onSeek [in] The function to call when the write position needs to move. +pUserData [in, optional] A pointer to application defined data that will be passed to onWrite and onSeek. + +Returns true if successful; false otherwise. + +Close the writer with drwav_uninit(). + +This is the lowest level function for initializing a WAV file. You can also use drwav_init_file() and drwav_init_memory() +to open the stream from a file or from a block of memory respectively. + +If the total sample count is known, you can use drwav_init_write_sequential(). This avoids the need for dr_wav to perform +a post-processing step for storing the total sample count and the size of the data chunk which requires a backwards seek. + +If you want dr_wav to manage the memory allocation for you, consider using drwav_open() instead. This will allocate +a drwav object on the heap and return a pointer to it. + +See also: drwav_init_file_write(), drwav_init_memory_write(), drwav_uninit() +*/ drwav_bool32 drwav_init_write(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData); drwav_bool32 drwav_init_write_sequential(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_write_proc onWrite, void* pUserData); -// Uninitializes the given drwav object. -// -// Use this only for objects initialized with drwav_init(). +/* +Uninitializes the given drwav object. + +Use this only for objects initialized with drwav_init(). +*/ void drwav_uninit(drwav* pWav); -// Opens a wav file using the given callbacks. -// -// 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. -// -// Returns null on error. -// -// Close the loader with drwav_close(). -// -// You can also use drwav_open_file() and drwav_open_memory() to open the stream from a file or from a block of -// memory respectively. -// -// This is different from drwav_init() in that it will allocate the drwav object for you via DRWAV_MALLOC() before -// initializing it. -// -// See also: drwav_open_file(), drwav_open_memory(), drwav_close() -drwav* drwav_open(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData); +/* +Opens a wav file using the given callbacks. -// Opens a wav file for writing using the given callbacks. -// -// onWrite [in] The function to call when data needs to be written. -// onSeek [in] The function to call when the write position needs to move. -// pUserData [in, optional] A pointer to application defined data that will be passed to onWrite and onSeek. -// -// Returns null on error. -// -// Close the loader with drwav_close(). -// -// You can also use drwav_open_file_write() and drwav_open_memory_write() to open the stream from a file or from a block -// of memory respectively. -// -// This is different from drwav_init_write() in that it will allocate the drwav object for you via DRWAV_MALLOC() before -// initializing it. -// -// See also: drwav_open_file_write(), drwav_open_memory_write(), drwav_close() +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. + +Returns null on error. + +Close the loader with drwav_close(). + +You can also use drwav_open_file() and drwav_open_memory() to open the stream from a file or from a block of +memory respectively. + +This is different from drwav_init() in that it will allocate the drwav object for you via DRWAV_MALLOC() before +initializing it. + +See also: drwav_init(), drwav_open_file(), drwav_open_memory(), drwav_close() +*/ +drwav* drwav_open(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData); +drwav* drwav_open_ex(drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags); + +/* +Opens a wav file for writing using the given callbacks. + +onWrite [in] The function to call when data needs to be written. +onSeek [in] The function to call when the write position needs to move. +pUserData [in, optional] A pointer to application defined data that will be passed to onWrite and onSeek. + +Returns null on error. + +Close the loader with drwav_close(). + +You can also use drwav_open_file_write() and drwav_open_memory_write() to open the stream from a file or from a block +of memory respectively. + +This is different from drwav_init_write() in that it will allocate the drwav object for you via DRWAV_MALLOC() before +initializing it. + +See also: drwav_open_file_write(), drwav_open_memory_write(), drwav_close() +*/ drwav* drwav_open_write(const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData); drwav* drwav_open_write_sequential(const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_write_proc onWrite, void* pUserData); -// Uninitializes and deletes the the given drwav object. -// -// Use this only for objects created with drwav_open(). +/* +Uninitializes and deletes the the given drwav object. + +Use this only for objects created with drwav_open(). +*/ void drwav_close(drwav* pWav); -// Reads raw audio data. -// -// This is the lowest level function for reading audio data. It simply reads the given number of -// bytes of the raw internal sample data. -// -// Consider using drwav_read_s16(), drwav_read_s32() or drwav_read_f32() for reading sample data in -// a consistent format. -// -// Returns the number of bytes actually read. +/* +Reads raw audio data. + +This is the lowest level function for reading audio data. It simply reads the given number of +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. + +Returns the number of bytes actually read. +*/ size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut); -// Reads a chunk of audio data in the native internal format. -// -// This is typically the most efficient way to retrieve audio data, but it does not do any format -// conversions which means you'll need to convert the data manually if required. -// -// If the return value is less than it means the end of the file has been reached or -// you have requested more samples 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_s16/s32/f32/etc(). -drwav_uint64 drwav_read(drwav* pWav, drwav_uint64 samplesToRead, void* pBufferOut); +/* +Reads a chunk of audio data in the native internal format. -// Seeks to the given sample. -// -// Returns true if successful; false otherwise. -drwav_bool32 drwav_seek_to_sample(drwav* pWav, drwav_uint64 sample); +This is typically the most efficient way to retrieve audio data, but it does not do any format +conversions which means you'll need to convert the data manually if required. + +If the return value is less than it means the end of the file has been reached or +you have requested more samples 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/etc(). +*/ +drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut); + +/* +Seeks to the given PCM frame. + +Returns true if successful; false otherwise. +*/ +drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetFrameIndex); -// Writes raw audio data. -// -// Returns the number of bytes actually written. If this differs from bytesToWrite, it indicates an error. +/* +Writes raw audio data. + +Returns the number of bytes actually written. If this differs from bytesToWrite, it indicates an error. +*/ size_t drwav_write_raw(drwav* pWav, size_t bytesToWrite, const void* pData); -// Writes audio data based on sample counts. -// -// Returns the number of samples written. -drwav_uint64 drwav_write(drwav* pWav, drwav_uint64 samplesToWrite, const void* pData); +/* +Writes PCM frames. + +Returns the number of PCM frames written. +*/ +drwav_uint64 drwav_write_pcm_frames(drwav* pWav, drwav_uint64 framesToWrite, const void* pData); - -//// Conversion Utilities //// +/* Conversion Utilities */ #ifndef DR_WAV_NO_CONVERSION_API -// Reads a chunk of audio data and converts it to signed 16-bit PCM samples. -// -// Returns the number of samples actually read. -// -// If the return value is less than it means the end of the file has been reached. -drwav_uint64 drwav_read_s16(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); +/* +Reads a chunk of audio data and converts it to signed 16-bit PCM samples. -// Low-level function for converting unsigned 8-bit PCM samples to signed 16-bit PCM samples. +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. +*/ +drwav_uint64 drwav_read_pcm_frames_s16(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut); + +/* Low-level function for converting unsigned 8-bit PCM samples to signed 16-bit PCM samples. */ void drwav_u8_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); -// Low-level function for converting signed 24-bit PCM samples to signed 16-bit PCM samples. +/* Low-level function for converting signed 24-bit PCM samples to signed 16-bit PCM samples. */ void drwav_s24_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); -// Low-level function for converting signed 32-bit PCM samples to signed 16-bit PCM samples. +/* Low-level function for converting signed 32-bit PCM samples to signed 16-bit PCM samples. */ void drwav_s32_to_s16(drwav_int16* pOut, const drwav_int32* pIn, size_t sampleCount); -// Low-level function for converting IEEE 32-bit floating point samples to signed 16-bit PCM samples. +/* Low-level function for converting IEEE 32-bit floating point samples to signed 16-bit PCM samples. */ void drwav_f32_to_s16(drwav_int16* pOut, const float* pIn, size_t sampleCount); -// Low-level function for converting IEEE 64-bit floating point samples to signed 16-bit PCM samples. +/* Low-level function for converting IEEE 64-bit floating point samples to signed 16-bit PCM samples. */ void drwav_f64_to_s16(drwav_int16* pOut, const double* pIn, size_t sampleCount); -// Low-level function for converting A-law samples to signed 16-bit PCM samples. +/* Low-level function for converting A-law samples to signed 16-bit PCM samples. */ void drwav_alaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); -// Low-level function for converting u-law samples to signed 16-bit PCM samples. +/* Low-level function for converting u-law samples to signed 16-bit PCM samples. */ void drwav_mulaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); -// Reads a chunk of audio data and converts it to IEEE 32-bit floating point samples. -// -// Returns the number of samples actually read. -// -// If the return value is less than it means the end of the file has been reached. -drwav_uint64 drwav_read_f32(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut); +/* +Reads a chunk of audio data and converts it to IEEE 32-bit floating point samples. -// Low-level function for converting unsigned 8-bit PCM samples to IEEE 32-bit floating point samples. +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. +*/ +drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut); + +/* Low-level function for converting unsigned 8-bit PCM samples to IEEE 32-bit floating point samples. */ void drwav_u8_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); -// Low-level function for converting signed 16-bit PCM samples to IEEE 32-bit floating point samples. +/* Low-level function for converting signed 16-bit PCM samples to IEEE 32-bit floating point samples. */ void drwav_s16_to_f32(float* pOut, const drwav_int16* pIn, size_t sampleCount); -// Low-level function for converting signed 24-bit PCM samples to IEEE 32-bit floating point samples. +/* Low-level function for converting signed 24-bit PCM samples to IEEE 32-bit floating point samples. */ void drwav_s24_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); -// Low-level function for converting signed 32-bit PCM samples to IEEE 32-bit floating point samples. +/* Low-level function for converting signed 32-bit PCM samples to IEEE 32-bit floating point samples. */ void drwav_s32_to_f32(float* pOut, const drwav_int32* pIn, size_t sampleCount); -// Low-level function for converting IEEE 64-bit floating point samples to IEEE 32-bit floating point samples. +/* Low-level function for converting IEEE 64-bit floating point samples to IEEE 32-bit floating point samples. */ void drwav_f64_to_f32(float* pOut, const double* pIn, size_t sampleCount); -// Low-level function for converting A-law samples to IEEE 32-bit floating point samples. +/* Low-level function for converting A-law samples to IEEE 32-bit floating point samples. */ void drwav_alaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); -// Low-level function for converting u-law samples to IEEE 32-bit floating point samples. +/* Low-level function for converting u-law samples to IEEE 32-bit floating point samples. */ void drwav_mulaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); -// Reads a chunk of audio data and converts it to signed 32-bit PCM samples. -// -// Returns the number of samples actually read. -// -// If the return value is less than it means the end of the file has been reached. -drwav_uint64 drwav_read_s32(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut); +/* +Reads a chunk of audio data and converts it to signed 32-bit PCM samples. -// Low-level function for converting unsigned 8-bit PCM samples to signed 32-bit PCM samples. +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. +*/ +drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut); + +/* Low-level function for converting unsigned 8-bit PCM samples to signed 32-bit PCM samples. */ void drwav_u8_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); -// Low-level function for converting signed 16-bit PCM samples to signed 32-bit PCM samples. +/* Low-level function for converting signed 16-bit PCM samples to signed 32-bit PCM samples. */ void drwav_s16_to_s32(drwav_int32* pOut, const drwav_int16* pIn, size_t sampleCount); -// Low-level function for converting signed 24-bit PCM samples to signed 32-bit PCM samples. +/* Low-level function for converting signed 24-bit PCM samples to signed 32-bit PCM samples. */ void drwav_s24_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); -// Low-level function for converting IEEE 32-bit floating point samples to signed 32-bit PCM samples. +/* Low-level function for converting IEEE 32-bit floating point samples to signed 32-bit PCM samples. */ void drwav_f32_to_s32(drwav_int32* pOut, const float* pIn, size_t sampleCount); -// Low-level function for converting IEEE 64-bit floating point samples to signed 32-bit PCM samples. +/* Low-level function for converting IEEE 64-bit floating point samples to signed 32-bit PCM samples. */ void drwav_f64_to_s32(drwav_int32* pOut, const double* pIn, size_t sampleCount); -// Low-level function for converting A-law samples to signed 32-bit PCM samples. +/* Low-level function for converting A-law samples to signed 32-bit PCM samples. */ void drwav_alaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); -// Low-level function for converting u-law samples to signed 32-bit PCM samples. +/* Low-level function for converting u-law samples to signed 32-bit PCM samples. */ void drwav_mulaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); -#endif //DR_WAV_NO_CONVERSION_API +#endif /* DR_WAV_NO_CONVERSION_API */ -//// High-Level Convenience Helpers //// +/* High-Level Convenience Helpers */ #ifndef DR_WAV_NO_STDIO +/* +Helper for initializing a wave file using stdio. -// Helper for initializing a wave file using stdio. -// -// This holds the internal FILE object until drwav_uninit() is called. Keep this in mind if you're caching drwav -// objects because the operating system may restrict the number of file handles an application can have open at -// any given time. +This holds the internal FILE object until drwav_uninit() is called. Keep this in mind if you're caching drwav +objects because the operating system may restrict the number of file handles an application can have open at +any given time. +*/ drwav_bool32 drwav_init_file(drwav* pWav, const char* filename); +drwav_bool32 drwav_init_file_ex(drwav* pWav, const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags); -// Helper for initializing a wave file for writing using stdio. -// -// This holds the internal FILE object until drwav_uninit() is called. Keep this in mind if you're caching drwav -// objects because the operating system may restrict the number of file handles an application can have open at -// any given time. +/* +Helper for initializing a wave file for writing using stdio. + +This holds the internal FILE object until drwav_uninit() is called. Keep this in mind if you're caching drwav +objects because the operating system may restrict the number of file handles an application can have open at +any given time. +*/ drwav_bool32 drwav_init_file_write(drwav* pWav, const char* filename, const drwav_data_format* pFormat); drwav_bool32 drwav_init_file_write_sequential(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); -// Helper for opening a wave file using stdio. -// -// This holds the internal FILE object until drwav_close() is called. Keep this in mind if you're caching drwav -// objects because the operating system may restrict the number of file handles an application can have open at -// any given time. -drwav* drwav_open_file(const char* filename); +/* +Helper for opening a wave file using stdio. -// Helper for opening a wave file for writing using stdio. -// -// This holds the internal FILE object until drwav_close() is called. Keep this in mind if you're caching drwav -// objects because the operating system may restrict the number of file handles an application can have open at -// any given time. +This holds the internal FILE object until drwav_close() is called. Keep this in mind if you're caching drwav +objects because the operating system may restrict the number of file handles an application can have open at +any given time. +*/ +drwav* drwav_open_file(const char* filename); +drwav* drwav_open_file_ex(const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags); + +/* +Helper for opening a wave file for writing using stdio. + +This holds the internal FILE object until drwav_close() is called. Keep this in mind if you're caching drwav +objects because the operating system may restrict the number of file handles an application can have open at +any given time. +*/ drwav* drwav_open_file_write(const char* filename, const drwav_data_format* pFormat); drwav* drwav_open_file_write_sequential(const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); -#endif //DR_WAV_NO_STDIO +#endif /* DR_WAV_NO_STDIO */ -// Helper for initializing a loader from a pre-allocated memory buffer. -// -// 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 drwav object. -// -// The buffer should contain the contents of the entire wave file, not just the sample data. +/* +Helper for initializing a loader from a pre-allocated memory buffer. + +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 drwav object. + +The buffer should contain the contents of the entire wave file, not just the sample data. +*/ drwav_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize); +drwav_bool32 drwav_init_memory_ex(drwav* pWav, const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags); -// 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. +/* +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. +*/ drwav_bool32 drwav_init_memory_write(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat); drwav_bool32 drwav_init_memory_write_sequential(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); -// Helper for opening a loader from a pre-allocated memory buffer. -// -// 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 drwav object. -// -// The buffer should contain the contents of the entire wave file, not just the sample data. -drwav* drwav_open_memory(const void* data, size_t dataSize); +/* +Helper for opening a loader from a pre-allocated memory buffer. -// Helper for opening 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_close() is called. Indeed, the buffer should not be -// considered valid until after drwav_close() has been called anyway. +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 drwav object. + +The buffer should contain the contents of the entire wave file, not just the sample data. +*/ +drwav* drwav_open_memory(const void* data, size_t dataSize); +drwav* drwav_open_memory_ex(const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags); + +/* +Helper for opening 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_close() is called. Indeed, the buffer should not be +considered valid until after drwav_close() has been called anyway. +*/ drwav* drwav_open_memory_write(void** ppData, size_t* pDataSize, const drwav_data_format* pFormat); drwav* drwav_open_memory_write_sequential(void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); #ifndef DR_WAV_NO_CONVERSION_API -// Opens and reads a wav file in a single operation. +/* Opens and reads a wav file in a single operation. */ +drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +#ifndef DR_WAV_NO_STDIO +/* Opens and decodes a wav file in a single operation. */ +drwav_int16* drwav_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +float* drwav_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +drwav_int32* drwav_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +#endif + +/* Opens and decodes a wav file from a block of memory in a single operation. */ +drwav_int16* drwav_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +float* drwav_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +drwav_int32* drwav_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +#endif + +/* Frees data that was allocated internally by dr_wav. */ +void drwav_free(void* pDataReturnedByOpenAndRead); + + +/* DEPRECATED APIS */ +drwav_uint64 drwav_read(drwav* pWav, drwav_uint64 samplesToRead, void* pBufferOut); +drwav_uint64 drwav_read_s16(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); +drwav_uint64 drwav_read_f32(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut); +drwav_uint64 drwav_read_s32(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut); +drwav_bool32 drwav_seek_to_sample(drwav* pWav, drwav_uint64 sample); +drwav_uint64 drwav_write(drwav* pWav, drwav_uint64 samplesToWrite, const void* pData); +#ifndef DR_WAV_NO_CONVERSION_API drwav_int16* drwav_open_and_read_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); float* drwav_open_and_read_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); drwav_int32* drwav_open_and_read_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); #ifndef DR_WAV_NO_STDIO -// Opens and decodes a wav file in a single operation. -drwav_int16* drwav_open_and_read_file_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); -float* drwav_open_and_read_file_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); -drwav_int32* drwav_open_and_read_file_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +drwav_int16* drwav_open_memory_and_read_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +float* drwav_open_file_and_read_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +drwav_int32* drwav_open_file_and_read_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +#endif +drwav_int16* drwav_open_memory_and_read_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +float* drwav_open_memory_and_read_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +drwav_int32* drwav_open_memory_and_read_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); #endif -// Opens and decodes a wav file from a block of memory in a single operation. -drwav_int16* drwav_open_and_read_memory_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); -float* drwav_open_and_read_memory_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); -drwav_int32* drwav_open_and_read_memory_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); -#endif - -// Frees data that was allocated internally by dr_wav. -void drwav_free(void* pDataReturnedByOpenAndRead); #ifdef __cplusplus } #endif -#endif // dr_wav_h +#endif /* dr_wav_h */ -///////////////////////////////////////////////////// -// -// IMPLEMENTATION -// -///////////////////////////////////////////////////// +/************************************************************************************************************************************************************ + ************************************************************************************************************************************************************ + IMPLEMENTATION + + ************************************************************************************************************************************************************ + ************************************************************************************************************************************************************/ #ifdef DR_WAV_IMPLEMENTATION #include -#include // For memcpy(), memset() -#include // For INT_MAX +#include /* For memcpy(), memset() */ +#include /* For INT_MAX */ #ifndef DR_WAV_NO_STDIO #include #endif -// Standard library stuff. +/* Standard library stuff. */ #ifndef DRWAV_ASSERT #include #define DRWAV_ASSERT(expression) assert(expression) @@ -727,34 +934,43 @@ void drwav_free(void* pDataReturnedByOpenAndRead); #define drwav_copy_memory DRWAV_COPY_MEMORY #define drwav_zero_memory DRWAV_ZERO_MEMORY +typedef drwav_int32 drwav_result; +#define DRWAV_SUCCESS 0 +#define DRWAV_ERROR -1 +#define DRWAV_INVALID_ARGS -2 +#define DRWAV_INVALID_OPERATION -3 +#define DRWAV_INVALID_FILE -100 +#define DRWAV_EOF -101 -#define DRWAV_MAX_SIMD_VECTOR_SIZE 64 // 64 for AVX-512 in the future. +#define DRWAV_MAX_SIMD_VECTOR_SIZE 64 /* 64 for AVX-512 in the future. */ #ifdef _MSC_VER #define DRWAV_INLINE __forceinline #else #ifdef __GNUC__ -#define DRWAV_INLINE inline __attribute__((always_inline)) +#define DRWAV_INLINE __inline__ __attribute__((always_inline)) #else -#define DRWAV_INLINE inline +#define DRWAV_INLINE #endif #endif -// I couldn't figure out where SIZE_MAX was defined for VC6. If anybody knows, let me know. -#if defined(_MSC_VER) && _MSC_VER <= 1200 - #if defined(_WIN64) - #define SIZE_MAX ((drwav_uint64)0xFFFFFFFFFFFFFFFF) +#if defined(SIZE_MAX) + #define DRWAV_SIZE_MAX SIZE_MAX +#else + #if defined(_WIN64) || defined(_LP64) || defined(__LP64__) + #define DRWAV_SIZE_MAX ((drwav_uint64)0xFFFFFFFFFFFFFFFF) #else - #define SIZE_MAX 0xFFFFFFFF + #define DRWAV_SIZE_MAX 0xFFFFFFFF #endif #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}; // 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_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_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_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]) { @@ -787,11 +1003,7 @@ static DRWAV_INLINE int drwav__is_little_endian() static DRWAV_INLINE unsigned short drwav__bytes_to_u16(const unsigned char* data) { - if (drwav__is_little_endian()) { - return (data[0] << 0) | (data[1] << 8); - } else { - return (data[1] << 0) | (data[0] << 8); - } + return (data[0] << 0) | (data[1] << 8); } static DRWAV_INLINE short drwav__bytes_to_s16(const unsigned char* data) @@ -801,29 +1013,20 @@ static DRWAV_INLINE short drwav__bytes_to_s16(const unsigned char* data) static DRWAV_INLINE unsigned int drwav__bytes_to_u32(const unsigned char* data) { - if (drwav__is_little_endian()) { - return (data[0] << 0) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); - } else { - return (data[3] << 0) | (data[2] << 8) | (data[1] << 16) | (data[0] << 24); - } + return (data[0] << 0) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); } static DRWAV_INLINE drwav_uint64 drwav__bytes_to_u64(const unsigned char* data) { - if (drwav__is_little_endian()) { - 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); - } else { - return - ((drwav_uint64)data[7] << 0) | ((drwav_uint64)data[6] << 8) | ((drwav_uint64)data[5] << 16) | ((drwav_uint64)data[4] << 24) | - ((drwav_uint64)data[3] << 32) | ((drwav_uint64)data[2] << 40) | ((drwav_uint64)data[1] << 48) | ((drwav_uint64)data[0] << 56); - } + 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 void drwav__bytes_to_guid(const unsigned char* data, drwav_uint8* guid) { - for (int i = 0; i < 16; ++i) { + int i; + for (i = 0; i < 16; ++i) { guid[i] = data[i]; } } @@ -836,60 +1039,44 @@ static DRWAV_INLINE drwav_bool32 drwav__is_compressed_format_tag(drwav_uint16 fo formatTag == DR_WAVE_FORMAT_DVI_ADPCM; } - drwav_uint64 drwav_read_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); drwav_uint64 drwav_read_s16__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData); drwav* drwav_open_write__internal(const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData); -typedef struct -{ - union - { - drwav_uint8 fourcc[4]; - drwav_uint8 guid[16]; - } id; - - // The size in bytes of the chunk. - drwav_uint64 sizeInBytes; - - // RIFF = 2 byte alignment. - // W64 = 8 byte alignment. - unsigned int paddingSize; - -} drwav__chunk_header; - -static drwav_bool32 drwav__read_chunk_header(drwav_read_proc onRead, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav__chunk_header* pHeaderOut) +static 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) { + unsigned char sizeInBytes[4]; + if (onRead(pUserData, pHeaderOut->id.fourcc, 4) != 4) { - return DRWAV_FALSE; + return DRWAV_EOF; } - unsigned char sizeInBytes[4]; if (onRead(pUserData, sizeInBytes, 4) != 4) { - return DRWAV_FALSE; + return DRWAV_INVALID_FILE; } pHeaderOut->sizeInBytes = drwav__bytes_to_u32(sizeInBytes); pHeaderOut->paddingSize = (unsigned int)(pHeaderOut->sizeInBytes % 2); *pRunningBytesReadOut += 8; } else { - if (onRead(pUserData, pHeaderOut->id.guid, 16) != 16) { - return DRWAV_FALSE; - } - unsigned char sizeInBytes[8]; - if (onRead(pUserData, sizeInBytes, 8) != 8) { - return DRWAV_FALSE; + + if (onRead(pUserData, pHeaderOut->id.guid, 16) != 16) { + return DRWAV_EOF; } - pHeaderOut->sizeInBytes = drwav__bytes_to_u64(sizeInBytes) - 24; // <-- Subtract 24 because w64 includes the size of the header. + if (onRead(pUserData, sizeInBytes, 8) != 8) { + return DRWAV_INVALID_FILE; + } + + pHeaderOut->sizeInBytes = drwav__bytes_to_u64(sizeInBytes) - 24; /* <-- Subtract 24 because w64 includes the size of the header. */ pHeaderOut->paddingSize = (unsigned int)(pHeaderOut->sizeInBytes % 8); *pRunningBytesReadOut += 24; } - return DRWAV_TRUE; + return DRWAV_SUCCESS; } static drwav_bool32 drwav__seek_forward(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) @@ -912,27 +1099,59 @@ 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) +{ + if (offset <= 0x7FFFFFFF) { + return onSeek(pUserData, (int)offset, drwav_seek_origin_start); + } + + /* Larger than 32-bit seek. */ + if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_start)) { + return DRWAV_FALSE; + } + offset -= 0x7FFFFFFF; + + for (;;) { + if (offset <= 0x7FFFFFFF) { + return onSeek(pUserData, (int)offset, drwav_seek_origin_current); + } + + if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + offset -= 0x7FFFFFFF; + } + + /* Should never get here. */ + /*return DRWAV_TRUE; */ +} + 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__chunk_header header; - if (!drwav__read_chunk_header(onRead, pUserData, container, pRunningBytesReadOut, &header)) { + drwav_chunk_header header; + unsigned char fmt[16]; + + if (drwav__read_chunk_header(onRead, pUserData, container, pRunningBytesReadOut, &header) != DRWAV_SUCCESS) { return DRWAV_FALSE; } - // Skip non-fmt chunks. - if ((container == drwav_container_riff && !drwav__fourcc_equal(header.id.fourcc, "fmt ")) || (container == drwav_container_w64 && !drwav__guid_equal(header.id.guid, drwavGUID_W64_FMT))) { + /* 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))) { if (!drwav__seek_forward(onSeek, header.sizeInBytes + header.paddingSize, pUserData)) { return DRWAV_FALSE; } *pRunningBytesReadOut += header.sizeInBytes + header.paddingSize; - return drwav__read_fmt(onRead, onSeek, pUserData, container, pRunningBytesReadOut, fmtOut); + /* Try the next header. */ + if (drwav__read_chunk_header(onRead, pUserData, container, pRunningBytesReadOut, &header) != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } } - // Validation. + /* Validation. */ if (container == drwav_container_riff) { if (!drwav__fourcc_equal(header.id.fourcc, "fmt ")) { return DRWAV_FALSE; @@ -944,7 +1163,6 @@ static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSe } - unsigned char fmt[16]; if (onRead(pUserData, fmt, sizeof(fmt)) != sizeof(fmt)) { return DRWAV_FALSE; } @@ -964,16 +1182,18 @@ static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSe if (header.sizeInBytes > 16) { unsigned char fmt_cbSize[2]; + int bytesReadSoFar = 0; + if (onRead(pUserData, fmt_cbSize, sizeof(fmt_cbSize)) != sizeof(fmt_cbSize)) { - return DRWAV_FALSE; // Expecting more data. + return DRWAV_FALSE; /* Expecting more data. */ } *pRunningBytesReadOut += sizeof(fmt_cbSize); - int bytesReadSoFar = 18; + bytesReadSoFar = 18; fmtOut->extendedSize = drwav__bytes_to_u16(fmt_cbSize); if (fmtOut->extendedSize > 0) { - // Simple validation. + /* Simple validation. */ if (fmtOut->formatTag == DR_WAVE_FORMAT_EXTENSIBLE) { if (fmtOut->extendedSize != 22) { return DRWAV_FALSE; @@ -983,7 +1203,7 @@ static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSe if (fmtOut->formatTag == DR_WAVE_FORMAT_EXTENSIBLE) { unsigned char fmtext[22]; if (onRead(pUserData, fmtext, fmtOut->extendedSize) != fmtOut->extendedSize) { - return DRWAV_FALSE; // Expecting more data. + return DRWAV_FALSE; /* Expecting more data. */ } fmtOut->validBitsPerSample = drwav__bytes_to_u16(fmtext + 0); @@ -999,7 +1219,7 @@ static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSe bytesReadSoFar += fmtOut->extendedSize; } - // Seek past any leftover bytes. For w64 the leftover will be defined based on the chunk size. + /* Seek past any leftover bytes. For w64 the leftover will be defined based on the chunk size. */ if (!onSeek(pUserData, (int)(header.sizeInBytes - bytesReadSoFar), drwav_seek_origin_current)) { return DRWAV_FALSE; } @@ -1051,13 +1271,18 @@ static drwav_bool32 drwav__on_seek_stdio(void* pUserData, int offset, drwav_seek } drwav_bool32 drwav_init_file(drwav* pWav, const char* filename) +{ + return drwav_init_file_ex(pWav, filename, NULL, NULL, 0); +} + +drwav_bool32 drwav_init_file_ex(drwav* pWav, const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags) { FILE* pFile = drwav_fopen(filename, "rb"); if (pFile == NULL) { return DRWAV_FALSE; } - return drwav_init(pWav, drwav__on_read_stdio, drwav__on_seek_stdio, (void*)pFile); + return drwav_init_ex(pWav, drwav__on_read_stdio, drwav__on_seek_stdio, onChunk, (void*)pFile, pChunkUserData, flags); } @@ -1083,12 +1308,20 @@ drwav_bool32 drwav_init_file_write_sequential(drwav* pWav, const char* filename, drwav* drwav_open_file(const char* filename) { - FILE* pFile = drwav_fopen(filename, "rb"); + return drwav_open_file_ex(filename, NULL, NULL, 0); +} + +drwav* drwav_open_file_ex(const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags) +{ + FILE* pFile; + drwav* pWav; + + pFile = drwav_fopen(filename, "rb"); if (pFile == NULL) { return DRWAV_FALSE; } - drwav* pWav = drwav_open(drwav__on_read_stdio, drwav__on_seek_stdio, (void*)pFile); + pWav = drwav_open_ex(drwav__on_read_stdio, drwav__on_seek_stdio, onChunk, (void*)pFile, pChunkUserData, flags); if (pWav == NULL) { fclose(pFile); return NULL; @@ -1100,12 +1333,15 @@ drwav* drwav_open_file(const char* filename) drwav* drwav_open_file_write__internal(const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential) { - FILE* pFile = drwav_fopen(filename, "wb"); + FILE* pFile; + drwav* pWav; + + pFile = drwav_fopen(filename, "wb"); if (pFile == NULL) { return DRWAV_FALSE; } - drwav* pWav = drwav_open_write__internal(pFormat, totalSampleCount, isSequential, drwav__on_write_stdio, drwav__on_seek_stdio, (void*)pFile); + pWav = drwav_open_write__internal(pFormat, totalSampleCount, isSequential, drwav__on_write_stdio, drwav__on_seek_stdio, (void*)pFile); if (pWav == NULL) { fclose(pFile); return NULL; @@ -1123,16 +1359,18 @@ drwav* drwav_open_file_write_sequential(const char* filename, const drwav_data_f { return drwav_open_file_write__internal(filename, pFormat, totalSampleCount, DRWAV_TRUE); } -#endif //DR_WAV_NO_STDIO +#endif /* DR_WAV_NO_STDIO */ static size_t drwav__on_read_memory(void* pUserData, void* pBufferOut, size_t bytesToRead) { drwav__memory_stream* memory = (drwav__memory_stream*)pUserData; + size_t bytesRemaining; + drwav_assert(memory != NULL); drwav_assert(memory->dataSize >= memory->currentReadPos); - size_t bytesRemaining = memory->dataSize - memory->currentReadPos; + bytesRemaining = memory->dataSize - memory->currentReadPos; if (bytesToRead > bytesRemaining) { bytesToRead = bytesRemaining; } @@ -1153,21 +1391,21 @@ static drwav_bool32 drwav__on_seek_memory(void* pUserData, int offset, drwav_see if (origin == drwav_seek_origin_current) { if (offset > 0) { if (memory->currentReadPos + offset > memory->dataSize) { - offset = (int)(memory->dataSize - memory->currentReadPos); // Trying to seek too far forward. + return DRWAV_FALSE; /* Trying to seek too far forward. */ } } else { if (memory->currentReadPos < (size_t)-offset) { - offset = -(int)memory->currentReadPos; // Trying to seek too far backwards. + return DRWAV_FALSE; /* Trying to seek too far backwards. */ } } - // This will never underflow thanks to the clamps above. + /* This will never underflow thanks to the clamps above. */ memory->currentReadPos += offset; } else { if ((drwav_uint32)offset <= memory->dataSize) { memory->currentReadPos = offset; } else { - memory->currentReadPos = memory->dataSize; // Trying to seek too far forward. + return DRWAV_FALSE; /* Trying to seek too far forward. */ } } @@ -1177,20 +1415,23 @@ static drwav_bool32 drwav__on_seek_memory(void* pUserData, int offset, drwav_see static size_t drwav__on_write_memory(void* pUserData, const void* pDataIn, size_t bytesToWrite) { drwav__memory_stream_write* memory = (drwav__memory_stream_write*)pUserData; + size_t bytesRemaining; + drwav_assert(memory != NULL); drwav_assert(memory->dataCapacity >= memory->currentWritePos); - size_t bytesRemaining = memory->dataCapacity - memory->currentWritePos; + bytesRemaining = memory->dataCapacity - memory->currentWritePos; if (bytesRemaining < bytesToWrite) { - // Need to reallocate. + /* Need to reallocate. */ + void* pNewData; size_t newDataCapacity = (memory->dataCapacity == 0) ? 256 : memory->dataCapacity * 2; - // If doubling wasn't enough, just make it the minimum required size to write the data. + /* If doubling wasn't enough, just make it the minimum required size to write the data. */ if ((newDataCapacity - memory->currentWritePos) < bytesToWrite) { newDataCapacity = memory->currentWritePos + bytesToWrite; } - void* pNewData = DRWAV_REALLOC(*memory->ppData, newDataCapacity); + pNewData = DRWAV_REALLOC(*memory->ppData, newDataCapacity); if (pNewData == NULL) { return 0; } @@ -1199,8 +1440,7 @@ static size_t drwav__on_write_memory(void* pUserData, const void* pDataIn, size_ memory->dataCapacity = newDataCapacity; } - drwav_uint8* pDataOut = (drwav_uint8*)(*memory->ppData); - DRWAV_COPY_MEMORY(pDataOut + memory->currentWritePos, pDataIn, bytesToWrite); + DRWAV_COPY_MEMORY(((drwav_uint8*)(*memory->ppData)) + memory->currentWritePos, pDataIn, bytesToWrite); memory->currentWritePos += bytesToWrite; if (memory->dataSize < memory->currentWritePos) { @@ -1220,21 +1460,21 @@ static drwav_bool32 drwav__on_seek_memory_write(void* pUserData, int offset, drw if (origin == drwav_seek_origin_current) { if (offset > 0) { if (memory->currentWritePos + offset > memory->dataSize) { - offset = (int)(memory->dataSize - memory->currentWritePos); // Trying to seek too far forward. + offset = (int)(memory->dataSize - memory->currentWritePos); /* Trying to seek too far forward. */ } } else { if (memory->currentWritePos < (size_t)-offset) { - offset = -(int)memory->currentWritePos; // Trying to seek too far backwards. + offset = -(int)memory->currentWritePos; /* Trying to seek too far backwards. */ } } - // This will never underflow thanks to the clamps above. + /* This will never underflow thanks to the clamps above. */ memory->currentWritePos += offset; } else { if ((drwav_uint32)offset <= memory->dataSize) { memory->currentWritePos = offset; } else { - memory->currentWritePos = memory->dataSize; // Trying to seek too far forward. + memory->currentWritePos = memory->dataSize; /* Trying to seek too far forward. */ } } @@ -1243,17 +1483,23 @@ static drwav_bool32 drwav__on_seek_memory_write(void* pUserData, int offset, drw drwav_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize) { + return drwav_init_memory_ex(pWav, data, dataSize, NULL, NULL, 0); +} + +drwav_bool32 drwav_init_memory_ex(drwav* pWav, const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags) +{ + drwav__memory_stream memoryStream; + if (data == NULL || dataSize == 0) { return DRWAV_FALSE; } - drwav__memory_stream memoryStream; drwav_zero_memory(&memoryStream, sizeof(memoryStream)); memoryStream.data = (const unsigned char*)data; memoryStream.dataSize = dataSize; memoryStream.currentReadPos = 0; - if (!drwav_init(pWav, drwav__on_read_memory, drwav__on_seek_memory, (void*)&memoryStream)) { + if (!drwav_init_ex(pWav, drwav__on_read_memory, drwav__on_seek_memory, onChunk, (void*)&memoryStream, pChunkUserData, flags)) { return DRWAV_FALSE; } @@ -1265,14 +1511,15 @@ drwav_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize) 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) { + drwav__memory_stream_write memoryStreamWrite; + if (ppData == NULL) { return DRWAV_FALSE; } - *ppData = NULL; // Important because we're using realloc()! + *ppData = NULL; /* Important because we're using realloc()! */ *pDataSize = 0; - drwav__memory_stream_write memoryStreamWrite; drwav_zero_memory(&memoryStreamWrite, sizeof(memoryStreamWrite)); memoryStreamWrite.ppData = ppData; memoryStreamWrite.pDataSize = pDataSize; @@ -1302,17 +1549,24 @@ drwav_bool32 drwav_init_memory_write_sequential(drwav* pWav, void** ppData, size drwav* drwav_open_memory(const void* data, size_t dataSize) { + return drwav_open_memory_ex(data, dataSize, NULL, NULL, 0); +} + +drwav* drwav_open_memory_ex(const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags) +{ + drwav__memory_stream memoryStream; + drwav* pWav; + if (data == NULL || dataSize == 0) { return NULL; } - drwav__memory_stream memoryStream; drwav_zero_memory(&memoryStream, sizeof(memoryStream)); memoryStream.data = (const unsigned char*)data; memoryStream.dataSize = dataSize; memoryStream.currentReadPos = 0; - drwav* pWav = drwav_open(drwav__on_read_memory, drwav__on_seek_memory, (void*)&memoryStream); + pWav = drwav_open_ex(drwav__on_read_memory, drwav__on_seek_memory, onChunk, (void*)&memoryStream, pChunkUserData, flags); if (pWav == NULL) { return NULL; } @@ -1325,14 +1579,16 @@ drwav* drwav_open_memory(const void* data, size_t dataSize) drwav* drwav_open_memory_write__internal(void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential) { + drwav__memory_stream_write memoryStreamWrite; + drwav* pWav; + if (ppData == NULL) { return NULL; } - *ppData = NULL; // Important because we're using realloc()! + *ppData = NULL; /* Important because we're using realloc()! */ *pDataSize = 0; - drwav__memory_stream_write memoryStreamWrite; drwav_zero_memory(&memoryStreamWrite, sizeof(memoryStreamWrite)); memoryStreamWrite.ppData = ppData; memoryStreamWrite.pDataSize = pDataSize; @@ -1340,7 +1596,7 @@ drwav* drwav_open_memory_write__internal(void** ppData, size_t* pDataSize, const memoryStreamWrite.dataCapacity = 0; memoryStreamWrite.currentWritePos = 0; - drwav* pWav = drwav_open_write__internal(pFormat, totalSampleCount, isSequential, drwav__on_write_memory, drwav__on_seek_memory_write, (void*)&memoryStreamWrite); + pWav = drwav_open_write__internal(pFormat, totalSampleCount, isSequential, drwav__on_write_memory, drwav__on_seek_memory_write, (void*)&memoryStreamWrite); if (pWav == NULL) { return NULL; } @@ -1361,143 +1617,274 @@ drwav* drwav_open_memory_write_sequential(void** ppData, size_t* pDataSize, cons } +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); + drwav_assert(pCursor != NULL); + + bytesRead = onRead(pUserData, pBufferOut, bytesToRead); + *pCursor += bytesRead; + return bytesRead; +} + +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); + + if (!onSeek(pUserData, offset, origin)) { + return DRWAV_FALSE; + } + + if (origin == drwav_seek_origin_start) { + *pCursor = offset; + } else { + *pCursor += offset; + } + + return DRWAV_TRUE; +} + + +static drwav_uint32 drwav_get_bytes_per_sample(drwav* pWav) +{ + /* + The number of bytes per sample is based on the bits per sample or the block align. We prioritize floor(bitsPerSample/8), but if + this is zero or the bits per sample is not a multiple of 8 we need to fall back to the block align. + */ + drwav_uint32 bytesPerSample = pWav->bitsPerSample >> 3; + if (bytesPerSample == 0 || (pWav->bitsPerSample & 0x7) != 0) { + bytesPerSample = pWav->fmt.blockAlign/pWav->fmt.channels; + } + + return bytesPerSample; +} + +static drwav_uint32 drwav_get_bytes_per_pcm_frame(drwav* pWav) +{ + /* + The number of bytes per frame is based on the bits per sample or the block align. We prioritize floor(bitsPerSample*channels/8), but if + this is zero or the bits per frame is not a multiple of 8 we need to fall back to the block align. + */ + drwav_uint32 bitsPerFrame = pWav->bitsPerSample * pWav->fmt.channels; + drwav_uint32 bytesPerFrame = bitsPerFrame >> 3; + if (bytesPerFrame == 0 || (bitsPerFrame & 0x7) != 0) { + bytesPerFrame = pWav->fmt.blockAlign; + } + + return bytesPerFrame; +} + + drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData) { + return drwav_init_ex(pWav, onRead, onSeek, NULL, pUserData, NULL, 0); +} + +drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags) +{ + drwav_uint64 cursor; /* <-- Keeps track of the byte position so we can seek to specific locations. */ + drwav_bool32 sequential; + unsigned char riff[4]; + drwav_fmt fmt; + unsigned short translatedFormatTag; + drwav_uint64 sampleCountFromFactChunk; + drwav_bool32 foundDataChunk; + drwav_uint64 dataChunkSize; + drwav_uint64 chunkSize; + if (onRead == NULL || onSeek == NULL) { return DRWAV_FALSE; } + cursor = 0; + sequential = (flags & DRWAV_SEQUENTIAL) != 0; + drwav_zero_memory(pWav, sizeof(*pWav)); + pWav->onRead = onRead; + pWav->onSeek = onSeek; + pWav->pUserData = pReadSeekUserData; - - // The first 4 bytes should be the RIFF identifier. - unsigned char riff[4]; - if (onRead(pUserData, riff, sizeof(riff)) != sizeof(riff)) { - return DRWAV_FALSE; // Failed to read data. + /* The first 4 bytes should be the RIFF identifier. */ + if (drwav__on_read(onRead, pReadSeekUserData, riff, sizeof(riff), &cursor) != sizeof(riff)) { + return DRWAV_FALSE; } - // 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". + /* + 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")) { pWav->container = drwav_container_riff; } else if (drwav__fourcc_equal(riff, "riff")) { + int i; + drwav_uint8 riff2[12]; + pWav->container = drwav_container_w64; - // Check the rest of the GUID for validity. - drwav_uint8 riff2[12]; - if (onRead(pUserData, riff2, sizeof(riff2)) != sizeof(riff2)) { + /* Check the rest of the GUID for validity. */ + if (drwav__on_read(onRead, pReadSeekUserData, riff2, sizeof(riff2), &cursor) != sizeof(riff2)) { return DRWAV_FALSE; } - for (int i = 0; i < 12; ++i) { + for (i = 0; i < 12; ++i) { if (riff2[i] != drwavGUID_W64_RIFF[i+4]) { return DRWAV_FALSE; } } } else { - return DRWAV_FALSE; // Unknown or unsupported container. + return DRWAV_FALSE; /* Unknown or unsupported container. */ } if (pWav->container == drwav_container_riff) { - // RIFF/WAVE unsigned char chunkSizeBytes[4]; - if (onRead(pUserData, chunkSizeBytes, sizeof(chunkSizeBytes)) != sizeof(chunkSizeBytes)) { + unsigned char wave[4]; + + /* RIFF/WAVE */ + if (drwav__on_read(onRead, pReadSeekUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) { return DRWAV_FALSE; } - unsigned int chunkSize = drwav__bytes_to_u32(chunkSizeBytes); - if (chunkSize < 36) { - return DRWAV_FALSE; // Chunk size should always be at least 36 bytes. + if (drwav__bytes_to_u32(chunkSizeBytes) < 36) { + return DRWAV_FALSE; /* Chunk size should always be at least 36 bytes. */ } - unsigned char wave[4]; - if (onRead(pUserData, wave, sizeof(wave)) != sizeof(wave)) { + if (drwav__on_read(onRead, pReadSeekUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) { return DRWAV_FALSE; } if (!drwav__fourcc_equal(wave, "WAVE")) { - return DRWAV_FALSE; // Expecting "WAVE". + return DRWAV_FALSE; /* Expecting "WAVE". */ } - - pWav->dataChunkDataPos = 4 + sizeof(chunkSizeBytes) + sizeof(wave); } else { - // W64 - unsigned char chunkSize[8]; - if (onRead(pUserData, chunkSize, sizeof(chunkSize)) != sizeof(chunkSize)) { - return DRWAV_FALSE; - } - - if (drwav__bytes_to_u64(chunkSize) < 80) { - return DRWAV_FALSE; - } - + unsigned char chunkSizeBytes[8]; drwav_uint8 wave[16]; - if (onRead(pUserData, wave, sizeof(wave)) != sizeof(wave)) { + + /* W64 */ + if (drwav__on_read(onRead, pReadSeekUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) { + return DRWAV_FALSE; + } + + if (drwav__bytes_to_u64(chunkSizeBytes) < 80) { + return DRWAV_FALSE; + } + + if (drwav__on_read(onRead, pReadSeekUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) { return DRWAV_FALSE; } if (!drwav__guid_equal(wave, drwavGUID_W64_WAVE)) { return DRWAV_FALSE; } - - pWav->dataChunkDataPos = 16 + sizeof(chunkSize) + sizeof(wave); } - // The next bytes should be the "fmt " chunk. - drwav_fmt fmt; - if (!drwav__read_fmt(onRead, onSeek, pUserData, pWav->container, &pWav->dataChunkDataPos, &fmt)) { - return DRWAV_FALSE; // Failed to read the "fmt " chunk. + /* The next bytes should be the "fmt " chunk. */ + if (!drwav__read_fmt(onRead, onSeek, pReadSeekUserData, pWav->container, &cursor, &fmt)) { + return DRWAV_FALSE; /* Failed to read the "fmt " chunk. */ } - // Basic validation. + /* Basic validation. */ if (fmt.sampleRate == 0 || fmt.channels == 0 || fmt.bitsPerSample == 0 || fmt.blockAlign == 0) { - return DRWAV_FALSE; // Invalid channel count. Probably an invalid WAV file. + return DRWAV_FALSE; /* Invalid channel count. Probably an invalid WAV file. */ } - // Translate the internal format. - unsigned short translatedFormatTag = fmt.formatTag; + /* Translate the internal format. */ + translatedFormatTag = fmt.formatTag; if (translatedFormatTag == DR_WAVE_FORMAT_EXTENSIBLE) { translatedFormatTag = drwav__bytes_to_u16(fmt.subFormat + 0); } - drwav_uint64 sampleCountFromFactChunk = 0; - // The next chunk we care about is the "data" chunk. This is not necessarily the next chunk so we'll need to loop. - drwav_uint64 dataSize; + sampleCountFromFactChunk = 0; + + /* + We need to enumerate over each chunk for two reasons: + 1) The "data" chunk may not be the next one + 2) We may want to report each chunk back to the client + + 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. */ + chunkSize = 0; for (;;) { - drwav__chunk_header header; - if (!drwav__read_chunk_header(onRead, pUserData, pWav->container, &pWav->dataChunkDataPos, &header)) { - return DRWAV_FALSE; + drwav_chunk_header header; + drwav_result result = drwav__read_chunk_header(onRead, pReadSeekUserData, pWav->container, &cursor, &header); + if (result != DRWAV_SUCCESS) { + if (!foundDataChunk) { + return DRWAV_FALSE; + } else { + break; /* Probably at the end of the file. Get out of the loop. */ + } } - dataSize = header.sizeInBytes; + /* Tell the client about this chunk. */ + if (!sequential && onChunk != NULL) { + drwav_uint64 callbackBytesRead = onChunk(pChunkUserData, onRead, onSeek, pReadSeekUserData, &header); + + /* + dr_wav may need to read the contents of the chunk, so we now need to seek back to the position before + we called the callback. + */ + if (callbackBytesRead > 0) { + if (!drwav__seek_from_start(onSeek, cursor, pReadSeekUserData)) { + return DRWAV_FALSE; + } + } + } + + + if (!foundDataChunk) { + pWav->dataChunkDataPos = cursor; + } + + chunkSize = header.sizeInBytes; if (pWav->container == drwav_container_riff) { if (drwav__fourcc_equal(header.id.fourcc, "data")) { - break; + foundDataChunk = DRWAV_TRUE; + dataChunkSize = chunkSize; } } else { if (drwav__guid_equal(header.id.guid, drwavGUID_W64_DATA)) { - break; + foundDataChunk = DRWAV_TRUE; + dataChunkSize = chunkSize; } } - // Optional. Get the total sample count from the FACT chunk. This is useful for compressed formats. + /* + If at this point we have found the data chunk and we're running in sequential mode, we need to break out of this loop. The reason for + this is that we would otherwise require a backwards seek which sequential mode forbids. + */ + if (foundDataChunk && sequential) { + break; + } + + /* 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")) { drwav_uint32 sampleCount; - if (onRead(pUserData, &sampleCount, 4) != 4) { + if (drwav__on_read(onRead, pReadSeekUserData, &sampleCount, 4, &cursor) != 4) { return DRWAV_FALSE; } - pWav->dataChunkDataPos += 4; - dataSize -= 4; + chunkSize -= 4; - // The sample count in the "fact" chunk is either unreliable, or I'm not understanding it properly. For now I am only enabling this - // for Microsoft ADPCM formats. + if (!foundDataChunk) { + pWav->dataChunkDataPos = cursor; + } + + /* + The sample count in the "fact" chunk is either unreliable, or I'm not understanding it properly. For now I am only enabling this + for Microsoft ADPCM formats. + */ if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { sampleCountFromFactChunk = sampleCount; } else { @@ -1506,62 +1893,120 @@ drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onS } } else { if (drwav__guid_equal(header.id.guid, drwavGUID_W64_FACT)) { - if (onRead(pUserData, &sampleCountFromFactChunk, 8) != 8) { + if (drwav__on_read(onRead, pReadSeekUserData, &sampleCountFromFactChunk, 8, &cursor) != 8) { return DRWAV_FALSE; } - pWav->dataChunkDataPos += 8; - dataSize -= 8; + chunkSize -= 8; + + if (!foundDataChunk) { + pWav->dataChunkDataPos = cursor; + } } } - // If we get here it means we didn't find the "data" chunk. Seek past it. + /* "smpl" chunk. */ + if (pWav->container == drwav_container_riff) { + if (drwav__fourcc_equal(header.id.fourcc, "smpl")) { + unsigned char smplHeaderData[36]; /* 36 = size of the smpl header section, not including the loop data. */ + if (chunkSize >= sizeof(smplHeaderData)) { + drwav_uint64 bytesJustRead = drwav__on_read(onRead, pReadSeekUserData, smplHeaderData, sizeof(smplHeaderData), &cursor); + chunkSize -= bytesJustRead; - // Make sure we seek past the padding. - dataSize += header.paddingSize; - drwav__seek_forward(onSeek, dataSize, pUserData); - pWav->dataChunkDataPos += dataSize; + 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); + + for (iLoop = 0; iLoop < pWav->smpl.numSampleLoops && iLoop < drwav_countof(pWav->smpl.loops); ++iLoop) { + unsigned char smplLoopData[24]; /* 24 = size of a loop section in the smpl chunk. */ + bytesJustRead = drwav__on_read(onRead, pReadSeekUserData, 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); + } else { + break; /* Break from the smpl loop for loop. */ + } + } + } + } else { + /* Looks like invalid data. Ignore the chunk. */ + } + } + } else { + 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. + */ + } + } + + /* Make sure we seek past the padding. */ + chunkSize += header.paddingSize; + if (!drwav__seek_forward(onSeek, chunkSize, pReadSeekUserData)) { + break; + } + cursor += chunkSize; + + if (!foundDataChunk) { + pWav->dataChunkDataPos = cursor; + } } - // At this point we should be sitting on the first byte of the raw audio data. + /* If we haven't found a data chunk, return an error. */ + if (!foundDataChunk) { + return DRWAV_FALSE; + } + + /* We may have moved passed the data chunk. If so we need to move back. If running in sequential mode we can assume we are already sitting on the data chunk. */ + if (!sequential) { + if (!drwav__seek_from_start(onSeek, pWav->dataChunkDataPos, pReadSeekUserData)) { + return DRWAV_FALSE; + } + cursor = pWav->dataChunkDataPos; + } + + + /* At this point we should be sitting on the first byte of the raw audio data. */ - pWav->onRead = onRead; - pWav->onSeek = onSeek; - pWav->pUserData = pUserData; pWav->fmt = fmt; pWav->sampleRate = fmt.sampleRate; pWav->channels = fmt.channels; pWav->bitsPerSample = fmt.bitsPerSample; - pWav->bytesPerSample = fmt.blockAlign / fmt.channels; - pWav->bytesRemaining = dataSize; + pWav->bytesRemaining = dataChunkSize; pWav->translatedFormatTag = translatedFormatTag; - pWav->dataChunkDataSize = dataSize; - - // The bytes per sample should never be 0 at this point. This would indicate an invalid WAV file. - if (pWav->bytesPerSample == 0) { - return DRWAV_FALSE; - } + pWav->dataChunkDataSize = dataChunkSize; if (sampleCountFromFactChunk != 0) { - pWav->totalSampleCount = sampleCountFromFactChunk * fmt.channels; + pWav->totalPCMFrameCount = sampleCountFromFactChunk; } else { - pWav->totalSampleCount = dataSize / pWav->bytesPerSample; + pWav->totalPCMFrameCount = dataChunkSize / drwav_get_bytes_per_pcm_frame(pWav); if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { - drwav_uint64 blockCount = dataSize / fmt.blockAlign; - pWav->totalSampleCount = (blockCount * (fmt.blockAlign - (6*pWav->channels))) * 2; // x2 because two samples per byte. + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + pWav->totalPCMFrameCount = (((blockCount * (fmt.blockAlign - (6*pWav->channels))) * 2)) / fmt.channels; /* x2 because two samples per byte. */ } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { - drwav_uint64 blockCount = dataSize / fmt.blockAlign; - pWav->totalSampleCount = ((blockCount * (fmt.blockAlign - (4*pWav->channels))) * 2) + (blockCount * pWav->channels); + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + pWav->totalPCMFrameCount = (((blockCount * (fmt.blockAlign - (4*pWav->channels))) * 2) + (blockCount * pWav->channels)) / fmt.channels; } } - // The way we calculate the bytes per sample does not make sense for compressed formats so we just set it to 0. - if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { - pWav->bytesPerSample = 0; - } - - // Some formats only support a certain number of channels. + /* Some formats only support a certain number of channels. */ if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM || pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { if (pWav->channels > 2) { return DRWAV_FALSE; @@ -1569,29 +2014,33 @@ drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onS } #ifdef DR_WAV_LIBSNDFILE_COMPAT - // I use libsndfile as a benchmark for testing, however in the version I'm using (from the Windows installer on the libsndfile website), - // it appears the total sample count libsndfile uses for MS-ADPCM is incorrect. It would seem they are computing the total sample count - // from the number of blocks, however this results in the inclusion of extra silent samples at the end of the last block. The correct - // way to know the total sample count is to inspect the "fact" chunk, which should always be present for compressed formats, and should - // always include the sample count. This little block of code below is only used to emulate the libsndfile logic so I can properly run my - // correctness tests against libsndfile, and is disabled by default. + /* + I use libsndfile as a benchmark for testing, however in the version I'm using (from the Windows installer on the libsndfile website), + it appears the total sample count libsndfile uses for MS-ADPCM is incorrect. It would seem they are computing the total sample count + from the number of blocks, however this results in the inclusion of extra silent samples at the end of the last block. The correct + way to know the total sample count is to inspect the "fact" chunk, which should always be present for compressed formats, and should + always include the sample count. This little block of code below is only used to emulate the libsndfile logic so I can properly run my + correctness tests against libsndfile, and is disabled by default. + */ if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { - drwav_uint64 blockCount = dataSize / fmt.blockAlign; - pWav->totalSampleCount = (blockCount * (fmt.blockAlign - (6*pWav->channels))) * 2; // x2 because two samples per byte. + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + pWav->totalPCMFrameCount = (((blockCount * (fmt.blockAlign - (6*pWav->channels))) * 2)) / fmt.channels; /* x2 because two samples per byte. */ } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { - drwav_uint64 blockCount = dataSize / fmt.blockAlign; - pWav->totalSampleCount = ((blockCount * (fmt.blockAlign - (4*pWav->channels))) * 2) + (blockCount * pWav->channels); + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + pWav->totalPCMFrameCount = (((blockCount * (fmt.blockAlign - (4*pWav->channels))) * 2) + (blockCount * pWav->channels)) / fmt.channels; } #endif + pWav->totalSampleCount = pWav->totalPCMFrameCount * pWav->channels; + return DRWAV_TRUE; } drwav_uint32 drwav_riff_chunk_size_riff(drwav_uint64 dataChunkSize) { - if (dataChunkSize <= (0xFFFFFFFF - 36)) { + if (dataChunkSize <= (0xFFFFFFFFUL - 36)) { return 36 + (drwav_uint32)dataChunkSize; } else { return 0xFFFFFFFF; @@ -1600,26 +2049,30 @@ drwav_uint32 drwav_riff_chunk_size_riff(drwav_uint64 dataChunkSize) drwav_uint32 drwav_data_chunk_size_riff(drwav_uint64 dataChunkSize) { - if (dataChunkSize <= 0xFFFFFFFF) { + if (dataChunkSize <= 0xFFFFFFFFUL) { return (drwav_uint32)dataChunkSize; } else { - return 0xFFFFFFFF; + return 0xFFFFFFFFUL; } } drwav_uint64 drwav_riff_chunk_size_w64(drwav_uint64 dataChunkSize) { - return 80 + 24 + dataChunkSize; // +24 because W64 includes the size of the GUID and size fields. + return 80 + 24 + dataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ } 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. + return 24 + dataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ } drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData) { + size_t runningPos = 0; + drwav_uint64 initialDataChunkSize = 0; + drwav_uint64 chunkSizeFMT; + if (pWav == NULL) { return DRWAV_FALSE; } @@ -1629,11 +2082,11 @@ drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pF } if (!isSequential && onSeek == NULL) { - return DRWAV_FALSE; // <-- onSeek is required when in non-sequential mode. + return DRWAV_FALSE; /* <-- onSeek is required when in non-sequential mode. */ } - // Not currently supporting compressed formats. Will need to add support for the "fact" chunk before we enable this. + /* Not currently supporting compressed formats. Will need to add support for the "fact" chunk before we enable this. */ if (pFormat->format == DR_WAVE_FORMAT_EXTENSIBLE) { return DRWAV_FALSE; } @@ -1655,21 +2108,21 @@ drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pF pWav->fmt.extendedSize = 0; pWav->isSequentialWrite = isSequential; - - size_t runningPos = 0; - - // The initial values for the "RIFF" and "data" chunks depends on whether or not we are initializing in sequential mode or not. In - // sequential mode we set this to its final values straight away since they can be calculated from the total sample count. In non- - // sequential mode we initialize it all to zero and fill it out in drwav_uninit() using a backwards seek. - drwav_uint64 initialDataChunkSize = 0; + /* + The initial values for the "RIFF" and "data" chunks depends on whether or not we are initializing in sequential mode or not. In + sequential mode we set this to its final values straight away since they can be calculated from the total sample count. In non- + sequential mode we initialize it all to zero and fill it out in drwav_uninit() using a backwards seek. + */ if (isSequential) { initialDataChunkSize = (totalSampleCount * pWav->fmt.bitsPerSample) / 8; - // The RIFF container has a limit on the number of samples. drwav is not allowing this. There's no practical limits for Wave64 - // so for the sake of simplicity I'm not doing any validation for that. + /* + The RIFF container has a limit on the number of samples. drwav is not allowing this. There's no practical limits for Wave64 + so for the sake of simplicity I'm not doing any validation for that. + */ if (pFormat->container == drwav_container_riff) { - if (initialDataChunkSize > (0xFFFFFFFF - 36)) { - return DRWAV_FALSE; // Not enough room to store every sample. + if (initialDataChunkSize > (0xFFFFFFFFUL - 36)) { + return DRWAV_FALSE; /* Not enough room to store every sample. */ } } } @@ -1677,21 +2130,20 @@ drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pF pWav->dataChunkDataSizeTargetWrite = initialDataChunkSize; - // "RIFF" chunk. + /* "RIFF" chunk. */ if (pFormat->container == drwav_container_riff) { - drwav_uint32 chunkSizeRIFF = 36 + (drwav_uint32)initialDataChunkSize; // +36 = "RIFF"+[RIFF Chunk Size]+"WAVE" + [sizeof "fmt " chunk] + drwav_uint32 chunkSizeRIFF = 36 + (drwav_uint32)initialDataChunkSize; /* +36 = "RIFF"+[RIFF Chunk Size]+"WAVE" + [sizeof "fmt " chunk] */ runningPos += pWav->onWrite(pUserData, "RIFF", 4); runningPos += pWav->onWrite(pUserData, &chunkSizeRIFF, 4); runningPos += pWav->onWrite(pUserData, "WAVE", 4); } else { - drwav_uint64 chunkSizeRIFF = 80 + 24 + initialDataChunkSize; // +24 because W64 includes the size of the GUID and size fields. + drwav_uint64 chunkSizeRIFF = 80 + 24 + initialDataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ runningPos += pWav->onWrite(pUserData, drwavGUID_W64_RIFF, 16); runningPos += pWav->onWrite(pUserData, &chunkSizeRIFF, 8); runningPos += pWav->onWrite(pUserData, drwavGUID_W64_WAVE, 16); } - // "fmt " chunk. - drwav_uint64 chunkSizeFMT; + /* "fmt " chunk. */ if (pFormat->container == drwav_container_riff) { chunkSizeFMT = 16; runningPos += pWav->onWrite(pUserData, "fmt ", 4); @@ -1711,19 +2163,19 @@ drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pF pWav->dataChunkDataPos = runningPos; - // "data" chunk. + /* "data" chunk. */ if (pFormat->container == drwav_container_riff) { drwav_uint32 chunkSizeDATA = (drwav_uint32)initialDataChunkSize; runningPos += pWav->onWrite(pUserData, "data", 4); runningPos += pWav->onWrite(pUserData, &chunkSizeDATA, 4); } else { - drwav_uint64 chunkSizeDATA = 24 + initialDataChunkSize; // +24 because W64 includes the size of the GUID and size fields. + drwav_uint64 chunkSizeDATA = 24 + initialDataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ runningPos += pWav->onWrite(pUserData, drwavGUID_W64_DATA, 16); runningPos += pWav->onWrite(pUserData, &chunkSizeDATA, 8); } - // Simple validation. + /* Simple validation. */ if (pFormat->container == drwav_container_riff) { if (runningPos != 20 + chunkSizeFMT + 8) { return DRWAV_FALSE; @@ -1736,12 +2188,11 @@ drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pF - // Set some properties for the client's convenience. + /* 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->bytesPerSample = (drwav_uint16)(pFormat->bitsPerSample >> 3); pWav->translatedFormatTag = (drwav_uint16)pFormat->format; return DRWAV_TRUE; @@ -1750,12 +2201,12 @@ drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pF drwav_bool32 drwav_init_write(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData) { - return drwav_init_write__internal(pWav, pFormat, 0, DRWAV_FALSE, onWrite, onSeek, pUserData); // DRWAV_FALSE = Not Sequential + return drwav_init_write__internal(pWav, pFormat, 0, DRWAV_FALSE, onWrite, onSeek, pUserData); /* DRWAV_FALSE = Not Sequential */ } drwav_bool32 drwav_init_write_sequential(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_write_proc onWrite, void* pUserData) { - return drwav_init_write__internal(pWav, pFormat, totalSampleCount, DRWAV_TRUE, onWrite, NULL, pUserData); // DRWAV_TRUE = Sequential + return drwav_init_write__internal(pWav, pFormat, totalSampleCount, DRWAV_TRUE, onWrite, NULL, pUserData); /* DRWAV_TRUE = Sequential */ } void drwav_uninit(drwav* pWav) @@ -1764,17 +2215,20 @@ void drwav_uninit(drwav* pWav) return; } - // If the drwav object was opened in write mode we'll need to finalize a few things: - // - Make sure the "data" chunk is aligned to 16-bits for RIFF containers, or 64 bits for W64 containers. - // - Set the size of the "data" chunk. + /* + If the drwav object was opened in write mode we'll need to finalize a few things: + - Make sure the "data" chunk is aligned to 16-bits for RIFF containers, or 64 bits for W64 containers. + - Set the size of the "data" chunk. + */ if (pWav->onWrite != NULL) { - // Validation for sequential mode. + drwav_uint32 paddingSize = 0; + + /* Validation for sequential mode. */ if (pWav->isSequentialWrite) { drwav_assert(pWav->dataChunkDataSize == pWav->dataChunkDataSizeTargetWrite); } - // Padding. Do not adjust pWav->dataChunkDataSize - this should not include the padding. - drwav_uint32 paddingSize = 0; + /* Padding. Do not adjust pWav->dataChunkDataSize - this should not include the padding. */ if (pWav->container == drwav_container_riff) { paddingSize = (drwav_uint32)(pWav->dataChunkDataSize % 2); } else { @@ -1786,30 +2240,31 @@ void drwav_uninit(drwav* pWav) pWav->onWrite(pWav->pUserData, &paddingData, paddingSize); } - - // Chunk sizes. When using sequential mode, these will have been filled in at initialization time. We only need - // to do this when using non-sequential mode. + /* + Chunk sizes. When using sequential mode, these will have been filled in at initialization time. We only need + to do this when using non-sequential mode. + */ if (pWav->onSeek && !pWav->isSequentialWrite) { if (pWav->container == drwav_container_riff) { - // The "RIFF" chunk size. + /* 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. + /* 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. + /* 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); } - // The "data" chunk size. + /* The "data" chunk size. */ if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos + 16, drwav_seek_origin_start)) { drwav_uint64 dataChunkSize = drwav_data_chunk_size_w64(pWav->dataChunkDataSize); pWav->onWrite(pWav->pUserData, &dataChunkSize, 8); @@ -1819,8 +2274,10 @@ void drwav_uninit(drwav* pWav) } #ifndef DR_WAV_NO_STDIO - // If we opened the file with drwav_open_file() we will want to close the file handle. We can know whether or not drwav_open_file() - // was used by looking at the onRead and onSeek callbacks. + /* + If we opened the file with drwav_open_file() we will want to close the file handle. We can know whether or not drwav_open_file() + was used by looking at the onRead and onSeek callbacks. + */ if (pWav->onRead == drwav__on_read_stdio || pWav->onWrite == drwav__on_write_stdio) { fclose((FILE*)pWav->pUserData); } @@ -1829,13 +2286,18 @@ void drwav_uninit(drwav* pWav) drwav* drwav_open(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData) +{ + return drwav_open_ex(onRead, onSeek, NULL, pUserData, NULL, 0); +} + +drwav* drwav_open_ex(drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags) { drwav* pWav = (drwav*)DRWAV_MALLOC(sizeof(*pWav)); if (pWav == NULL) { return NULL; } - if (!drwav_init(pWav, onRead, onSeek, pUserData)) { + if (!drwav_init_ex(pWav, onRead, onSeek, onChunk, pReadSeekUserData, pChunkUserData, flags)) { DRWAV_FREE(pWav); return NULL; } @@ -1878,6 +2340,8 @@ void drwav_close(drwav* pWav) size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut) { + size_t bytesRead; + if (pWav == NULL || bytesToRead == 0 || pBufferOut == NULL) { return 0; } @@ -1886,7 +2350,7 @@ size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut) bytesToRead = (size_t)pWav->bytesRemaining; } - size_t bytesRead = pWav->onRead(pWav->pUserData, pBufferOut, bytesToRead); + bytesRead = pWav->onRead(pWav->pUserData, pBufferOut, bytesToRead); pWav->bytesRemaining -= bytesRead; return bytesRead; @@ -1894,28 +2358,64 @@ size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut) drwav_uint64 drwav_read(drwav* pWav, drwav_uint64 samplesToRead, void* pBufferOut) { + drwav_uint32 bytesPerSample; + size_t bytesRead; + if (pWav == NULL || samplesToRead == 0 || pBufferOut == NULL) { return 0; } - // Cannot use this function for compressed formats. + /* Cannot use this function for compressed formats. */ if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { return 0; } - // Don't try to read more samples than can potentially fit in the output buffer. - if (samplesToRead * pWav->bytesPerSample > SIZE_MAX) { - samplesToRead = SIZE_MAX / pWav->bytesPerSample; + bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { + return 0; } - size_t bytesRead = drwav_read_raw(pWav, (size_t)(samplesToRead * pWav->bytesPerSample), pBufferOut); - return bytesRead / pWav->bytesPerSample; + /* Don't try to read more samples than can potentially fit in the output buffer. */ + if (samplesToRead * bytesPerSample > DRWAV_SIZE_MAX) { + samplesToRead = DRWAV_SIZE_MAX / bytesPerSample; + } + + bytesRead = drwav_read_raw(pWav, (size_t)(samplesToRead * bytesPerSample), pBufferOut); + return bytesRead / bytesPerSample; } -drwav_bool32 drwav_seek_to_first_sample(drwav* pWav) +drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut) +{ + drwav_uint32 bytesPerFrame; + size_t bytesRead; + + if (pWav == NULL || framesToRead == 0 || pBufferOut == NULL) { + return 0; + } + + /* Cannot use this function for compressed formats. */ + if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { + return 0; + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + /* 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; + } + + bytesRead = drwav_read_raw(pWav, (size_t)(framesToRead * bytesPerFrame), pBufferOut); + return bytesRead / bytesPerFrame; +} + +drwav_bool32 drwav_seek_to_first_pcm_frame(drwav* pWav) { if (pWav->onWrite != NULL) { - return DRWAV_FALSE; // No seeking in write mode. + return DRWAV_FALSE; /* No seeking in write mode. */ } if (!pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos, drwav_seek_origin_start)) { @@ -1932,36 +2432,39 @@ drwav_bool32 drwav_seek_to_first_sample(drwav* pWav) drwav_bool32 drwav_seek_to_sample(drwav* pWav, drwav_uint64 sample) { - // Seeking should be compatible with wave files > 2GB. + /* Seeking should be compatible with wave files > 2GB. */ if (pWav->onWrite != NULL) { - return DRWAV_FALSE; // No seeking in write mode. + return DRWAV_FALSE; /* No seeking in write mode. */ } if (pWav == NULL || pWav->onSeek == NULL) { return DRWAV_FALSE; } - // If there are no samples, just return DRWAV_TRUE without doing anything. + /* If there are no samples, just return DRWAV_TRUE without doing anything. */ if (pWav->totalSampleCount == 0) { return DRWAV_TRUE; } - // Make sure the sample is clamped. + /* Make sure the sample is clamped. */ if (sample >= pWav->totalSampleCount) { sample = pWav->totalSampleCount - 1; } - - // For compressed formats we just use a slow generic seek. If we are seeking forward we just seek forward. If we are going backwards we need - // to seek back to the start. + /* + For compressed formats we just use a slow generic seek. If we are seeking forward we just seek forward. If we are going backwards we need + to seek back to the start. + */ if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { - // TODO: This can be optimized. + /* TODO: This can be optimized. */ - // If we're seeking forward it's simple - just keep reading samples until we hit the sample we're requesting. If we're seeking backwards, - // we first need to seek back to the start and then just do the same thing as a forward seek. + /* + If we're seeking forward it's simple - just keep reading samples until we hit the sample we're requesting. If we're seeking backwards, + we first need to seek back to the start and then just do the same thing as a forward seek. + */ if (sample < pWav->compressed.iCurrentSample) { - if (!drwav_seek_to_first_sample(pWav)) { + if (!drwav_seek_to_first_pcm_frame(pWav)) { return DRWAV_FALSE; } } @@ -1971,18 +2474,18 @@ drwav_bool32 drwav_seek_to_sample(drwav* pWav, drwav_uint64 sample) drwav_int16 devnull[2048]; while (offset > 0) { + drwav_uint64 samplesRead = 0; drwav_uint64 samplesToRead = offset; if (samplesToRead > 2048) { samplesToRead = 2048; } - drwav_uint64 samplesRead = 0; if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { samplesRead = drwav_read_s16__msadpcm(pWav, samplesToRead, devnull); } else if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { samplesRead = drwav_read_s16__ima(pWav, samplesToRead, devnull); } else { - 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. + 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. */ } if (samplesRead != samplesToRead) { @@ -1993,19 +2496,23 @@ drwav_bool32 drwav_seek_to_sample(drwav* pWav, drwav_uint64 sample) } } } else { - drwav_uint64 totalSizeInBytes = pWav->totalSampleCount * pWav->bytesPerSample; + drwav_uint64 totalSizeInBytes; + drwav_uint64 currentBytePos; + drwav_uint64 targetBytePos; + drwav_uint64 offset; + + totalSizeInBytes = pWav->totalPCMFrameCount * drwav_get_bytes_per_pcm_frame(pWav); drwav_assert(totalSizeInBytes >= pWav->bytesRemaining); - drwav_uint64 currentBytePos = totalSizeInBytes - pWav->bytesRemaining; - drwav_uint64 targetBytePos = sample * pWav->bytesPerSample; + currentBytePos = totalSizeInBytes - pWav->bytesRemaining; + targetBytePos = sample * drwav_get_bytes_per_sample(pWav); - drwav_uint64 offset; if (currentBytePos < targetBytePos) { - // Offset forwards. + /* Offset forwards. */ offset = (targetBytePos - currentBytePos); } else { - // Offset backwards. - if (!drwav_seek_to_first_sample(pWav)) { + /* Offset backwards. */ + if (!drwav_seek_to_first_pcm_frame(pWav)) { return DRWAV_FALSE; } offset = targetBytePos; @@ -2025,14 +2532,21 @@ drwav_bool32 drwav_seek_to_sample(drwav* pWav, drwav_uint64 sample) return DRWAV_TRUE; } +drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetFrameIndex) +{ + return drwav_seek_to_sample(pWav, targetFrameIndex * pWav->channels); +} + size_t drwav_write_raw(drwav* pWav, size_t bytesToWrite, const void* pData) { + size_t bytesWritten; + if (pWav == NULL || bytesToWrite == 0 || pData == NULL) { return 0; } - size_t bytesWritten = pWav->onWrite(pWav->pUserData, pData, bytesToWrite); + bytesWritten = pWav->onWrite(pWav->pUserData, pData, bytesToWrite); pWav->dataChunkDataSize += bytesWritten; return bytesWritten; @@ -2040,24 +2554,29 @@ size_t drwav_write_raw(drwav* pWav, size_t bytesToWrite, const void* pData) drwav_uint64 drwav_write(drwav* pWav, drwav_uint64 samplesToWrite, const void* pData) { + drwav_uint64 bytesToWrite; + drwav_uint64 bytesWritten; + const drwav_uint8* pRunningData; + if (pWav == NULL || samplesToWrite == 0 || pData == NULL) { return 0; } - drwav_uint64 bytesToWrite = ((samplesToWrite * pWav->bitsPerSample) / 8); - if (bytesToWrite > SIZE_MAX) { + bytesToWrite = ((samplesToWrite * pWav->bitsPerSample) / 8); + if (bytesToWrite > DRWAV_SIZE_MAX) { return 0; } - drwav_uint64 bytesWritten = 0; - const drwav_uint8* pRunningData = (const drwav_uint8*)pData; + bytesWritten = 0; + pRunningData = (const drwav_uint8*)pData; while (bytesToWrite > 0) { + size_t bytesJustWritten; drwav_uint64 bytesToWriteThisIteration = bytesToWrite; - if (bytesToWriteThisIteration > SIZE_MAX) { - bytesToWriteThisIteration = SIZE_MAX; + if (bytesToWriteThisIteration > DRWAV_SIZE_MAX) { + bytesToWriteThisIteration = DRWAV_SIZE_MAX; } - size_t bytesJustWritten = drwav_write_raw(pWav, (size_t)bytesToWriteThisIteration, pRunningData); + bytesJustWritten = drwav_write_raw(pWav, (size_t)bytesToWriteThisIteration, pRunningData); if (bytesJustWritten == 0) { break; } @@ -2070,23 +2589,28 @@ drwav_uint64 drwav_write(drwav* pWav, drwav_uint64 samplesToWrite, const void* p return (bytesWritten * 8) / pWav->bitsPerSample; } +drwav_uint64 drwav_write_pcm_frames(drwav* pWav, drwav_uint64 framesToWrite, const void* pData) +{ + return drwav_write(pWav, framesToWrite * pWav->channels, pData) / pWav->channels; +} + drwav_uint64 drwav_read_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) { + drwav_uint64 totalSamplesRead = 0; + drwav_assert(pWav != NULL); drwav_assert(samplesToRead > 0); drwav_assert(pBufferOut != NULL); - // TODO: Lots of room for optimization here. - - drwav_uint64 totalSamplesRead = 0; + /* TODO: Lots of room for optimization here. */ while (samplesToRead > 0 && pWav->compressed.iCurrentSample < pWav->totalSampleCount) { - // If there are no cached samples we need to load a new block. + /* If there are no cached samples we need to load a new block. */ if (pWav->msadpcm.cachedSampleCount == 0 && pWav->msadpcm.bytesRemainingInBlock == 0) { if (pWav->channels == 1) { - // Mono. + /* Mono. */ drwav_uint8 header[7]; if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { return totalSamplesRead; @@ -2101,7 +2625,7 @@ drwav_uint64 drwav_read_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, dr pWav->msadpcm.cachedSamples[3] = pWav->msadpcm.prevSamples[0][1]; pWav->msadpcm.cachedSampleCount = 2; } else { - // Stereo. + /* Stereo. */ drwav_uint8 header[14]; if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { return totalSamplesRead; @@ -2125,7 +2649,7 @@ drwav_uint64 drwav_read_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, dr } } - // Output anything that's cached. + /* Output anything that's cached. */ while (samplesToRead > 0 && pWav->msadpcm.cachedSampleCount > 0 && pWav->compressed.iCurrentSample < pWav->totalSampleCount) { pBufferOut[0] = (drwav_int16)pWav->msadpcm.cachedSamples[drwav_countof(pWav->msadpcm.cachedSamples) - pWav->msadpcm.cachedSampleCount]; pWav->msadpcm.cachedSampleCount -= 1; @@ -2141,22 +2665,14 @@ drwav_uint64 drwav_read_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, dr } - // If there's nothing left in the cache, just go ahead and load more. If there's nothing left to load in the current block we just continue to the next - // loop iteration which will trigger the loading of a new block. + /* + If there's nothing left in the cache, just go ahead and load more. If there's nothing left to load in the current block we just continue to the next + loop iteration which will trigger the loading of a new block. + */ if (pWav->msadpcm.cachedSampleCount == 0) { if (pWav->msadpcm.bytesRemainingInBlock == 0) { continue; } else { - drwav_uint8 nibbles; - if (pWav->onRead(pWav->pUserData, &nibbles, 1) != 1) { - return totalSamplesRead; - } - pWav->msadpcm.bytesRemainingInBlock -= 1; - - // TODO: Optimize away these if statements. - drwav_int32 nibble0 = ((nibbles & 0xF0) >> 4); if ((nibbles & 0x80)) { nibble0 |= 0xFFFFFFF0UL; } - drwav_int32 nibble1 = ((nibbles & 0x0F) >> 0); if ((nibbles & 0x08)) { nibble1 |= 0xFFFFFFF0UL; } - static drwav_int32 adaptationTable[] = { 230, 230, 230, 230, 307, 409, 512, 614, 768, 614, 512, 409, 307, 230, 230, 230 @@ -2164,9 +2680,24 @@ drwav_uint64 drwav_read_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, dr static drwav_int32 coeff1Table[] = { 256, 512, 0, 192, 240, 460, 392 }; static drwav_int32 coeff2Table[] = { 0, -256, 0, 64, 0, -208, -232 }; + drwav_uint8 nibbles; + drwav_int32 nibble0; + drwav_int32 nibble1; + + if (pWav->onRead(pWav->pUserData, &nibbles, 1) != 1) { + return totalSamplesRead; + } + pWav->msadpcm.bytesRemainingInBlock -= 1; + + /* TODO: Optimize away these if statements. */ + nibble0 = ((nibbles & 0xF0) >> 4); if ((nibbles & 0x80)) { nibble0 |= 0xFFFFFFF0UL; } + nibble1 = ((nibbles & 0x0F) >> 0); if ((nibbles & 0x08)) { nibble1 |= 0xFFFFFFF0UL; } + if (pWav->channels == 1) { - // Mono. + /* Mono. */ drwav_int32 newSample0; + drwav_int32 newSample1; + newSample0 = ((pWav->msadpcm.prevSamples[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevSamples[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; newSample0 += nibble0 * pWav->msadpcm.delta[0]; newSample0 = drwav_clamp(newSample0, -32768, 32767); @@ -2180,7 +2711,6 @@ drwav_uint64 drwav_read_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, dr pWav->msadpcm.prevSamples[0][1] = newSample0; - drwav_int32 newSample1; newSample1 = ((pWav->msadpcm.prevSamples[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevSamples[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; newSample1 += nibble1 * pWav->msadpcm.delta[0]; newSample1 = drwav_clamp(newSample1, -32768, 32767); @@ -2198,10 +2728,11 @@ drwav_uint64 drwav_read_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, dr pWav->msadpcm.cachedSamples[3] = newSample1; pWav->msadpcm.cachedSampleCount = 2; } else { - // Stereo. - - // Left. + /* Stereo. */ drwav_int32 newSample0; + drwav_int32 newSample1; + + /* Left. */ newSample0 = ((pWav->msadpcm.prevSamples[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevSamples[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; newSample0 += nibble0 * pWav->msadpcm.delta[0]; newSample0 = drwav_clamp(newSample0, -32768, 32767); @@ -2215,8 +2746,7 @@ drwav_uint64 drwav_read_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, dr pWav->msadpcm.prevSamples[0][1] = newSample0; - // Right. - drwav_int32 newSample1; + /* Right. */ newSample1 = ((pWav->msadpcm.prevSamples[1][1] * coeff1Table[pWav->msadpcm.predictor[1]]) + (pWav->msadpcm.prevSamples[1][0] * coeff2Table[pWav->msadpcm.predictor[1]])) >> 8; newSample1 += nibble1 * pWav->msadpcm.delta[1]; newSample1 = drwav_clamp(newSample1, -32768, 32767); @@ -2242,19 +2772,19 @@ drwav_uint64 drwav_read_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, dr drwav_uint64 drwav_read_s16__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) { + drwav_uint64 totalSamplesRead = 0; + drwav_assert(pWav != NULL); drwav_assert(samplesToRead > 0); drwav_assert(pBufferOut != NULL); - // TODO: Lots of room for optimization here. - - drwav_uint64 totalSamplesRead = 0; + /* TODO: Lots of room for optimization here. */ while (samplesToRead > 0 && pWav->compressed.iCurrentSample < pWav->totalSampleCount) { - // If there are no cached samples we need to load a new block. + /* If there are no cached samples we need to load a new block. */ if (pWav->ima.cachedSampleCount == 0 && pWav->ima.bytesRemainingInBlock == 0) { if (pWav->channels == 1) { - // Mono. + /* Mono. */ drwav_uint8 header[4]; if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { return totalSamplesRead; @@ -2266,7 +2796,7 @@ drwav_uint64 drwav_read_s16__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_ pWav->ima.cachedSamples[drwav_countof(pWav->ima.cachedSamples) - 1] = pWav->ima.predictor[0]; pWav->ima.cachedSampleCount = 1; } else { - // Stereo. + /* Stereo. */ drwav_uint8 header[8]; if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { return totalSamplesRead; @@ -2284,7 +2814,7 @@ drwav_uint64 drwav_read_s16__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_ } } - // Output anything that's cached. + /* Output anything that's cached. */ while (samplesToRead > 0 && pWav->ima.cachedSampleCount > 0 && pWav->compressed.iCurrentSample < pWav->totalSampleCount) { pBufferOut[0] = (drwav_int16)pWav->ima.cachedSamples[drwav_countof(pWav->ima.cachedSamples) - pWav->ima.cachedSampleCount]; pWav->ima.cachedSampleCount -= 1; @@ -2299,8 +2829,10 @@ drwav_uint64 drwav_read_s16__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_ return totalSamplesRead; } - // If there's nothing left in the cache, just go ahead and load more. If there's nothing left to load in the current block we just continue to the next - // loop iteration which will trigger the loading of a new block. + /* + If there's nothing left in the cache, just go ahead and load more. If there's nothing left to load in the current block we just continue to the next + loop iteration which will trigger the loading of a new block. + */ if (pWav->ima.cachedSampleCount == 0) { if (pWav->ima.bytesRemainingInBlock == 0) { continue; @@ -2322,17 +2854,22 @@ drwav_uint64 drwav_read_s16__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_ 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 }; - // 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. + 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. + */ pWav->ima.cachedSampleCount = 8 * pWav->channels; - for (drwav_uint32 iChannel = 0; iChannel < pWav->channels; ++iChannel) { + for (iChannel = 0; iChannel < pWav->channels; ++iChannel) { + drwav_uint32 iByte; drwav_uint8 nibbles[4]; if (pWav->onRead(pWav->pUserData, &nibbles, 4) != 4) { return totalSamplesRead; } pWav->ima.bytesRemainingInBlock -= 4; - for (drwav_uint32 iByte = 0; iByte < 4; ++iByte) { + for (iByte = 0; iByte < 4; ++iByte) { drwav_uint8 nibble0 = ((nibbles[iByte] & 0x0F) >> 0); drwav_uint8 nibble1 = ((nibbles[iByte] & 0xF0) >> 4); @@ -2425,19 +2962,21 @@ static DRWAV_INLINE drwav_int16 drwav__mulaw_to_s16(drwav_uint8 sampleIn) -static void drwav__pcm_to_s16(drwav_int16* pOut, const unsigned char* pIn, size_t totalSampleCount, unsigned short bytesPerSample) +static void drwav__pcm_to_s16(drwav_int16* pOut, const unsigned char* pIn, size_t totalSampleCount, unsigned int bytesPerSample) { - // Special case for 8-bit sample data because it's treated as unsigned. + unsigned int i; + + /* Special case for 8-bit sample data because it's treated as unsigned. */ if (bytesPerSample == 1) { drwav_u8_to_s16(pOut, pIn, totalSampleCount); return; } - // Slightly more optimal implementation for common formats. + /* Slightly more optimal implementation for common formats. */ if (bytesPerSample == 2) { - for (unsigned int i = 0; i < totalSampleCount; ++i) { - *pOut++ = ((drwav_int16*)pIn)[i]; + for (i = 0; i < totalSampleCount; ++i) { + *pOut++ = ((const drwav_int16*)pIn)[i]; } return; } @@ -2451,15 +2990,15 @@ static void drwav__pcm_to_s16(drwav_int16* pOut, const unsigned char* pIn, size_ } - // Anything more than 64 bits per sample is not supported. + /* Anything more than 64 bits per sample is not supported. */ if (bytesPerSample > 8) { drwav_zero_memory(pOut, totalSampleCount * sizeof(*pOut)); return; } - // Generic, slow converter. - for (unsigned int i = 0; i < totalSampleCount; ++i) { + /* Generic, slow converter. */ + for (i = 0; i < totalSampleCount; ++i) { drwav_uint64 sample = 0; unsigned int shift = (8 - bytesPerSample) * 8; @@ -2474,16 +3013,16 @@ static void drwav__pcm_to_s16(drwav_int16* pOut, const unsigned char* pIn, size_ } } -static void drwav__ieee_to_s16(drwav_int16* pOut, const unsigned char* pIn, size_t totalSampleCount, unsigned short bytesPerSample) +static void drwav__ieee_to_s16(drwav_int16* pOut, const unsigned char* pIn, size_t totalSampleCount, unsigned int bytesPerSample) { if (bytesPerSample == 4) { - drwav_f32_to_s16(pOut, (float*)pIn, totalSampleCount); + drwav_f32_to_s16(pOut, (const float*)pIn, totalSampleCount); return; } else if (bytesPerSample == 8) { - drwav_f64_to_s16(pOut, (double*)pIn, totalSampleCount); + drwav_f64_to_s16(pOut, (const double*)pIn, totalSampleCount); return; } else { - // Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. + /* Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. */ drwav_zero_memory(pOut, totalSampleCount * sizeof(*pOut)); return; } @@ -2491,20 +3030,29 @@ static void drwav__ieee_to_s16(drwav_int16* pOut, const unsigned char* pIn, size drwav_uint64 drwav_read_s16__pcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) { - // Fast path. - if (pWav->bytesPerSample == 2) { + drwav_uint32 bytesPerSample; + drwav_uint64 totalSamplesRead; + unsigned char sampleData[4096]; + + /* Fast path. */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 16) { return drwav_read(pWav, samplesToRead, pBufferOut); } + + bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { + return 0; + } - drwav_uint64 totalSamplesRead = 0; - unsigned char sampleData[4096]; + totalSamplesRead = 0; + while (samplesToRead > 0) { - drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); if (samplesRead == 0) { break; } - drwav__pcm_to_s16(pBufferOut, sampleData, (size_t)samplesRead, pWav->bytesPerSample); + drwav__pcm_to_s16(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); pBufferOut += samplesRead; samplesToRead -= samplesRead; @@ -2516,15 +3064,23 @@ drwav_uint64 drwav_read_s16__pcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_ drwav_uint64 drwav_read_s16__ieee(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) { - drwav_uint64 totalSamplesRead = 0; + drwav_uint64 totalSamplesRead; unsigned char sampleData[4096]; + + drwav_uint32 bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { + return 0; + } + + totalSamplesRead = 0; + while (samplesToRead > 0) { - drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); if (samplesRead == 0) { break; } - drwav__ieee_to_s16(pBufferOut, sampleData, (size_t)samplesRead, pWav->bytesPerSample); + drwav__ieee_to_s16(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); pBufferOut += samplesRead; samplesToRead -= samplesRead; @@ -2536,10 +3092,18 @@ drwav_uint64 drwav_read_s16__ieee(drwav* pWav, drwav_uint64 samplesToRead, drwav drwav_uint64 drwav_read_s16__alaw(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) { - drwav_uint64 totalSamplesRead = 0; + drwav_uint64 totalSamplesRead; unsigned char sampleData[4096]; + + drwav_uint32 bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { + return 0; + } + + totalSamplesRead = 0; + while (samplesToRead > 0) { - drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); if (samplesRead == 0) { break; } @@ -2556,10 +3120,18 @@ drwav_uint64 drwav_read_s16__alaw(drwav* pWav, drwav_uint64 samplesToRead, drwav drwav_uint64 drwav_read_s16__mulaw(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) { - drwav_uint64 totalSamplesRead = 0; + drwav_uint64 totalSamplesRead; unsigned char sampleData[4096]; + + drwav_uint32 bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { + return 0; + } + + totalSamplesRead = 0; + while (samplesToRead > 0) { - drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); if (samplesRead == 0) { break; } @@ -2580,9 +3152,9 @@ drwav_uint64 drwav_read_s16(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16 return 0; } - // Don't try to read more samples than can potentially fit in the output buffer. - if (samplesToRead * sizeof(drwav_int16) > SIZE_MAX) { - samplesToRead = SIZE_MAX / sizeof(drwav_int16); + /* Don't try to read more samples than can potentially fit in the output buffer. */ + if (samplesToRead * sizeof(drwav_int16) > DRWAV_SIZE_MAX) { + samplesToRead = DRWAV_SIZE_MAX / sizeof(drwav_int16); } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { @@ -2612,13 +3184,19 @@ drwav_uint64 drwav_read_s16(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16 return 0; } +drwav_uint64 drwav_read_pcm_frames_s16(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + return drwav_read_s16(pWav, framesToRead * pWav->channels, pBufferOut) / pWav->channels; +} + void drwav_u8_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) { int r; - for (size_t i = 0; i < sampleCount; ++i) { + size_t i; + for (i = 0; i < sampleCount; ++i) { int x = pIn[i]; - r = x - 128; - r = r << 8; + r = x << 8; + r = r - 32768; pOut[i] = (short)r; } } @@ -2626,8 +3204,9 @@ void drwav_u8_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCou void drwav_s24_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) { int r; - for (size_t i = 0; i < sampleCount; ++i) { - int x = ((int)(((unsigned int)(((unsigned char*)pIn)[i*3+0]) << 8) | ((unsigned int)(((unsigned char*)pIn)[i*3+1]) << 16) | ((unsigned int)(((unsigned char*)pIn)[i*3+2])) << 24)) >> 8; + size_t i; + for (i = 0; i < sampleCount; ++i) { + int x = ((int)(((unsigned int)(((const unsigned char*)pIn)[i*3+0]) << 8) | ((unsigned int)(((const unsigned char*)pIn)[i*3+1]) << 16) | ((unsigned int)(((const unsigned char*)pIn)[i*3+2])) << 24)) >> 8; r = x >> 8; pOut[i] = (short)r; } @@ -2636,7 +3215,8 @@ void drwav_s24_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCo void drwav_s32_to_s16(drwav_int16* pOut, const drwav_int32* pIn, size_t sampleCount) { int r; - for (size_t i = 0; i < sampleCount; ++i) { + size_t i; + for (i = 0; i < sampleCount; ++i) { int x = pIn[i]; r = x >> 16; pOut[i] = (short)r; @@ -2646,7 +3226,8 @@ void drwav_s32_to_s16(drwav_int16* pOut, const drwav_int32* pIn, size_t sampleCo void drwav_f32_to_s16(drwav_int16* pOut, const float* pIn, size_t sampleCount) { int r; - for (size_t i = 0; i < sampleCount; ++i) { + size_t i; + for (i = 0; i < sampleCount; ++i) { float x = pIn[i]; float c; c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); @@ -2660,7 +3241,8 @@ void drwav_f32_to_s16(drwav_int16* pOut, const float* pIn, size_t sampleCount) void drwav_f64_to_s16(drwav_int16* pOut, const double* pIn, size_t sampleCount) { int r; - for (size_t i = 0; i < sampleCount; ++i) { + size_t i; + for (i = 0; i < sampleCount; ++i) { double x = pIn[i]; double c; c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); @@ -2673,29 +3255,33 @@ void drwav_f64_to_s16(drwav_int16* pOut, const double* pIn, size_t sampleCount) void drwav_alaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) { - for (size_t i = 0; i < sampleCount; ++i) { + size_t i; + for (i = 0; i < sampleCount; ++i) { pOut[i] = drwav__alaw_to_s16(pIn[i]); } } void drwav_mulaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) { - for (size_t i = 0; i < sampleCount; ++i) { + size_t i; + for (i = 0; i < sampleCount; ++i) { pOut[i] = drwav__mulaw_to_s16(pIn[i]); } } -static void drwav__pcm_to_f32(float* pOut, const unsigned char* pIn, size_t sampleCount, unsigned short bytesPerSample) +static void drwav__pcm_to_f32(float* pOut, const unsigned char* pIn, size_t sampleCount, unsigned int bytesPerSample) { - // Special case for 8-bit sample data because it's treated as unsigned. + unsigned int i; + + /* Special case for 8-bit sample data because it's treated as unsigned. */ if (bytesPerSample == 1) { drwav_u8_to_f32(pOut, pIn, sampleCount); return; } - // Slightly more optimal implementation for common formats. + /* Slightly more optimal implementation for common formats. */ if (bytesPerSample == 2) { drwav_s16_to_f32(pOut, (const drwav_int16*)pIn, sampleCount); return; @@ -2710,15 +3296,15 @@ static void drwav__pcm_to_f32(float* pOut, const unsigned char* pIn, size_t samp } - // Anything more than 64 bits per sample is not supported. + /* Anything more than 64 bits per sample is not supported. */ if (bytesPerSample > 8) { drwav_zero_memory(pOut, sampleCount * sizeof(*pOut)); return; } - // Generic, slow converter. - for (unsigned int i = 0; i < sampleCount; ++i) { + /* Generic, slow converter. */ + for (i = 0; i < sampleCount; ++i) { drwav_uint64 sample = 0; unsigned int shift = (8 - bytesPerSample) * 8; @@ -2733,18 +3319,19 @@ static void drwav__pcm_to_f32(float* pOut, const unsigned char* pIn, size_t samp } } -static void drwav__ieee_to_f32(float* pOut, const unsigned char* pIn, size_t sampleCount, unsigned short bytesPerSample) +static void drwav__ieee_to_f32(float* pOut, const unsigned char* pIn, size_t sampleCount, unsigned int bytesPerSample) { if (bytesPerSample == 4) { - for (unsigned int i = 0; i < sampleCount; ++i) { - *pOut++ = ((float*)pIn)[i]; + unsigned int i; + for (i = 0; i < sampleCount; ++i) { + *pOut++ = ((const float*)pIn)[i]; } return; } else if (bytesPerSample == 8) { - drwav_f64_to_f32(pOut, (double*)pIn, sampleCount); + drwav_f64_to_f32(pOut, (const double*)pIn, sampleCount); return; } else { - // Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. + /* Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. */ drwav_zero_memory(pOut, sampleCount * sizeof(*pOut)); return; } @@ -2753,19 +3340,23 @@ static void drwav__ieee_to_f32(float* pOut, const unsigned char* pIn, size_t sam drwav_uint64 drwav_read_f32__pcm(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut) { - if (pWav->bytesPerSample == 0) { + drwav_uint64 totalSamplesRead; + unsigned char sampleData[4096]; + + drwav_uint32 bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { return 0; } - drwav_uint64 totalSamplesRead = 0; - unsigned char sampleData[4096]; + totalSamplesRead = 0; + while (samplesToRead > 0) { - drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); if (samplesRead == 0) { break; } - drwav__pcm_to_f32(pBufferOut, sampleData, (size_t)samplesRead, pWav->bytesPerSample); + drwav__pcm_to_f32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); pBufferOut += samplesRead; samplesToRead -= samplesRead; @@ -2777,8 +3368,10 @@ drwav_uint64 drwav_read_f32__pcm(drwav* pWav, drwav_uint64 samplesToRead, float* drwav_uint64 drwav_read_f32__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, 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 - // want to duplicate that code. + /* + 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 + want to duplicate that code. + */ drwav_uint64 totalSamplesRead = 0; drwav_int16 samples16[2048]; while (samplesToRead > 0) { @@ -2787,7 +3380,7 @@ drwav_uint64 drwav_read_f32__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, fl break; } - drwav_s16_to_f32(pBufferOut, samples16, (size_t)samplesRead); // <-- Safe cast because we're clamping to 2048. + drwav_s16_to_f32(pBufferOut, samples16, (size_t)samplesRead); /* <-- Safe cast because we're clamping to 2048. */ pBufferOut += samplesRead; samplesToRead -= samplesRead; @@ -2799,8 +3392,10 @@ drwav_uint64 drwav_read_f32__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, fl drwav_uint64 drwav_read_f32__ima(drwav* pWav, drwav_uint64 samplesToRead, 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 - // want to duplicate that code. + /* + 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 + want to duplicate that code. + */ drwav_uint64 totalSamplesRead = 0; drwav_int16 samples16[2048]; while (samplesToRead > 0) { @@ -2809,7 +3404,7 @@ drwav_uint64 drwav_read_f32__ima(drwav* pWav, drwav_uint64 samplesToRead, float* break; } - drwav_s16_to_f32(pBufferOut, samples16, (size_t)samplesRead); // <-- Safe cast because we're clamping to 2048. + drwav_s16_to_f32(pBufferOut, samples16, (size_t)samplesRead); /* <-- Safe cast because we're clamping to 2048. */ pBufferOut += samplesRead; samplesToRead -= samplesRead; @@ -2821,24 +3416,29 @@ drwav_uint64 drwav_read_f32__ima(drwav* pWav, drwav_uint64 samplesToRead, float* drwav_uint64 drwav_read_f32__ieee(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut) { - // Fast path. - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT && pWav->bytesPerSample == 4) { + drwav_uint64 totalSamplesRead; + unsigned char sampleData[4096]; + drwav_uint32 bytesPerSample; + + /* Fast path. */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT && pWav->bitsPerSample == 32) { return drwav_read(pWav, samplesToRead, pBufferOut); } - - if (pWav->bytesPerSample == 0) { + + bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { return 0; } - drwav_uint64 totalSamplesRead = 0; - unsigned char sampleData[4096]; + totalSamplesRead = 0; + while (samplesToRead > 0) { - drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); if (samplesRead == 0) { break; } - drwav__ieee_to_f32(pBufferOut, sampleData, (size_t)samplesRead, pWav->bytesPerSample); + drwav__ieee_to_f32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); pBufferOut += samplesRead; samplesToRead -= samplesRead; @@ -2850,14 +3450,17 @@ drwav_uint64 drwav_read_f32__ieee(drwav* pWav, drwav_uint64 samplesToRead, float drwav_uint64 drwav_read_f32__alaw(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut) { - if (pWav->bytesPerSample == 0) { + drwav_uint64 totalSamplesRead; + unsigned char sampleData[4096]; + drwav_uint32 bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { return 0; } - drwav_uint64 totalSamplesRead = 0; - unsigned char sampleData[4096]; + totalSamplesRead = 0; + while (samplesToRead > 0) { - drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); if (samplesRead == 0) { break; } @@ -2874,14 +3477,18 @@ drwav_uint64 drwav_read_f32__alaw(drwav* pWav, drwav_uint64 samplesToRead, float drwav_uint64 drwav_read_f32__mulaw(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut) { - if (pWav->bytesPerSample == 0) { + drwav_uint64 totalSamplesRead; + unsigned char sampleData[4096]; + + drwav_uint32 bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { return 0; } - drwav_uint64 totalSamplesRead = 0; - unsigned char sampleData[4096]; + totalSamplesRead = 0; + while (samplesToRead > 0) { - drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); if (samplesRead == 0) { break; } @@ -2902,9 +3509,9 @@ drwav_uint64 drwav_read_f32(drwav* pWav, drwav_uint64 samplesToRead, float* pBuf return 0; } - // Don't try to read more samples than can potentially fit in the output buffer. - if (samplesToRead * sizeof(float) > SIZE_MAX) { - samplesToRead = SIZE_MAX / sizeof(float); + /* Don't try to read more samples than can potentially fit in the output buffer. */ + if (samplesToRead * sizeof(float) > DRWAV_SIZE_MAX) { + samplesToRead = DRWAV_SIZE_MAX / sizeof(float); } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { @@ -2934,22 +3541,31 @@ drwav_uint64 drwav_read_f32(drwav* pWav, drwav_uint64 samplesToRead, float* pBuf return 0; } +drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + return drwav_read_f32(pWav, framesToRead * pWav->channels, pBufferOut) / pWav->channels; +} + void drwav_u8_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) { + size_t i; + if (pOut == NULL || pIn == NULL) { return; } #ifdef DR_WAV_LIBSNDFILE_COMPAT - // It appears libsndfile uses slightly different logic for the u8 -> f32 conversion to dr_wav, which in my opinion is incorrect. It appears - // libsndfile performs the conversion something like "f32 = (u8 / 256) * 2 - 1", however I think it should be "f32 = (u8 / 255) * 2 - 1" (note - // the divisor of 256 vs 255). I use libsndfile as a benchmark for testing, so I'm therefore leaving this block here just for my automated - // correctness testing. This is disabled by default. - for (size_t i = 0; i < sampleCount; ++i) { + /* + It appears libsndfile uses slightly different logic for the u8 -> f32 conversion to dr_wav, which in my opinion is incorrect. It appears + libsndfile performs the conversion something like "f32 = (u8 / 256) * 2 - 1", however I think it should be "f32 = (u8 / 255) * 2 - 1" (note + the divisor of 256 vs 255). I use libsndfile as a benchmark for testing, so I'm therefore leaving this block here just for my automated + correctness testing. This is disabled by default. + */ + for (i = 0; i < sampleCount; ++i) { *pOut++ = (pIn[i] / 256.0f) * 2 - 1; } #else - for (size_t i = 0; i < sampleCount; ++i) { + for (i = 0; i < sampleCount; ++i) { *pOut++ = (pIn[i] / 255.0f) * 2 - 1; } #endif @@ -2957,22 +3573,26 @@ void drwav_u8_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) void drwav_s16_to_f32(float* pOut, const drwav_int16* pIn, size_t sampleCount) { + size_t i; + if (pOut == NULL || pIn == NULL) { return; } - for (size_t i = 0; i < sampleCount; ++i) { + for (i = 0; i < sampleCount; ++i) { *pOut++ = pIn[i] / 32768.0f; } } void drwav_s24_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) { + size_t i; + if (pOut == NULL || pIn == NULL) { return; } - for (size_t i = 0; i < sampleCount; ++i) { + for (i = 0; i < sampleCount; ++i) { unsigned int s0 = pIn[i*3 + 0]; unsigned int s1 = pIn[i*3 + 1]; unsigned int s2 = pIn[i*3 + 2]; @@ -2984,59 +3604,68 @@ void drwav_s24_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) void drwav_s32_to_f32(float* pOut, const drwav_int32* pIn, size_t sampleCount) { + size_t i; if (pOut == NULL || pIn == NULL) { return; } - for (size_t i = 0; i < sampleCount; ++i) { + for (i = 0; i < sampleCount; ++i) { *pOut++ = (float)(pIn[i] / 2147483648.0); } } void drwav_f64_to_f32(float* pOut, const double* pIn, size_t sampleCount) { + size_t i; + if (pOut == NULL || pIn == NULL) { return; } - for (size_t i = 0; i < sampleCount; ++i) { + for (i = 0; i < sampleCount; ++i) { *pOut++ = (float)pIn[i]; } } void drwav_alaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) { + size_t i; + if (pOut == NULL || pIn == NULL) { return; } - for (size_t i = 0; i < sampleCount; ++i) { + for (i = 0; i < sampleCount; ++i) { *pOut++ = drwav__alaw_to_s16(pIn[i]) / 32768.0f; } } void drwav_mulaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) { + size_t i; + if (pOut == NULL || pIn == NULL) { return; } - for (size_t i = 0; i < sampleCount; ++i) { + for (i = 0; i < sampleCount; ++i) { *pOut++ = drwav__mulaw_to_s16(pIn[i]) / 32768.0f; } } -static void drwav__pcm_to_s32(drwav_int32* pOut, const unsigned char* pIn, size_t totalSampleCount, unsigned short bytesPerSample) +static void drwav__pcm_to_s32(drwav_int32* pOut, const unsigned char* pIn, size_t totalSampleCount, unsigned int bytesPerSample) { - // Special case for 8-bit sample data because it's treated as unsigned. + unsigned int i; + + /* Special case for 8-bit sample data because it's treated as unsigned. */ if (bytesPerSample == 1) { drwav_u8_to_s32(pOut, pIn, totalSampleCount); return; } - // Slightly more optimal implementation for common formats. + /* Slightly more optimal implementation for common formats. */ if (bytesPerSample == 2) { drwav_s16_to_s32(pOut, (const drwav_int16*)pIn, totalSampleCount); return; @@ -3046,22 +3675,22 @@ static void drwav__pcm_to_s32(drwav_int32* pOut, const unsigned char* pIn, size_ return; } if (bytesPerSample == 4) { - for (unsigned int i = 0; i < totalSampleCount; ++i) { - *pOut++ = ((drwav_int32*)pIn)[i]; + for (i = 0; i < totalSampleCount; ++i) { + *pOut++ = ((const drwav_int32*)pIn)[i]; } return; } - // Anything more than 64 bits per sample is not supported. + /* Anything more than 64 bits per sample is not supported. */ if (bytesPerSample > 8) { drwav_zero_memory(pOut, totalSampleCount * sizeof(*pOut)); return; } - // Generic, slow converter. - for (unsigned int i = 0; i < totalSampleCount; ++i) { + /* Generic, slow converter. */ + for (i = 0; i < totalSampleCount; ++i) { drwav_uint64 sample = 0; unsigned int shift = (8 - bytesPerSample) * 8; @@ -3076,16 +3705,16 @@ static void drwav__pcm_to_s32(drwav_int32* pOut, const unsigned char* pIn, size_ } } -static void drwav__ieee_to_s32(drwav_int32* pOut, const unsigned char* pIn, size_t totalSampleCount, unsigned short bytesPerSample) +static void drwav__ieee_to_s32(drwav_int32* pOut, const unsigned char* pIn, size_t totalSampleCount, unsigned int bytesPerSample) { if (bytesPerSample == 4) { - drwav_f32_to_s32(pOut, (float*)pIn, totalSampleCount); + drwav_f32_to_s32(pOut, (const float*)pIn, totalSampleCount); return; } else if (bytesPerSample == 8) { - drwav_f64_to_s32(pOut, (double*)pIn, totalSampleCount); + drwav_f64_to_s32(pOut, (const double*)pIn, totalSampleCount); return; } else { - // Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. + /* Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. */ drwav_zero_memory(pOut, totalSampleCount * sizeof(*pOut)); return; } @@ -3094,24 +3723,29 @@ static void drwav__ieee_to_s32(drwav_int32* pOut, const unsigned char* pIn, size drwav_uint64 drwav_read_s32__pcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut) { - // Fast path. - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bytesPerSample == 4) { + drwav_uint64 totalSamplesRead; + unsigned char sampleData[4096]; + drwav_uint32 bytesPerSample; + + /* Fast path. */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 32) { return drwav_read(pWav, samplesToRead, pBufferOut); } - - if (pWav->bytesPerSample == 0) { + + bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { return 0; } - drwav_uint64 totalSamplesRead = 0; - unsigned char sampleData[4096]; + totalSamplesRead = 0; + while (samplesToRead > 0) { - drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); if (samplesRead == 0) { break; } - drwav__pcm_to_s32(pBufferOut, sampleData, (size_t)samplesRead, pWav->bytesPerSample); + drwav__pcm_to_s32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); pBufferOut += samplesRead; samplesToRead -= samplesRead; @@ -3123,8 +3757,10 @@ drwav_uint64 drwav_read_s32__pcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_ drwav_uint64 drwav_read_s32__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, 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 - // want to duplicate that code. + /* + 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 + want to duplicate that code. + */ drwav_uint64 totalSamplesRead = 0; drwav_int16 samples16[2048]; while (samplesToRead > 0) { @@ -3133,7 +3769,7 @@ drwav_uint64 drwav_read_s32__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, dr break; } - drwav_s16_to_s32(pBufferOut, samples16, (size_t)samplesRead); // <-- Safe cast because we're clamping to 2048. + drwav_s16_to_s32(pBufferOut, samples16, (size_t)samplesRead); /* <-- Safe cast because we're clamping to 2048. */ pBufferOut += samplesRead; samplesToRead -= samplesRead; @@ -3145,8 +3781,10 @@ drwav_uint64 drwav_read_s32__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, dr drwav_uint64 drwav_read_s32__ima(drwav* pWav, drwav_uint64 samplesToRead, 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 - // want to duplicate that code. + /* + 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 + want to duplicate that code. + */ drwav_uint64 totalSamplesRead = 0; drwav_int16 samples16[2048]; while (samplesToRead > 0) { @@ -3155,7 +3793,7 @@ drwav_uint64 drwav_read_s32__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_ break; } - drwav_s16_to_s32(pBufferOut, samples16, (size_t)samplesRead); // <-- Safe cast because we're clamping to 2048. + drwav_s16_to_s32(pBufferOut, samples16, (size_t)samplesRead); /* <-- Safe cast because we're clamping to 2048. */ pBufferOut += samplesRead; samplesToRead -= samplesRead; @@ -3167,19 +3805,23 @@ drwav_uint64 drwav_read_s32__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_ drwav_uint64 drwav_read_s32__ieee(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut) { - if (pWav->bytesPerSample == 0) { + drwav_uint64 totalSamplesRead; + unsigned char sampleData[4096]; + + drwav_uint32 bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { return 0; } - drwav_uint64 totalSamplesRead = 0; - unsigned char sampleData[4096]; + totalSamplesRead = 0; + while (samplesToRead > 0) { - drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); if (samplesRead == 0) { break; } - drwav__ieee_to_s32(pBufferOut, sampleData, (size_t)samplesRead, pWav->bytesPerSample); + drwav__ieee_to_s32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); pBufferOut += samplesRead; samplesToRead -= samplesRead; @@ -3191,14 +3833,18 @@ drwav_uint64 drwav_read_s32__ieee(drwav* pWav, drwav_uint64 samplesToRead, drwav drwav_uint64 drwav_read_s32__alaw(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut) { - if (pWav->bytesPerSample == 0) { + drwav_uint64 totalSamplesRead; + unsigned char sampleData[4096]; + + drwav_uint32 bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { return 0; } - drwav_uint64 totalSamplesRead = 0; - unsigned char sampleData[4096]; + totalSamplesRead = 0; + while (samplesToRead > 0) { - drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); if (samplesRead == 0) { break; } @@ -3215,14 +3861,18 @@ drwav_uint64 drwav_read_s32__alaw(drwav* pWav, drwav_uint64 samplesToRead, drwav drwav_uint64 drwav_read_s32__mulaw(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut) { - if (pWav->bytesPerSample == 0) { + drwav_uint64 totalSamplesRead; + unsigned char sampleData[4096]; + + drwav_uint32 bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { return 0; } - drwav_uint64 totalSamplesRead = 0; - unsigned char sampleData[4096]; + totalSamplesRead = 0; + while (samplesToRead > 0) { - drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); if (samplesRead == 0) { break; } @@ -3243,9 +3893,9 @@ drwav_uint64 drwav_read_s32(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32 return 0; } - // Don't try to read more samples than can potentially fit in the output buffer. - if (samplesToRead * sizeof(drwav_int32) > SIZE_MAX) { - samplesToRead = SIZE_MAX / sizeof(drwav_int32); + /* Don't try to read more samples than can potentially fit in the output buffer. */ + if (samplesToRead * sizeof(drwav_int32) > DRWAV_SIZE_MAX) { + samplesToRead = DRWAV_SIZE_MAX / sizeof(drwav_int32); } @@ -3276,35 +3926,46 @@ drwav_uint64 drwav_read_s32(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32 return 0; } +drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + return drwav_read_s32(pWav, framesToRead * pWav->channels, pBufferOut) / pWav->channels; +} + void drwav_u8_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) { + size_t i; + if (pOut == NULL || pIn == NULL) { return; } - for (size_t i = 0; i < sampleCount; ++i) { + for (i = 0; i < sampleCount; ++i) { *pOut++ = ((int)pIn[i] - 128) << 24; } } void drwav_s16_to_s32(drwav_int32* pOut, const drwav_int16* pIn, size_t sampleCount) { + size_t i; + if (pOut == NULL || pIn == NULL) { return; } - for (size_t i = 0; i < sampleCount; ++i) { + for (i = 0; i < sampleCount; ++i) { *pOut++ = pIn[i] << 16; } } void drwav_s24_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) { + size_t i; + if (pOut == NULL || pIn == NULL) { return; } - for (size_t i = 0; i < sampleCount; ++i) { + for (i = 0; i < sampleCount; ++i) { unsigned int s0 = pIn[i*3 + 0]; unsigned int s1 = pIn[i*3 + 1]; unsigned int s2 = pIn[i*3 + 2]; @@ -3316,44 +3977,52 @@ void drwav_s24_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCo void drwav_f32_to_s32(drwav_int32* pOut, const float* pIn, size_t sampleCount) { + size_t i; + if (pOut == NULL || pIn == NULL) { return; } - for (size_t i = 0; i < sampleCount; ++i) { + for (i = 0; i < sampleCount; ++i) { *pOut++ = (drwav_int32)(2147483648.0 * pIn[i]); } } void drwav_f64_to_s32(drwav_int32* pOut, const double* pIn, size_t sampleCount) { + size_t i; + if (pOut == NULL || pIn == NULL) { return; } - for (size_t i = 0; i < sampleCount; ++i) { + for (i = 0; i < sampleCount; ++i) { *pOut++ = (drwav_int32)(2147483648.0 * pIn[i]); } } void drwav_alaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) { + size_t i; + if (pOut == NULL || pIn == NULL) { return; } - for (size_t i = 0; i < sampleCount; ++i) { + for (i = 0; i < sampleCount; ++i) { *pOut++ = ((drwav_int32)drwav__alaw_to_s16(pIn[i])) << 16; } } void drwav_mulaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) { + size_t i; + if (pOut == NULL || pIn == NULL) { return; } - for (size_t i= 0; i < sampleCount; ++i) { + for (i= 0; i < sampleCount; ++i) { *pOut++ = ((drwav_int32)drwav__mulaw_to_s16(pIn[i])) << 16; } } @@ -3362,105 +4031,145 @@ void drwav_mulaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sample drwav_int16* drwav__read_and_close_s16(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) { + drwav_uint64 sampleDataSize; + drwav_int16* pSampleData; + drwav_uint64 samplesRead; + drwav_assert(pWav != NULL); - drwav_uint64 sampleDataSize = pWav->totalSampleCount * sizeof(drwav_int16); - if (sampleDataSize > SIZE_MAX) { + sampleDataSize = pWav->totalSampleCount * sizeof(drwav_int16); + if (sampleDataSize > DRWAV_SIZE_MAX) { drwav_uninit(pWav); - return NULL; // File's too big. + return NULL; /* File's too big. */ } - drwav_int16* pSampleData = (drwav_int16*)DRWAV_MALLOC((size_t)sampleDataSize); // <-- Safe cast due to the check above. + pSampleData = (drwav_int16*)DRWAV_MALLOC((size_t)sampleDataSize); /* <-- Safe cast due to the check above. */ if (pSampleData == NULL) { drwav_uninit(pWav); - return NULL; // Failed to allocate memory. + return NULL; /* Failed to allocate memory. */ } - drwav_uint64 samplesRead = drwav_read_s16(pWav, (size_t)pWav->totalSampleCount, pSampleData); + samplesRead = drwav_read_s16(pWav, (size_t)pWav->totalSampleCount, pSampleData); if (samplesRead != pWav->totalSampleCount) { DRWAV_FREE(pSampleData); drwav_uninit(pWav); - return NULL; // There was an error reading the samples. + return NULL; /* There was an error reading the samples. */ } drwav_uninit(pWav); - if (sampleRate) *sampleRate = pWav->sampleRate; - if (channels) *channels = pWav->channels; - if (totalSampleCount) *totalSampleCount = pWav->totalSampleCount; + if (sampleRate) { + *sampleRate = pWav->sampleRate; + } + if (channels) { + *channels = pWav->channels; + } + if (totalSampleCount) { + *totalSampleCount = pWav->totalSampleCount; + } + return pSampleData; } float* drwav__read_and_close_f32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) { + drwav_uint64 sampleDataSize; + float* pSampleData; + drwav_uint64 samplesRead; + drwav_assert(pWav != NULL); - drwav_uint64 sampleDataSize = pWav->totalSampleCount * sizeof(float); - if (sampleDataSize > SIZE_MAX) { + sampleDataSize = pWav->totalSampleCount * sizeof(float); + if (sampleDataSize > DRWAV_SIZE_MAX) { drwav_uninit(pWav); - return NULL; // File's too big. + return NULL; /* File's too big. */ } - float* pSampleData = (float*)DRWAV_MALLOC((size_t)sampleDataSize); // <-- Safe cast due to the check above. + pSampleData = (float*)DRWAV_MALLOC((size_t)sampleDataSize); /* <-- Safe cast due to the check above. */ if (pSampleData == NULL) { drwav_uninit(pWav); - return NULL; // Failed to allocate memory. + return NULL; /* Failed to allocate memory. */ } - drwav_uint64 samplesRead = drwav_read_f32(pWav, (size_t)pWav->totalSampleCount, pSampleData); + samplesRead = drwav_read_f32(pWav, (size_t)pWav->totalSampleCount, pSampleData); if (samplesRead != pWav->totalSampleCount) { DRWAV_FREE(pSampleData); drwav_uninit(pWav); - return NULL; // There was an error reading the samples. + return NULL; /* There was an error reading the samples. */ } drwav_uninit(pWav); - if (sampleRate) *sampleRate = pWav->sampleRate; - if (channels) *channels = pWav->channels; - if (totalSampleCount) *totalSampleCount = pWav->totalSampleCount; + if (sampleRate) { + *sampleRate = pWav->sampleRate; + } + if (channels) { + *channels = pWav->channels; + } + if (totalSampleCount) { + *totalSampleCount = pWav->totalSampleCount; + } + return pSampleData; } drwav_int32* drwav__read_and_close_s32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) { + drwav_uint64 sampleDataSize; + drwav_int32* pSampleData; + drwav_uint64 samplesRead; + drwav_assert(pWav != NULL); - drwav_uint64 sampleDataSize = pWav->totalSampleCount * sizeof(drwav_int32); - if (sampleDataSize > SIZE_MAX) { + sampleDataSize = pWav->totalSampleCount * sizeof(drwav_int32); + if (sampleDataSize > DRWAV_SIZE_MAX) { drwav_uninit(pWav); - return NULL; // File's too big. + return NULL; /* File's too big. */ } - drwav_int32* pSampleData = (drwav_int32*)DRWAV_MALLOC((size_t)sampleDataSize); // <-- Safe cast due to the check above. + pSampleData = (drwav_int32*)DRWAV_MALLOC((size_t)sampleDataSize); /* <-- Safe cast due to the check above. */ if (pSampleData == NULL) { drwav_uninit(pWav); - return NULL; // Failed to allocate memory. + return NULL; /* Failed to allocate memory. */ } - drwav_uint64 samplesRead = drwav_read_s32(pWav, (size_t)pWav->totalSampleCount, pSampleData); + samplesRead = drwav_read_s32(pWav, (size_t)pWav->totalSampleCount, pSampleData); if (samplesRead != pWav->totalSampleCount) { DRWAV_FREE(pSampleData); drwav_uninit(pWav); - return NULL; // There was an error reading the samples. + return NULL; /* There was an error reading the samples. */ } drwav_uninit(pWav); - if (sampleRate) *sampleRate = pWav->sampleRate; - if (channels) *channels = pWav->channels; - if (totalSampleCount) *totalSampleCount = pWav->totalSampleCount; + if (sampleRate) { + *sampleRate = pWav->sampleRate; + } + if (channels) { + *channels = pWav->channels; + } + if (totalSampleCount) { + *totalSampleCount = pWav->totalSampleCount; + } + return pSampleData; } drwav_int16* drwav_open_and_read_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) { - if (sampleRate) *sampleRate = 0; - if (channels) *channels = 0; - if (totalSampleCount) *totalSampleCount = 0; - drwav wav; + + if (channels) { + *channels = 0; + } + if (sampleRate) { + *sampleRate = 0; + } + if (totalSampleCount) { + *totalSampleCount = 0; + } + if (!drwav_init(&wav, onRead, onSeek, pUserData)) { return NULL; } @@ -3468,13 +4177,55 @@ drwav_int16* drwav_open_and_read_s16(drwav_read_proc onRead, drwav_seek_proc onS return drwav__read_and_close_s16(&wav, channels, sampleRate, totalSampleCount); } +drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) +{ + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + drwav_int16* result; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + result = drwav_open_and_read_s16(onRead, onSeek, pUserData, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) { + *channelsOut = channels; + } + if (sampleRateOut) { + *sampleRateOut = sampleRate; + } + if (totalFrameCountOut) { + *totalFrameCountOut = totalSampleCount / channels; + } + + return result; +} + float* drwav_open_and_read_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) { - if (sampleRate) *sampleRate = 0; - if (channels) *channels = 0; - if (totalSampleCount) *totalSampleCount = 0; - drwav wav; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalSampleCount) { + *totalSampleCount = 0; + } + if (!drwav_init(&wav, onRead, onSeek, pUserData)) { return NULL; } @@ -3482,13 +4233,55 @@ float* drwav_open_and_read_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, v return drwav__read_and_close_f32(&wav, channels, sampleRate, totalSampleCount); } +float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) +{ + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + float* result; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + result = drwav_open_and_read_f32(onRead, onSeek, pUserData, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) { + *channelsOut = channels; + } + if (sampleRateOut) { + *sampleRateOut = sampleRate; + } + if (totalFrameCountOut) { + *totalFrameCountOut = totalSampleCount / channels; + } + + return result; +} + drwav_int32* drwav_open_and_read_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) { - if (sampleRate) *sampleRate = 0; - if (channels) *channels = 0; - if (totalSampleCount) *totalSampleCount = 0; - drwav wav; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalSampleCount) { + *totalSampleCount = 0; + } + if (!drwav_init(&wav, onRead, onSeek, pUserData)) { return NULL; } @@ -3496,14 +4289,56 @@ drwav_int32* drwav_open_and_read_s32(drwav_read_proc onRead, drwav_seek_proc onS return drwav__read_and_close_s32(&wav, channels, sampleRate, totalSampleCount); } -#ifndef DR_WAV_NO_STDIO -drwav_int16* drwav_open_and_read_file_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) { - if (sampleRate) *sampleRate = 0; - if (channels) *channels = 0; - if (totalSampleCount) *totalSampleCount = 0; + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + drwav_int32* result; + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + result = drwav_open_and_read_s32(onRead, onSeek, pUserData, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) { + *channelsOut = channels; + } + if (sampleRateOut) { + *sampleRateOut = sampleRate; + } + if (totalFrameCountOut) { + *totalFrameCountOut = totalSampleCount / channels; + } + + return result; +} + +#ifndef DR_WAV_NO_STDIO +drwav_int16* drwav_open_file_and_read_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ drwav wav; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalSampleCount) { + *totalSampleCount = 0; + } + if (!drwav_init_file(&wav, filename)) { return NULL; } @@ -3511,13 +4346,55 @@ drwav_int16* drwav_open_and_read_file_s16(const char* filename, unsigned int* ch return drwav__read_and_close_s16(&wav, channels, sampleRate, totalSampleCount); } -float* drwav_open_and_read_file_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +drwav_int16* drwav_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) { - if (sampleRate) *sampleRate = 0; - if (channels) *channels = 0; - if (totalSampleCount) *totalSampleCount = 0; + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + drwav_int16* result; + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + result = drwav_open_file_and_read_s16(filename, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) { + *channelsOut = channels; + } + if (sampleRateOut) { + *sampleRateOut = sampleRate; + } + if (totalFrameCountOut) { + *totalFrameCountOut = totalSampleCount / channels; + } + + return result; +} + +float* drwav_open_file_and_read_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ drwav wav; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalSampleCount) { + *totalSampleCount = 0; + } + if (!drwav_init_file(&wav, filename)) { return NULL; } @@ -3525,28 +4402,112 @@ float* drwav_open_and_read_file_f32(const char* filename, unsigned int* channels return drwav__read_and_close_f32(&wav, channels, sampleRate, totalSampleCount); } -drwav_int32* drwav_open_and_read_file_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +float* drwav_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) { - if (sampleRate) *sampleRate = 0; - if (channels) *channels = 0; - if (totalSampleCount) *totalSampleCount = 0; + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + float* result; + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + result = drwav_open_file_and_read_f32(filename, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) { + *channelsOut = channels; + } + if (sampleRateOut) { + *sampleRateOut = sampleRate; + } + if (totalFrameCountOut) { + *totalFrameCountOut = totalSampleCount / channels; + } + + return result; +} + +drwav_int32* drwav_open_file_and_read_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ drwav wav; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalSampleCount) { + *totalSampleCount = 0; + } + if (!drwav_init_file(&wav, filename)) { return NULL; } return drwav__read_and_close_s32(&wav, channels, sampleRate, totalSampleCount); } + +drwav_int32* drwav_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) +{ + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + drwav_int32* result; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + result = drwav_open_file_and_read_s32(filename, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) { + *channelsOut = channels; + } + if (sampleRateOut) { + *sampleRateOut = sampleRate; + } + if (totalFrameCountOut) { + *totalFrameCountOut = totalSampleCount / channels; + } + + return result; +} #endif -drwav_int16* drwav_open_and_read_memory_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +drwav_int16* drwav_open_memory_and_read_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) { - if (sampleRate) *sampleRate = 0; - if (channels) *channels = 0; - if (totalSampleCount) *totalSampleCount = 0; - drwav wav; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalSampleCount) { + *totalSampleCount = 0; + } + if (!drwav_init_memory(&wav, data, dataSize)) { return NULL; } @@ -3554,13 +4515,55 @@ drwav_int16* drwav_open_and_read_memory_s16(const void* data, size_t dataSize, u return drwav__read_and_close_s16(&wav, channels, sampleRate, totalSampleCount); } -float* drwav_open_and_read_memory_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +drwav_int16* drwav_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) { - if (sampleRate) *sampleRate = 0; - if (channels) *channels = 0; - if (totalSampleCount) *totalSampleCount = 0; + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + drwav_int16* result; + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + result = drwav_open_memory_and_read_s16(data, dataSize, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) { + *channelsOut = channels; + } + if (sampleRateOut) { + *sampleRateOut = sampleRate; + } + if (totalFrameCountOut) { + *totalFrameCountOut = totalSampleCount / channels; + } + + return result; +} + +float* drwav_open_memory_and_read_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ drwav wav; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalSampleCount) { + *totalSampleCount = 0; + } + if (!drwav_init_memory(&wav, data, dataSize)) { return NULL; } @@ -3568,20 +4571,97 @@ float* drwav_open_and_read_memory_f32(const void* data, size_t dataSize, unsigne return drwav__read_and_close_f32(&wav, channels, sampleRate, totalSampleCount); } -drwav_int32* drwav_open_and_read_memory_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +float* drwav_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) { - if (sampleRate) *sampleRate = 0; - if (channels) *channels = 0; - if (totalSampleCount) *totalSampleCount = 0; + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + float* result; + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + result = drwav_open_memory_and_read_f32(data, dataSize, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) { + *channelsOut = channels; + } + if (sampleRateOut) { + *sampleRateOut = sampleRate; + } + if (totalFrameCountOut) { + *totalFrameCountOut = totalSampleCount / channels; + } + + return result; +} + +drwav_int32* drwav_open_memory_and_read_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ drwav wav; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalSampleCount) { + *totalSampleCount = 0; + } + if (!drwav_init_memory(&wav, data, dataSize)) { return NULL; } return drwav__read_and_close_s32(&wav, channels, sampleRate, totalSampleCount); } -#endif //DR_WAV_NO_CONVERSION_API + +drwav_int32* drwav_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) +{ + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + drwav_int32* result; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + result = drwav_open_memory_and_read_s32(data, dataSize, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) { + *channelsOut = channels; + } + if (sampleRateOut) { + *sampleRateOut = sampleRate; + } + if (totalFrameCountOut) { + *totalFrameCountOut = totalSampleCount / channels; + } + + return result; +} +#endif /* DR_WAV_NO_CONVERSION_API */ void drwav_free(void* pDataReturnedByOpenAndRead) @@ -3589,136 +4669,206 @@ void drwav_free(void* pDataReturnedByOpenAndRead) DRWAV_FREE(pDataReturnedByOpenAndRead); } -#endif //DR_WAV_IMPLEMENTATION - - -// REVISION HISTORY -// -// v0.8.1 - 2018-06-29 -// - Add support for sequential writing APIs. -// - Disable seeking in write mode. -// - Fix bugs with Wave64. -// - Fix typos. -// -// v0.8 - 2018-04-27 -// - Bug fix. -// - Start using major.minor.revision versioning. -// -// v0.7f - 2018-02-05 -// - Restrict ADPCM formats to a maximum of 2 channels. -// -// v0.7e - 2018-02-02 -// - Fix a crash. -// -// v0.7d - 2018-02-01 -// - Fix a crash. -// -// v0.7c - 2018-02-01 -// - Set drwav.bytesPerSample to 0 for all compressed formats. -// - Fix a crash when reading 16-bit floating point WAV files. In this case dr_wav will output silence for -// all format conversion reading APIs (*_s16, *_s32, *_f32 APIs). -// - Fix some divide-by-zero errors. -// -// v0.7b - 2018-01-22 -// - Fix errors with seeking of compressed formats. -// - Fix compilation error when DR_WAV_NO_CONVERSION_API -// -// v0.7a - 2017-11-17 -// - Fix some GCC warnings. -// -// v0.7 - 2017-11-04 -// - Add writing APIs. -// -// v0.6 - 2017-08-16 -// - API CHANGE: Rename dr_* types to drwav_*. -// - Add support for custom implementations of malloc(), realloc(), etc. -// - Add support for Microsoft ADPCM. -// - Add support for IMA ADPCM (DVI, format code 0x11). -// - Optimizations to drwav_read_s16(). -// - Bug fixes. -// -// v0.5g - 2017-07-16 -// - Change underlying type for booleans to unsigned. -// -// v0.5f - 2017-04-04 -// - Fix a minor bug with drwav_open_and_read_s16() and family. -// -// v0.5e - 2016-12-29 -// - Added support for reading samples as signed 16-bit integers. Use the _s16() family of APIs for this. -// - Minor fixes to documentation. -// -// v0.5d - 2016-12-28 -// - Use drwav_int*/drwav_uint* sized types to improve compiler support. -// -// v0.5c - 2016-11-11 -// - Properly handle JUNK chunks that come before the FMT chunk. -// -// v0.5b - 2016-10-23 -// - A minor change to drwav_bool8 and drwav_bool32 types. -// -// v0.5a - 2016-10-11 -// - Fixed a bug with drwav_open_and_read() and family due to incorrect argument ordering. -// - Improve A-law and mu-law efficiency. -// -// v0.5 - 2016-09-29 -// - API CHANGE. Swap the order of "channels" and "sampleRate" parameters in drwav_open_and_read*(). Rationale for this is to -// keep it consistent with dr_audio and dr_flac. -// -// v0.4b - 2016-09-18 -// - Fixed a typo in documentation. -// -// v0.4a - 2016-09-18 -// - Fixed a typo. -// - Change date format to ISO 8601 (YYYY-MM-DD) -// -// v0.4 - 2016-07-13 -// - API CHANGE. Make onSeek consistent with dr_flac. -// - API CHANGE. Rename drwav_seek() to drwav_seek_to_sample() for clarity and consistency with dr_flac. -// - Added support for Sony Wave64. -// -// v0.3a - 2016-05-28 -// - API CHANGE. Return drwav_bool32 instead of int in onSeek callback. -// - Fixed a memory leak. -// -// v0.3 - 2016-05-22 -// - Lots of API changes for consistency. -// -// v0.2a - 2016-05-16 -// - Fixed Linux/GCC build. -// -// v0.2 - 2016-05-11 -// - Added support for reading data as signed 32-bit PCM for consistency with dr_flac. -// -// v0.1a - 2016-05-07 -// - Fixed a bug in drwav_open_file() where the file handle would not be closed if the loader failed to initialize. -// -// v0.1 - 2016-05-04 -// - Initial versioned release. +#endif /* DR_WAV_IMPLEMENTATION */ /* +REVISION HISTORY +================ +v0.9.2 - 2019-05-21 + - Fix warnings. + +v0.9.1 - 2019-05-05 + - Add support for C89. + - Change license to choice of public domain or MIT-0. + +v0.9.0 - 2018-12-16 + - API CHANGE: Add new reading APIs for reading by PCM frames instead of samples. Old APIs have been deprecated and + will be removed in v0.10.0. Deprecated APIs and their replacements: + drwav_read() -> drwav_read_pcm_frames() + drwav_read_s16() -> drwav_read_pcm_frames_s16() + drwav_read_f32() -> drwav_read_pcm_frames_f32() + drwav_read_s32() -> drwav_read_pcm_frames_s32() + drwav_seek_to_sample() -> drwav_seek_to_pcm_frame() + drwav_write() -> drwav_write_pcm_frames() + drwav_open_and_read_s16() -> drwav_open_and_read_pcm_frames_s16() + drwav_open_and_read_f32() -> drwav_open_and_read_pcm_frames_f32() + drwav_open_and_read_s32() -> drwav_open_and_read_pcm_frames_s32() + drwav_open_file_and_read_s16() -> drwav_open_file_and_read_pcm_frames_s16() + drwav_open_file_and_read_f32() -> drwav_open_file_and_read_pcm_frames_f32() + drwav_open_file_and_read_s32() -> drwav_open_file_and_read_pcm_frames_s32() + drwav_open_memory_and_read_s16() -> drwav_open_memory_and_read_pcm_frames_s16() + drwav_open_memory_and_read_f32() -> drwav_open_memory_and_read_pcm_frames_f32() + drwav_open_memory_and_read_s32() -> drwav_open_memory_and_read_pcm_frames_s32() + drwav::totalSampleCount -> drwav::totalPCMFrameCount + - API CHANGE: Rename drwav_open_and_read_file_*() to drwav_open_file_and_read_*(). + - API CHANGE: Rename drwav_open_and_read_memory_*() to drwav_open_memory_and_read_*(). + - Add built-in support for smpl chunks. + - Add support for firing a callback for each chunk in the file at initialization time. + - This is enabled through the drwav_init_ex(), etc. family of APIs. + - Handle invalid FMT chunks more robustly. + +v0.8.5 - 2018-09-11 + - Const correctness. + - Fix a potential stack overflow. + +v0.8.4 - 2018-08-07 + - Improve 64-bit detection. + +v0.8.3 - 2018-08-05 + - Fix C++ build on older versions of GCC. + +v0.8.2 - 2018-08-02 + - Fix some big-endian bugs. + +v0.8.1 - 2018-06-29 + - Add support for sequential writing APIs. + - Disable seeking in write mode. + - Fix bugs with Wave64. + - Fix typos. + +v0.8 - 2018-04-27 + - Bug fix. + - Start using major.minor.revision versioning. + +v0.7f - 2018-02-05 + - Restrict ADPCM formats to a maximum of 2 channels. + +v0.7e - 2018-02-02 + - Fix a crash. + +v0.7d - 2018-02-01 + - Fix a crash. + +v0.7c - 2018-02-01 + - Set drwav.bytesPerSample to 0 for all compressed formats. + - Fix a crash when reading 16-bit floating point WAV files. In this case dr_wav will output silence for + all format conversion reading APIs (*_s16, *_s32, *_f32 APIs). + - Fix some divide-by-zero errors. + +v0.7b - 2018-01-22 + - Fix errors with seeking of compressed formats. + - Fix compilation error when DR_WAV_NO_CONVERSION_API + +v0.7a - 2017-11-17 + - Fix some GCC warnings. + +v0.7 - 2017-11-04 + - Add writing APIs. + +v0.6 - 2017-08-16 + - API CHANGE: Rename dr_* types to drwav_*. + - Add support for custom implementations of malloc(), realloc(), etc. + - Add support for Microsoft ADPCM. + - Add support for IMA ADPCM (DVI, format code 0x11). + - Optimizations to drwav_read_s16(). + - Bug fixes. + +v0.5g - 2017-07-16 + - Change underlying type for booleans to unsigned. + +v0.5f - 2017-04-04 + - Fix a minor bug with drwav_open_and_read_s16() and family. + +v0.5e - 2016-12-29 + - Added support for reading samples as signed 16-bit integers. Use the _s16() family of APIs for this. + - Minor fixes to documentation. + +v0.5d - 2016-12-28 + - Use drwav_int* and drwav_uint* sized types to improve compiler support. + +v0.5c - 2016-11-11 + - Properly handle JUNK chunks that come before the FMT chunk. + +v0.5b - 2016-10-23 + - A minor change to drwav_bool8 and drwav_bool32 types. + +v0.5a - 2016-10-11 + - Fixed a bug with drwav_open_and_read() and family due to incorrect argument ordering. + - Improve A-law and mu-law efficiency. + +v0.5 - 2016-09-29 + - API CHANGE. Swap the order of "channels" and "sampleRate" parameters in drwav_open_and_read*(). Rationale for this is to + keep it consistent with dr_audio and dr_flac. + +v0.4b - 2016-09-18 + - Fixed a typo in documentation. + +v0.4a - 2016-09-18 + - Fixed a typo. + - Change date format to ISO 8601 (YYYY-MM-DD) + +v0.4 - 2016-07-13 + - API CHANGE. Make onSeek consistent with dr_flac. + - API CHANGE. Rename drwav_seek() to drwav_seek_to_sample() for clarity and consistency with dr_flac. + - Added support for Sony Wave64. + +v0.3a - 2016-05-28 + - API CHANGE. Return drwav_bool32 instead of int in onSeek callback. + - Fixed a memory leak. + +v0.3 - 2016-05-22 + - Lots of API changes for consistency. + +v0.2a - 2016-05-16 + - Fixed Linux/GCC build. + +v0.2 - 2016-05-11 + - Added support for reading data as signed 32-bit PCM for consistency with dr_flac. + +v0.1a - 2016-05-07 + - Fixed a bug in drwav_open_file() where the file handle would not be closed if the loader failed to initialize. + +v0.1 - 2016-05-04 + - Initial versioned release. +*/ + +/* +This software is available as a choice of the following licenses. Choose +whichever you prefer. + +=============================================================================== +ALTERNATIVE 1 - 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. +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. +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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to + +=============================================================================== +ALTERNATIVE 2 - MIT No Attribution +=============================================================================== +Copyright 2018 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 +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. + +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. */ diff --git a/raylib/external/jar_xm.h b/raylib/external/jar_xm.h index 9d4f5b5..de6ad41 100644 --- a/raylib/external/jar_xm.h +++ b/raylib/external/jar_xm.h @@ -1925,7 +1925,6 @@ static void jar_xm_handle_note_and_instrument(jar_xm_context_t* ctx, jar_xm_chan 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; @@ -2365,7 +2364,7 @@ static void jar_xm_tick(jar_xm_context_t* ctx) { float panning, volume; panning = ch->panning + - (ch->panning_envelope_panning - .5f) * (.5f - fabsf(ch->panning - .5f)) * 2.0f; + (ch->panning_envelope_panning - .5f) * (.5f - fabs(ch->panning - .5f)) * 2.0f; if(ch->tremor_on) { volume = .0f; @@ -2660,6 +2659,22 @@ int jar_xm_create_context_from_file(jar_xm_context_t** ctx, uint32_t rate, const 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++) + { + jar_xm_cut_note(&ctx->channels[i]); + } + ctx->current_row = 0; + ctx->current_table_index = ctx->module.restart_position; + ctx->current_tick = 0; +} + + #endif//end of JAR_XM_IMPLEMENTATION //------------------------------------------------------------------------------- diff --git a/raylib/external/miniaudio.h b/raylib/external/miniaudio.h new file mode 100644 index 0000000..7eb4bee --- /dev/null +++ b/raylib/external/miniaudio.h @@ -0,0 +1,33744 @@ +/* +Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file. +miniaudio (formerly mini_al) - v0.9.6 - 2019-08-04 + +David Reid - davidreidsoftware@gmail.com + +https://github.com/dr-soft/miniaudio +*/ + +/* +MAJOR CHANGES IN VERSION 0.9 +============================ +Version 0.9 includes major API changes, centered mostly around full-duplex and the rebrand to "miniaudio". Before I go into +detail about the major changes I would like to apologize. I know it's annoying dealing with breaking API changes, but I think +it's best to get these changes out of the way now while the library is still relatively young and unknown. + +There's been a lot of refactoring with this release so there's a good chance a few bugs have been introduced. I apologize in +advance for this. You may want to hold off on upgrading for the short term if you're worried. If mini_al v0.8.14 works for +you, and you don't need full-duplex support, you can avoid upgrading (though you won't be getting future bug fixes). + + +Rebranding to "miniaudio" +------------------------- +The decision was made to rename mini_al to miniaudio. Don't worry, it's the same project. The reason for this is simple: + +1) Having the word "audio" in the title makes it immediately clear that the library is related to audio; and +2) I don't like the look of the underscore. + +This rebrand has necessitated a change in namespace from "mal" to "ma". I know this is annoying, and I apologize, but it's +better to get this out of the road now rather than later. Also, since there are necessary API changes for full-duplex support +I think it's better to just get the namespace change over and done with at the same time as the full-duplex changes. I'm hoping +this will be the last of the major API changes. Fingers crossed! + +The implementation define is now "#define MINIAUDIO_IMPLEMENTATION". You can also use "#define MA_IMPLEMENTATION" if that's +your preference. + + +Full-Duplex Support +------------------- +The major feature added to version 0.9 is full-duplex. This has necessitated a few API changes. + +1) The data callback has now changed. Previously there was one type of callback for playback and another for capture. I wanted + to avoid a third callback just for full-duplex so the decision was made to break this API and unify the callbacks. Now, + there is just one callback which is the same for all three modes (playback, capture, duplex). The new callback looks like + the following: + + void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); + + This callback allows you to move data straight out of the input buffer and into the output buffer in full-duplex mode. In + playback-only mode, pInput will be null. Likewise, pOutput will be null in capture-only mode. The sample count is no longer + returned from the callback since it's not necessary for miniaudio anymore. + +2) The device config needed to change in order to support full-duplex. Full-duplex requires the ability to allow the client + to choose a different PCM format for the playback and capture sides. The old ma_device_config object simply did not allow + this and needed to change. With these changes you now specify the device ID, format, channels, channel map and share mode + on a per-playback and per-capture basis (see example below). The sample rate must be the same for playback and capture. + + Since the device config API has changed I have also decided to take the opportunity to simplify device initialization. Now, + the device ID, device type and callback user data are set in the config. ma_device_init() is now simplified down to taking + just the context, device config and a pointer to the device object being initialized. The rationale for this change is that + it just makes more sense to me that these are set as part of the config like everything else. + + Example device initialization: + + ma_device_config config = ma_device_config_init(ma_device_type_duplex); // Or ma_device_type_playback or ma_device_type_capture. + config.playback.pDeviceID = &myPlaybackDeviceID; // Or NULL for the default playback device. + config.playback.format = ma_format_f32; + config.playback.channels = 2; + config.capture.pDeviceID = &myCaptureDeviceID; // Or NULL for the default capture device. + config.capture.format = ma_format_s16; + config.capture.channels = 1; + config.sampleRate = 44100; + config.dataCallback = data_callback; + config.pUserData = &myUserData; + + result = ma_device_init(&myContext, &config, &device); + if (result != MA_SUCCESS) { + ... handle error ... + } + + Note that the "onDataCallback" member of ma_device_config has been renamed to "dataCallback". Also, "onStopCallback" has + been renamed to "stopCallback". + +This is the first pass for full-duplex and there is a known bug. You will hear crackling on the following backends when sample +rate conversion is required for the playback device: + - Core Audio + - JACK + - AAudio + - OpenSL + - WebAudio + +In addition to the above, not all platforms have been absolutely thoroughly tested simply because I lack the hardware for such +thorough testing. If you experience a bug, an issue report on GitHub or an email would be greatly appreciated (and a sample +program that reproduces the issue if possible). + + +Other API Changes +----------------- +In addition to the above, the following API changes have been made: + +- The log callback is no longer passed to ma_context_config_init(). Instead you need to set it manually after initialization. +- The onLogCallback member of ma_context_config has been renamed to "logCallback". +- The log callback now takes a logLevel parameter. The new callback looks like: void log_callback(ma_context* pContext, ma_device* pDevice, ma_uint32 logLevel, const char* message) + - You can use ma_log_level_to_string() to convert the logLevel to human readable text if you want to log it. +- Some APIs have been renamed: + - mal_decoder_read() -> ma_decoder_read_pcm_frames() + - mal_decoder_seek_to_frame() -> ma_decoder_seek_to_pcm_frame() + - mal_sine_wave_read() -> ma_sine_wave_read_f32() + - mal_sine_wave_read_ex() -> ma_sine_wave_read_f32_ex() +- Some APIs have been removed: + - mal_device_get_buffer_size_in_bytes() + - mal_device_set_recv_callback() + - mal_device_set_send_callback() + - mal_src_set_input_sample_rate() + - mal_src_set_output_sample_rate() +- Error codes have been rearranged. If you're a binding maintainer you will need to update. +- The ma_backend enums have been rearranged to priority order. The rationale for this is to simplify automatic backend selection + and to make it easier to see the priority. If you're a binding maintainer you will need to update. +- ma_dsp has been renamed to ma_pcm_converter. The rationale for this change is that I'm expecting "ma_dsp" to conflict with + some future planned high-level APIs. +- For functions that take a pointer/count combo, such as ma_decoder_read_pcm_frames(), the parameter order has changed so that + the pointer comes before the count. The rationale for this is to keep it consistent with things like memcpy(). + + +Miscellaneous Changes +--------------------- +The following miscellaneous changes have also been made. + +- The AAudio backend has been added for Android 8 and above. This is Android's new "High-Performance Audio" API. (For the + record, this is one of the nicest audio APIs out there, just behind the BSD audio APIs). +- The WebAudio backend has been added. This is based on ScriptProcessorNode. This removes the need for SDL. +- The SDL and OpenAL backends have been removed. These were originally implemented to add support for platforms for which miniaudio + was not explicitly supported. These are no longer needed and have therefore been removed. +- Device initialization now fails if the requested share mode is not supported. If you ask for exclusive mode, you either get an + exclusive mode device, or an error. The rationale for this change is to give the client more control over how to handle cases + when the desired shared mode is unavailable. +- A lock-free ring buffer API has been added. There are two varients of this. "ma_rb" operates on bytes, whereas "ma_pcm_rb" + operates on PCM frames. +- The library is now licensed as a choice of Public Domain (Unlicense) _or_ MIT-0 (No Attribution) which is the same as MIT, but + removes the attribution requirement. The rationale for this is to support countries that don't recognize public domain. +*/ + +/* +ABOUT +===== +miniaudio is a single file library for audio playback and capture. It's written in C (compilable as +C++) and released into the public domain. + +Supported Backends: + - WASAPI + - DirectSound + - WinMM + - Core Audio (Apple) + - ALSA + - PulseAudio + - JACK + - sndio (OpenBSD) + - audio(4) (NetBSD and OpenBSD) + - OSS (FreeBSD) + - AAudio (Android 8.0+) + - OpenSL|ES (Android only) + - Web Audio (Emscripten) + - Null (Silence) + +Supported Formats: + - Unsigned 8-bit PCM + - Signed 16-bit PCM + - Signed 24-bit PCM (tightly packed) + - Signed 32-bit PCM + - IEEE 32-bit floating point PCM + + +USAGE +===== +miniaudio is a single-file library. To use it, do something like the following in one .c file. + #define MINIAUDIO_IMPLEMENTATION + #include "miniaudio.h" + +You can then #include this file in other parts of the program as you would with any other header file. + +miniaudio uses an asynchronous, callback based API. You initialize a device with a configuration (sample rate, +channel count, etc.) which includes the callback you want to use to handle data transmission to/from the +device. In the callback you either read from a data pointer in the case of playback or write to it in the case +of capture. + +Playback Example +---------------- + void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) + { + ma_decoder* pDecoder = (ma_decoder*)pDevice->pUserData; + if (pDecoder == NULL) { + return; + } + + ma_decoder_read_pcm_frames(pDecoder, frameCount, pOutput); + } + + ... + + ma_device_config config = ma_device_config_init(ma_device_type_playback); + config.playback.format = decoder.outputFormat; + config.playback.channels = decoder.outputChannels; + config.sampleRate = decoder.outputSampleRate; + config.dataCallback = data_callback; + config.pUserData = &decoder; + + ma_device device; + if (ma_device_init(NULL, &config, &device) != MA_SUCCESS) { + ... An error occurred ... + } + + ma_device_start(&device); // The device is sleeping by default so you'll need to start it manually. + + ... + + ma_device_uninit(&device); // This will stop the device so no need to do that manually. + + +BUILDING +======== +miniaudio should Just Work by adding it to your project's source tree. You do not need to download or install +any dependencies. See below for platform-specific details. + +If you want to disable a specific backend, #define the appropriate MA_NO_* option before the implementation. + +Note that GCC and Clang requires "-msse2", "-mavx2", etc. for SIMD optimizations. + + +Building for Windows +-------------------- +The Windows build should compile clean on all popular compilers without the need to configure any include paths +nor link to any libraries. + +Building for macOS and iOS +-------------------------- +The macOS build should compile clean without the need to download any dependencies or 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. + +Building for Linux +------------------ +The Linux build only requires linking to -ldl, -lpthread and -lm. You do not need any development packages. + +Building for BSD +---------------- +The BSD build only requires linking to -ldl, -lpthread and -lm. NetBSD uses audio(4), OpenBSD uses sndio and +FreeBSD uses OSS. + +Building for Android +-------------------- +AAudio is the highest priority backend on Android. This should work out 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+. + +Building for Emscripten +----------------------- +The Emscripten build emits Web Audio JavaScript directly and should Just Work without any configuration. + + +NOTES +===== +- This library uses an asynchronous API for delivering and requesting audio data. Each device will have + it's own worker thread which is managed by the library. +- If ma_device_init() is called with a device that's not aligned to the platform's natural alignment + boundary (4 bytes on 32-bit, 8 bytes on 64-bit), it will _not_ be thread-safe. The reason for this + is that it depends on members of ma_device being correctly aligned for atomic assignments. +- Sample data is always native-endian and interleaved. For example, ma_format_s16 means signed 16-bit + integer samples, interleaved. Let me know if you need non-interleaved and I'll look into it. +- The sndio backend is currently only enabled on OpenBSD builds. +- The audio(4) backend is supported on OpenBSD, but you may need to disable sndiod before you can use it. +- 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. + + +BACKEND NUANCES +=============== + +PulseAudio +---------- +- If you experience bad glitching/noise on Arch Linux, consider this fix from the Arch wiki: + https://wiki.archlinux.org/index.php/PulseAudio/Troubleshooting#Glitches,_skips_or_crackling + Alternatively, consider using a different backend such as ALSA. + +Android +------- +- To capture audio on Android, remember to add the RECORD_AUDIO permission to your manifest: + +- With OpenSL|ES, only a single ma_context can be active at any given time. This is due to a limitation with OpenSL|ES. +- With AAudio, only default devices are enumerated. This is due to AAudio not having an enumeration API (devices are + enumerated through Java). You can however perform your own device enumeration through Java and then set the ID in the + ma_device_id structure (ma_device_id.aaudio) and pass it to ma_device_init(). +- The backend API will perform resampling where possible. The reason for this as opposed to using miniaudio's built-in + resampler is to take advantage of any potential device-specific optimizations the driver may implement. + +UWP +--- +- UWP only supports default playback and capture devices. +- UWP requires the Microphone capability to be enabled in the application's manifest (Package.appxmanifest): + + ... + + + + + +Web Audio / Emscripten +---------------------- +- The first time a context is initialized it will create a global object called "miniaudio" whose primary purpose is to act + as a factory for device objects. +- Currently the Web Audio backend uses ScriptProcessorNode's, but this may need to change later as they've been deprecated. +- Google is implementing a policy in their browsers that prevent automatic media output without first receiving some kind + of user input. See here for details: https://developers.google.com/web/updates/2017/09/autoplay-policy-changes. Starting + the device may fail if you try to start playback without first handling some kind of user input. + + +OPTIONS +======= +#define these options before including this file. + +#define MA_NO_WASAPI + Disables the WASAPI backend. + +#define MA_NO_DSOUND + Disables the DirectSound backend. + +#define MA_NO_WINMM + Disables the WinMM backend. + +#define MA_NO_ALSA + Disables the ALSA backend. + +#define MA_NO_PULSEAUDIO + Disables the PulseAudio backend. + +#define MA_NO_JACK + Disables the JACK backend. + +#define MA_NO_COREAUDIO + Disables the Core Audio backend. + +#define MA_NO_SNDIO + Disables the sndio backend. + +#define MA_NO_AUDIO4 + Disables the audio(4) backend. + +#define MA_NO_OSS + Disables the OSS backend. + +#define MA_NO_AAUDIO + Disables the AAudio backend. + +#define MA_NO_OPENSL + Disables the OpenSL|ES backend. + +#define MA_NO_WEBAUDIO + Disables the Web Audio backend. + +#define MA_NO_NULL + Disables the null backend. + +#define MA_DEFAULT_PERIODS + When a period count of 0 is specified when a device is initialized, it will default to this. + +#define MA_BASE_BUFFER_SIZE_IN_MILLISECONDS_LOW_LATENCY +#define MA_BASE_BUFFER_SIZE_IN_MILLISECONDS_CONSERVATIVE + When a buffer size of 0 is specified when a device is initialized it will default to a buffer of this size, depending + on the chosen performance profile. These can be increased or decreased depending on your specific requirements. + +#define MA_NO_DECODING + Disables the decoding APIs. + +#define 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. + +#define MA_NO_STDIO + Disables file IO APIs. + +#define MA_NO_SSE2 + Disables SSE2 optimizations. + +#define MA_NO_AVX2 + Disables AVX2 optimizations. + +#define MA_NO_AVX512 + Disables AVX-512 optimizations. + +#define MA_NO_NEON + Disables NEON optimizations. + +#define MA_LOG_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 + +#define MA_DEBUG_OUTPUT + Enable printf() debug output. + +#define MA_COINIT_VALUE + Windows only. The value to pass to internal calls to CoInitializeEx(). Defaults to COINIT_MULTITHREADED. + + +DEFINITIONS +=========== +This section defines common terms used throughout miniaudio. Unfortunately there is often ambiguity in the use of terms +throughout the audio space, so this section is intended to clarify how miniaudio uses each term. + +Sample +------ +A sample is a single unit of audio data. If the sample format is f32, then one sample is one 32-bit floating point number. + +Frame / PCM Frame +----------------- +A frame is a groups of samples equal to the number of channels. For a stereo stream a frame is 2 samples, a mono frame +is 1 sample, a 5.1 surround sound frame is 6 samples, etc. The terms "frame" and "PCM frame" are the same thing in +miniaudio. Note that this is different to a compressed frame. If ever miniaudio needs to refer to a compressed frame, such +as a FLAC frame, it will always clarify what it's referring to with something like "FLAC frame" or whatnot. + +Channel +------- +A stream of monaural audio that is emitted from an individual speaker in a speaker system, or received from an individual +microphone in a microphone system. A stereo stream has two channels (a left channel, and a right channel), a 5.1 surround +sound system has 6 channels, etc. Some audio systems refer to a channel as a complex audio stream that's mixed with other +channels to produce the final mix - this is completely different to miniaudio's use of the term "channel" and should not be +confused. + +Sample Rate +----------- +The sample rate in miniaudio is always expressed in Hz, such as 44100, 48000, etc. It's the number of PCM frames that are +processed per second. + +Formats +------- +Throughout miniaudio you will see references to different sample formats: + u8 - Unsigned 8-bit integer + s16 - Signed 16-bit integer + s24 - Signed 24-bit integer (tightly packed). + s32 - Signed 32-bit integer + f32 - 32-bit floating point +*/ + +#ifndef miniaudio_h +#define miniaudio_h + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable:4201) /* nonstandard extension used: nameless struct/union */ + #pragma warning(disable:4324) /* structure was padded due to alignment specifier */ +#else + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wpedantic" /* For ISO C99 doesn't support unnamed structs/unions [-Wpedantic] */ +#endif + +/* Platform/backend detection. */ +#ifdef _WIN32 + #define MA_WIN32 + #if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PC_APP || WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) + #define MA_WIN32_UWP + #else + #define MA_WIN32_DESKTOP + #endif +#else + #define MA_POSIX + #include /* Unfortunate #include, but needed for pthread_t, pthread_mutex_t and pthread_cond_t types. */ + + #ifdef __unix__ + #define MA_UNIX + #if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) + #define MA_BSD + #endif + #endif + #ifdef __linux__ + #define MA_LINUX + #endif + #ifdef __APPLE__ + #define MA_APPLE + #endif + #ifdef __ANDROID__ + #define MA_ANDROID + #endif + #ifdef __EMSCRIPTEN__ + #define MA_EMSCRIPTEN + #endif +#endif + +#include /* For size_t. */ + +/* Sized types. Prefer built-in types. Fall back to stdint. */ +#ifdef _MSC_VER + #if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wlanguage-extension-token" + #pragma GCC diagnostic ignored "-Wc++11-long-long" + #endif + typedef signed __int8 ma_int8; + typedef unsigned __int8 ma_uint8; + typedef signed __int16 ma_int16; + typedef unsigned __int16 ma_uint16; + typedef signed __int32 ma_int32; + typedef unsigned __int32 ma_uint32; + typedef signed __int64 ma_int64; + typedef unsigned __int64 ma_uint64; + #if defined(__clang__) + #pragma GCC diagnostic pop + #endif +#else + #define MA_HAS_STDINT + #include + typedef int8_t ma_int8; + typedef uint8_t ma_uint8; + typedef int16_t ma_int16; + typedef uint16_t ma_uint16; + typedef int32_t ma_int32; + typedef uint32_t ma_uint32; + typedef int64_t ma_int64; + typedef uint64_t ma_uint64; +#endif + +#ifdef MA_HAS_STDINT + typedef uintptr_t ma_uintptr; +#else + #if defined(_WIN32) + #if defined(_WIN64) + typedef ma_uint64 ma_uintptr; + #else + typedef ma_uint32 ma_uintptr; + #endif + #elif defined(__GNUC__) + #if defined(__LP64__) + typedef ma_uint64 ma_uintptr; + #else + typedef ma_uint32 ma_uintptr; + #endif + #else + typedef ma_uint64 ma_uintptr; /* Fallback. */ + #endif +#endif + +typedef ma_uint8 ma_bool8; +typedef ma_uint32 ma_bool32; +#define MA_TRUE 1 +#define MA_FALSE 0 + +typedef void* ma_handle; +typedef void* ma_ptr; +typedef void (* ma_proc)(void); + +#if defined(_MSC_VER) && !defined(_WCHAR_T_DEFINED) +typedef ma_uint16 wchar_t; +#endif + +/* Define NULL for some compilers. */ +#ifndef NULL +#define NULL 0 +#endif + +#if defined(SIZE_MAX) + #define MA_SIZE_MAX SIZE_MAX +#else + #define MA_SIZE_MAX 0xFFFFFFFF /* When SIZE_MAX is not defined by the standard library just default to the maximum 32-bit unsigned integer. */ +#endif + + +#ifdef _MSC_VER +#define MA_INLINE __forceinline +#else +#ifdef __GNUC__ +#define MA_INLINE __inline__ __attribute__((always_inline)) +#else +#define MA_INLINE +#endif +#endif + +#if defined(_MSC_VER) + #if _MSC_VER >= 1400 + #define MA_ALIGN(alignment) __declspec(align(alignment)) + #endif +#elif !defined(__DMC__) + #define MA_ALIGN(alignment) __attribute__((aligned(alignment))) +#endif +#ifndef MA_ALIGN + #define MA_ALIGN(alignment) +#endif + +#ifdef _MSC_VER +#define MA_ALIGNED_STRUCT(alignment) MA_ALIGN(alignment) struct +#else +#define MA_ALIGNED_STRUCT(alignment) struct MA_ALIGN(alignment) +#endif + +/* SIMD alignment in bytes. Currently set to 64 bytes in preparation for future AVX-512 optimizations. */ +#define MA_SIMD_ALIGNMENT 64 + + +/* Logging levels */ +#define MA_LOG_LEVEL_VERBOSE 4 +#define MA_LOG_LEVEL_INFO 3 +#define MA_LOG_LEVEL_WARNING 2 +#define MA_LOG_LEVEL_ERROR 1 + +#ifndef MA_LOG_LEVEL +#define MA_LOG_LEVEL MA_LOG_LEVEL_ERROR +#endif + +typedef struct ma_context ma_context; +typedef struct ma_device ma_device; + +typedef ma_uint8 ma_channel; +#define MA_CHANNEL_NONE 0 +#define MA_CHANNEL_MONO 1 +#define MA_CHANNEL_FRONT_LEFT 2 +#define MA_CHANNEL_FRONT_RIGHT 3 +#define MA_CHANNEL_FRONT_CENTER 4 +#define MA_CHANNEL_LFE 5 +#define MA_CHANNEL_BACK_LEFT 6 +#define MA_CHANNEL_BACK_RIGHT 7 +#define MA_CHANNEL_FRONT_LEFT_CENTER 8 +#define MA_CHANNEL_FRONT_RIGHT_CENTER 9 +#define MA_CHANNEL_BACK_CENTER 10 +#define MA_CHANNEL_SIDE_LEFT 11 +#define MA_CHANNEL_SIDE_RIGHT 12 +#define MA_CHANNEL_TOP_CENTER 13 +#define MA_CHANNEL_TOP_FRONT_LEFT 14 +#define MA_CHANNEL_TOP_FRONT_CENTER 15 +#define MA_CHANNEL_TOP_FRONT_RIGHT 16 +#define MA_CHANNEL_TOP_BACK_LEFT 17 +#define MA_CHANNEL_TOP_BACK_CENTER 18 +#define MA_CHANNEL_TOP_BACK_RIGHT 19 +#define MA_CHANNEL_AUX_0 20 +#define MA_CHANNEL_AUX_1 21 +#define MA_CHANNEL_AUX_2 22 +#define MA_CHANNEL_AUX_3 23 +#define MA_CHANNEL_AUX_4 24 +#define MA_CHANNEL_AUX_5 25 +#define MA_CHANNEL_AUX_6 26 +#define MA_CHANNEL_AUX_7 27 +#define MA_CHANNEL_AUX_8 28 +#define MA_CHANNEL_AUX_9 29 +#define MA_CHANNEL_AUX_10 30 +#define MA_CHANNEL_AUX_11 31 +#define MA_CHANNEL_AUX_12 32 +#define MA_CHANNEL_AUX_13 33 +#define MA_CHANNEL_AUX_14 34 +#define MA_CHANNEL_AUX_15 35 +#define MA_CHANNEL_AUX_16 36 +#define MA_CHANNEL_AUX_17 37 +#define MA_CHANNEL_AUX_18 38 +#define MA_CHANNEL_AUX_19 39 +#define MA_CHANNEL_AUX_20 40 +#define MA_CHANNEL_AUX_21 41 +#define MA_CHANNEL_AUX_22 42 +#define MA_CHANNEL_AUX_23 43 +#define MA_CHANNEL_AUX_24 44 +#define MA_CHANNEL_AUX_25 45 +#define MA_CHANNEL_AUX_26 46 +#define MA_CHANNEL_AUX_27 47 +#define MA_CHANNEL_AUX_28 48 +#define MA_CHANNEL_AUX_29 49 +#define MA_CHANNEL_AUX_30 50 +#define MA_CHANNEL_AUX_31 51 +#define MA_CHANNEL_LEFT MA_CHANNEL_FRONT_LEFT +#define MA_CHANNEL_RIGHT MA_CHANNEL_FRONT_RIGHT +#define MA_CHANNEL_POSITION_COUNT MA_CHANNEL_AUX_31 + 1 + + +typedef int ma_result; +#define MA_SUCCESS 0 + +/* General errors. */ +#define MA_ERROR -1 /* A generic error. */ +#define MA_INVALID_ARGS -2 +#define MA_INVALID_OPERATION -3 +#define MA_OUT_OF_MEMORY -4 +#define MA_ACCESS_DENIED -5 +#define MA_TOO_LARGE -6 +#define MA_TIMEOUT -7 + +/* General miniaudio-specific errors. */ +#define MA_FORMAT_NOT_SUPPORTED -100 +#define MA_DEVICE_TYPE_NOT_SUPPORTED -101 +#define MA_SHARE_MODE_NOT_SUPPORTED -102 +#define MA_NO_BACKEND -103 +#define MA_NO_DEVICE -104 +#define MA_API_NOT_FOUND -105 +#define MA_INVALID_DEVICE_CONFIG -106 + +/* State errors. */ +#define MA_DEVICE_BUSY -200 +#define MA_DEVICE_NOT_INITIALIZED -201 +#define MA_DEVICE_NOT_STARTED -202 +#define MA_DEVICE_UNAVAILABLE -203 + +/* Operation errors. */ +#define MA_FAILED_TO_MAP_DEVICE_BUFFER -300 +#define MA_FAILED_TO_UNMAP_DEVICE_BUFFER -301 +#define MA_FAILED_TO_INIT_BACKEND -302 +#define MA_FAILED_TO_READ_DATA_FROM_CLIENT -303 +#define MA_FAILED_TO_READ_DATA_FROM_DEVICE -304 +#define MA_FAILED_TO_SEND_DATA_TO_CLIENT -305 +#define MA_FAILED_TO_SEND_DATA_TO_DEVICE -306 +#define MA_FAILED_TO_OPEN_BACKEND_DEVICE -307 +#define MA_FAILED_TO_START_BACKEND_DEVICE -308 +#define MA_FAILED_TO_STOP_BACKEND_DEVICE -309 +#define MA_FAILED_TO_CONFIGURE_BACKEND_DEVICE -310 +#define MA_FAILED_TO_CREATE_MUTEX -311 +#define MA_FAILED_TO_CREATE_EVENT -312 +#define MA_FAILED_TO_CREATE_THREAD -313 + + +/* 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_PCM_SAMPLE_SIZE_IN_BYTES 1 /* For simplicity, miniaudio does not support PCM samples that are not byte aligned. */ +#define MA_MAX_PCM_SAMPLE_SIZE_IN_BYTES 8 +#define MA_MIN_CHANNELS 1 +#define MA_MAX_CHANNELS 32 +#define MA_MIN_SAMPLE_RATE MA_SAMPLE_RATE_8000 +#define MA_MAX_SAMPLE_RATE MA_SAMPLE_RATE_384000 +#define MA_SRC_SINC_MIN_WINDOW_WIDTH 2 +#define MA_SRC_SINC_MAX_WINDOW_WIDTH 32 +#define MA_SRC_SINC_DEFAULT_WINDOW_WIDTH 32 +#define MA_SRC_SINC_LOOKUP_TABLE_RESOLUTION 8 +#define MA_SRC_INPUT_BUFFER_SIZE_IN_SAMPLES 256 + +typedef enum +{ + ma_stream_format_pcm = 0, +} ma_stream_format; + +typedef enum +{ + ma_stream_layout_interleaved = 0, + ma_stream_layout_deinterleaved +} ma_stream_layout; + +typedef enum +{ + ma_dither_mode_none = 0, + ma_dither_mode_rectangle, + ma_dither_mode_triangle +} ma_dither_mode; + +typedef enum +{ + /* + I like to keep these explicitly defined because they're used as a key into a lookup table. When items are + added to this, make sure there are no gaps and that they're added to the lookup table in ma_get_bytes_per_sample(). + */ + ma_format_unknown = 0, /* Mainly used for indicating an error, but also used as the default for the output format for decoders. */ + ma_format_u8 = 1, + ma_format_s16 = 2, /* Seems to be the most widely supported format. */ + ma_format_s24 = 3, /* Tightly packed. 3 bytes per sample. */ + ma_format_s32 = 4, + ma_format_f32 = 5, + ma_format_count +} ma_format; + +typedef enum +{ + ma_channel_mix_mode_rectangular = 0, /* Simple averaging based on the plane(s) the channel is sitting on. */ + ma_channel_mix_mode_simple, /* Drop excess channels; zeroed out extra channels. */ + ma_channel_mix_mode_custom_weights, /* Use custom weights specified in ma_channel_router_config. */ + ma_channel_mix_mode_planar_blend = ma_channel_mix_mode_rectangular, + ma_channel_mix_mode_default = ma_channel_mix_mode_planar_blend +} ma_channel_mix_mode; + +typedef enum +{ + ma_standard_channel_map_microsoft, + ma_standard_channel_map_alsa, + ma_standard_channel_map_rfc3551, /* Based off AIFF. */ + ma_standard_channel_map_flac, + ma_standard_channel_map_vorbis, + ma_standard_channel_map_sound4, /* FreeBSD's sound(4). */ + ma_standard_channel_map_sndio, /* www.sndio.org/tips.html */ + ma_standard_channel_map_webaudio = ma_standard_channel_map_flac, /* https://webaudio.github.io/web-audio-api/#ChannelOrdering. Only 1, 2, 4 and 6 channels are defined, but can fill in the gaps with logical assumptions. */ + ma_standard_channel_map_default = ma_standard_channel_map_microsoft +} ma_standard_channel_map; + +typedef enum +{ + ma_performance_profile_low_latency = 0, + ma_performance_profile_conservative +} ma_performance_profile; + + +typedef struct ma_format_converter ma_format_converter; +typedef ma_uint32 (* ma_format_converter_read_proc) (ma_format_converter* pConverter, ma_uint32 frameCount, void* pFramesOut, void* pUserData); +typedef ma_uint32 (* ma_format_converter_read_deinterleaved_proc)(ma_format_converter* pConverter, ma_uint32 frameCount, void** ppSamplesOut, void* pUserData); + +typedef struct +{ + ma_format formatIn; + ma_format formatOut; + ma_uint32 channels; + ma_stream_format streamFormatIn; + ma_stream_format streamFormatOut; + ma_dither_mode ditherMode; + ma_bool32 noSSE2 : 1; + ma_bool32 noAVX2 : 1; + ma_bool32 noAVX512 : 1; + ma_bool32 noNEON : 1; + ma_format_converter_read_proc onRead; + ma_format_converter_read_deinterleaved_proc onReadDeinterleaved; + void* pUserData; +} ma_format_converter_config; + +struct ma_format_converter +{ + ma_format_converter_config config; + ma_bool32 useSSE2 : 1; + ma_bool32 useAVX2 : 1; + ma_bool32 useAVX512 : 1; + ma_bool32 useNEON : 1; + void (* onConvertPCM)(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode); + void (* onInterleavePCM)(void* dst, const void** src, ma_uint64 frameCount, ma_uint32 channels); + void (* onDeinterleavePCM)(void** dst, const void* src, ma_uint64 frameCount, ma_uint32 channels); +}; + + + +typedef struct ma_channel_router ma_channel_router; +typedef ma_uint32 (* ma_channel_router_read_deinterleaved_proc)(ma_channel_router* pRouter, ma_uint32 frameCount, void** ppSamplesOut, void* pUserData); + +typedef struct +{ + ma_uint32 channelsIn; + ma_uint32 channelsOut; + ma_channel channelMapIn[MA_MAX_CHANNELS]; + ma_channel channelMapOut[MA_MAX_CHANNELS]; + ma_channel_mix_mode mixingMode; + float weights[MA_MAX_CHANNELS][MA_MAX_CHANNELS]; /* [in][out]. Only used when mixingMode is set to ma_channel_mix_mode_custom_weights. */ + ma_bool32 noSSE2 : 1; + ma_bool32 noAVX2 : 1; + ma_bool32 noAVX512 : 1; + ma_bool32 noNEON : 1; + ma_channel_router_read_deinterleaved_proc onReadDeinterleaved; + void* pUserData; +} ma_channel_router_config; + +struct ma_channel_router +{ + ma_channel_router_config config; + ma_bool32 isPassthrough : 1; + ma_bool32 isSimpleShuffle : 1; + ma_bool32 useSSE2 : 1; + ma_bool32 useAVX2 : 1; + ma_bool32 useAVX512 : 1; + ma_bool32 useNEON : 1; + ma_uint8 shuffleTable[MA_MAX_CHANNELS]; +}; + + + +typedef struct ma_src ma_src; +typedef ma_uint32 (* ma_src_read_deinterleaved_proc)(ma_src* pSRC, ma_uint32 frameCount, void** ppSamplesOut, void* pUserData); /* Returns the number of frames that were read. */ + +typedef enum +{ + ma_src_algorithm_linear = 0, + ma_src_algorithm_sinc, + ma_src_algorithm_none, + ma_src_algorithm_default = ma_src_algorithm_linear +} ma_src_algorithm; + +typedef enum +{ + ma_src_sinc_window_function_hann = 0, + ma_src_sinc_window_function_rectangular, + ma_src_sinc_window_function_default = ma_src_sinc_window_function_hann +} ma_src_sinc_window_function; + +typedef struct +{ + ma_src_sinc_window_function windowFunction; + ma_uint32 windowWidth; +} ma_src_config_sinc; + +typedef struct +{ + ma_uint32 sampleRateIn; + ma_uint32 sampleRateOut; + ma_uint32 channels; + ma_src_algorithm algorithm; + ma_bool32 neverConsumeEndOfInput : 1; + ma_bool32 noSSE2 : 1; + ma_bool32 noAVX2 : 1; + ma_bool32 noAVX512 : 1; + ma_bool32 noNEON : 1; + ma_src_read_deinterleaved_proc onReadDeinterleaved; + void* pUserData; + union + { + ma_src_config_sinc sinc; + }; +} ma_src_config; + +MA_ALIGNED_STRUCT(MA_SIMD_ALIGNMENT) ma_src +{ + union + { + struct + { + MA_ALIGN(MA_SIMD_ALIGNMENT) float input[MA_MAX_CHANNELS][MA_SRC_INPUT_BUFFER_SIZE_IN_SAMPLES]; + float timeIn; + ma_uint32 leftoverFrames; + } linear; + + struct + { + MA_ALIGN(MA_SIMD_ALIGNMENT) float input[MA_MAX_CHANNELS][MA_SRC_SINC_MAX_WINDOW_WIDTH*2 + MA_SRC_INPUT_BUFFER_SIZE_IN_SAMPLES]; + float timeIn; + ma_uint32 inputFrameCount; /* The number of frames sitting in the input buffer, not including the first half of the window. */ + ma_uint32 windowPosInSamples; /* An offset of . */ + float table[MA_SRC_SINC_MAX_WINDOW_WIDTH*1 * MA_SRC_SINC_LOOKUP_TABLE_RESOLUTION]; /* Precomputed lookup table. The +1 is used to avoid the need for an overflow check. */ + } sinc; + }; + + ma_src_config config; + ma_bool32 isEndOfInputLoaded : 1; + ma_bool32 useSSE2 : 1; + ma_bool32 useAVX2 : 1; + ma_bool32 useAVX512 : 1; + ma_bool32 useNEON : 1; +}; + +typedef struct ma_pcm_converter ma_pcm_converter; +typedef ma_uint32 (* ma_pcm_converter_read_proc)(ma_pcm_converter* pDSP, void* pFramesOut, ma_uint32 frameCount, void* pUserData); + +typedef struct +{ + ma_format formatIn; + ma_uint32 channelsIn; + ma_uint32 sampleRateIn; + ma_channel channelMapIn[MA_MAX_CHANNELS]; + ma_format formatOut; + ma_uint32 channelsOut; + ma_uint32 sampleRateOut; + ma_channel channelMapOut[MA_MAX_CHANNELS]; + ma_channel_mix_mode channelMixMode; + ma_dither_mode ditherMode; + ma_src_algorithm srcAlgorithm; + ma_bool32 allowDynamicSampleRate; + ma_bool32 neverConsumeEndOfInput : 1; /* <-- For SRC. */ + ma_bool32 noSSE2 : 1; + ma_bool32 noAVX2 : 1; + ma_bool32 noAVX512 : 1; + ma_bool32 noNEON : 1; + ma_pcm_converter_read_proc onRead; + void* pUserData; + union + { + ma_src_config_sinc sinc; + }; +} ma_pcm_converter_config; + +MA_ALIGNED_STRUCT(MA_SIMD_ALIGNMENT) ma_pcm_converter +{ + ma_pcm_converter_read_proc onRead; + void* pUserData; + ma_format_converter formatConverterIn; /* For converting data to f32 in preparation for further processing. */ + ma_format_converter formatConverterOut; /* For converting data to the requested output format. Used as the final step in the processing pipeline. */ + ma_channel_router channelRouter; /* For channel conversion. */ + ma_src src; /* For sample rate conversion. */ + ma_bool32 isDynamicSampleRateAllowed : 1; /* ma_pcm_converter_set_input_sample_rate() and ma_pcm_converter_set_output_sample_rate() will fail if this is set to false. */ + ma_bool32 isPreFormatConversionRequired : 1; + ma_bool32 isPostFormatConversionRequired : 1; + ma_bool32 isChannelRoutingRequired : 1; + ma_bool32 isSRCRequired : 1; + ma_bool32 isChannelRoutingAtStart : 1; + ma_bool32 isPassthrough : 1; /* <-- Will be set to true when the conversion pipeline is an optimized passthrough. */ +}; + + +/************************************************************************************************************************************************************ +************************************************************************************************************************************************************* + +DATA CONVERSION +=============== + +This section contains the APIs for data conversion. You will find everything here for channel mapping, sample format conversion, resampling, etc. + +************************************************************************************************************************************************************* +************************************************************************************************************************************************************/ + +/************************************************************************************************************************************************************ + +Channel Maps +============ + +Below is the channel map used by ma_standard_channel_map_default: + +|---------------|------------------------------| +| Channel Count | Mapping | +|---------------|------------------------------| +| 1 (Mono) | 0: MA_CHANNEL_MONO | +|---------------|------------------------------| +| 2 (Stereo) | 0: MA_CHANNEL_FRONT_LEFT | +| | 1: MA_CHANNEL_FRONT_RIGHT | +|---------------|------------------------------| +| 3 | 0: MA_CHANNEL_FRONT_LEFT | +| | 1: MA_CHANNEL_FRONT_RIGHT | +| | 2: MA_CHANNEL_FRONT_CENTER | +|---------------|------------------------------| +| 4 (Surround) | 0: MA_CHANNEL_FRONT_LEFT | +| | 1: MA_CHANNEL_FRONT_RIGHT | +| | 2: MA_CHANNEL_FRONT_CENTER | +| | 3: MA_CHANNEL_BACK_CENTER | +|---------------|------------------------------| +| 5 | 0: MA_CHANNEL_FRONT_LEFT | +| | 1: MA_CHANNEL_FRONT_RIGHT | +| | 2: MA_CHANNEL_FRONT_CENTER | +| | 3: MA_CHANNEL_BACK_LEFT | +| | 4: MA_CHANNEL_BACK_RIGHT | +|---------------|------------------------------| +| 6 (5.1) | 0: MA_CHANNEL_FRONT_LEFT | +| | 1: MA_CHANNEL_FRONT_RIGHT | +| | 2: MA_CHANNEL_FRONT_CENTER | +| | 3: MA_CHANNEL_LFE | +| | 4: MA_CHANNEL_SIDE_LEFT | +| | 5: MA_CHANNEL_SIDE_RIGHT | +|---------------|------------------------------| +| 7 | 0: MA_CHANNEL_FRONT_LEFT | +| | 1: MA_CHANNEL_FRONT_RIGHT | +| | 2: MA_CHANNEL_FRONT_CENTER | +| | 3: MA_CHANNEL_LFE | +| | 4: MA_CHANNEL_BACK_CENTER | +| | 4: MA_CHANNEL_SIDE_LEFT | +| | 5: MA_CHANNEL_SIDE_RIGHT | +|---------------|------------------------------| +| 8 (7.1) | 0: MA_CHANNEL_FRONT_LEFT | +| | 1: MA_CHANNEL_FRONT_RIGHT | +| | 2: MA_CHANNEL_FRONT_CENTER | +| | 3: MA_CHANNEL_LFE | +| | 4: MA_CHANNEL_BACK_LEFT | +| | 5: MA_CHANNEL_BACK_RIGHT | +| | 6: MA_CHANNEL_SIDE_LEFT | +| | 7: MA_CHANNEL_SIDE_RIGHT | +|---------------|------------------------------| +| Other | All channels set to 0. This | +| | is equivalent to the same | +| | mapping as the device. | +|---------------|------------------------------| + +************************************************************************************************************************************************************/ + +/* +Helper for retrieving a standard channel map. +*/ +void ma_get_standard_channel_map(ma_standard_channel_map standardChannelMap, ma_uint32 channels, ma_channel channelMap[MA_MAX_CHANNELS]); + +/* +Copies a channel map. +*/ +void ma_channel_map_copy(ma_channel* pOut, const ma_channel* pIn, ma_uint32 channels); + + +/* +Determines whether or not a channel map is valid. + +A blank channel map is valid (all channels set to MA_CHANNEL_NONE). The way a blank channel map is handled is context specific, but +is usually treated as a passthrough. + +Invalid channel maps: + - A channel map with no channels + - A channel map with more than one channel and a mono channel +*/ +ma_bool32 ma_channel_map_valid(ma_uint32 channels, const ma_channel channelMap[MA_MAX_CHANNELS]); + +/* +Helper for comparing two channel maps for equality. + +This assumes the channel count is the same between the two. +*/ +ma_bool32 ma_channel_map_equal(ma_uint32 channels, const ma_channel channelMapA[MA_MAX_CHANNELS], const ma_channel channelMapB[MA_MAX_CHANNELS]); + +/* +Helper for determining if a channel map is blank (all channels set to MA_CHANNEL_NONE). +*/ +ma_bool32 ma_channel_map_blank(ma_uint32 channels, const ma_channel channelMap[MA_MAX_CHANNELS]); + +/* +Helper for determining whether or not a channel is present in the given channel map. +*/ +ma_bool32 ma_channel_map_contains_channel_position(ma_uint32 channels, const ma_channel channelMap[MA_MAX_CHANNELS], ma_channel channelPosition); + + +/************************************************************************************************************************************************************ + +Format Conversion +================= +The format converter serves two purposes: + 1) Conversion between data formats (u8 to f32, etc.) + 2) Interleaving and deinterleaving + +When initializing a converter, you specify the input and output formats (u8, s16, etc.) and read callbacks. There are two read callbacks - one for +interleaved input data (onRead) and another for deinterleaved input data (onReadDeinterleaved). You implement whichever is most convenient for you. You +can implement both, but it's not recommended as it just introduces unnecessary complexity. + +To read data as interleaved samples, use ma_format_converter_read(). Otherwise use ma_format_converter_read_deinterleaved(). + +Dithering +--------- +The format converter also supports dithering. Dithering can be set using ditherMode variable in the config, like so. + + pConfig->ditherMode = ma_dither_mode_rectangle; + +The different dithering modes include the following, in order of efficiency: + - None: ma_dither_mode_none + - Rectangle: ma_dither_mode_rectangle + - Triangle: ma_dither_mode_triangle + +Note that even if the dither mode is set to something other than ma_dither_mode_none, it will be ignored for conversions where dithering is not needed. +Dithering is available for the following conversions: + - s16 -> u8 + - s24 -> u8 + - s32 -> u8 + - f32 -> u8 + - s24 -> s16 + - s32 -> s16 + - f32 -> s16 + +Note that it is not an error to pass something other than ma_dither_mode_none for conversions where dither is not used. It will just be ignored. + +************************************************************************************************************************************************************/ + +/* +Initializes a format converter. +*/ +ma_result ma_format_converter_init(const ma_format_converter_config* pConfig, ma_format_converter* pConverter); + +/* +Reads data from the format converter as interleaved channels. +*/ +ma_uint64 ma_format_converter_read(ma_format_converter* pConverter, ma_uint64 frameCount, void* pFramesOut, void* pUserData); + +/* +Reads data from the format converter as deinterleaved channels. +*/ +ma_uint64 ma_format_converter_read_deinterleaved(ma_format_converter* pConverter, ma_uint64 frameCount, void** ppSamplesOut, void* pUserData); + +/* +Helper for initializing a format converter config. +*/ +ma_format_converter_config ma_format_converter_config_init_new(void); +ma_format_converter_config ma_format_converter_config_init(ma_format formatIn, ma_format formatOut, ma_uint32 channels, ma_format_converter_read_proc onRead, void* pUserData); +ma_format_converter_config ma_format_converter_config_init_deinterleaved(ma_format formatIn, ma_format formatOut, ma_uint32 channels, ma_format_converter_read_deinterleaved_proc onReadDeinterleaved, void* pUserData); + + + +/************************************************************************************************************************************************************ + +Channel Routing +=============== +There are two main things you can do with the channel router: + 1) Rearrange channels + 2) Convert from one channel count to another + +Channel Rearrangement +--------------------- +A simple example of channel rearrangement may be swapping the left and right channels in a stereo stream. To do this you just pass in the same channel +count for both the input and output with channel maps that contain the same channels (in a different order). + +Channel Conversion +------------------ +The channel router can also convert from one channel count to another, such as converting a 5.1 stream to stero. When changing the channel count, the +router will first perform a 1:1 mapping of channel positions that are present in both the input and output channel maps. The second thing it will do +is distribute the input mono channel (if any) across all output channels, excluding any None and LFE channels. If there is an output mono channel, all +input channels will be averaged, excluding any None and LFE channels. + +The last case to consider is when a channel position in the input channel map is not present in the output channel map, and vice versa. In this case the +channel router will perform a blend of other related channels to produce an audible channel. There are several blending modes. + 1) Simple + Unmatched channels are silenced. + 2) Planar Blending + Channels are blended based on a set of planes that each speaker emits audio from. + +Rectangular / Planar Blending +----------------------------- +In this mode, channel positions are associated with a set of planes where the channel conceptually emits audio from. An example is the front/left speaker. +This speaker is positioned to the front of the listener, so you can think of it as emitting audio from the front plane. It is also positioned to the left +of the listener so you can think of it as also emitting audio from the left plane. Now consider the (unrealistic) situation where the input channel map +contains only the front/left channel position, but the output channel map contains both the front/left and front/center channel. When deciding on the audio +data to send to the front/center speaker (which has no 1:1 mapping with an input channel) we need to use some logic based on our available input channel +positions. + +As mentioned earlier, our front/left speaker is, conceptually speaking, emitting audio from the front _and_ the left planes. Similarly, the front/center +speaker is emitting audio from _only_ the front plane. What these two channels have in common is that they are both emitting audio from the front plane. +Thus, it makes sense that the front/center speaker should receive some contribution from the front/left channel. How much contribution depends on their +planar relationship (thus the name of this blending technique). + +Because the front/left channel is emitting audio from two planes (front and left), you can think of it as though it's willing to dedicate 50% of it's total +volume to each of it's planes (a channel position emitting from 1 plane would be willing to given 100% of it's total volume to that plane, and a channel +position emitting from 3 planes would be willing to given 33% of it's total volume to each plane). Similarly, the front/center speaker is emitting audio +from only one plane so you can think of it as though it's willing to _take_ 100% of it's volume from front plane emissions. Now, since the front/left +channel is willing to _give_ 50% of it's total volume to the front plane, and the front/center speaker is willing to _take_ 100% of it's total volume +from the front, you can imagine that 50% of the front/left speaker will be given to the front/center speaker. + +Usage +----- +To use the channel router you need to specify three things: + 1) The input channel count and channel map + 2) The output channel count and channel map + 3) The mixing mode to use in the case where a 1:1 mapping is unavailable + +Note that input and output data is always deinterleaved 32-bit floating point. + +Initialize the channel router with ma_channel_router_init(). You will need to pass in a config object which specifies the input and output configuration, +mixing mode and a callback for sending data to the router. This callback will be called when input data needs to be sent to the router for processing. Note +that the mixing mode is only used when a 1:1 mapping is unavailable. This includes the custom weights mode. + +Read data from the channel router with ma_channel_router_read_deinterleaved(). Output data is always 32-bit floating point. + +************************************************************************************************************************************************************/ + +/* +Initializes a channel router where it is assumed that the input data is non-interleaved. +*/ +ma_result ma_channel_router_init(const ma_channel_router_config* pConfig, ma_channel_router* pRouter); + +/* +Reads data from the channel router as deinterleaved channels. +*/ +ma_uint64 ma_channel_router_read_deinterleaved(ma_channel_router* pRouter, ma_uint64 frameCount, void** ppSamplesOut, void* pUserData); + +/* +Helper for initializing a channel router config. +*/ +ma_channel_router_config ma_channel_router_config_init(ma_uint32 channelsIn, const ma_channel channelMapIn[MA_MAX_CHANNELS], ma_uint32 channelsOut, const ma_channel channelMapOut[MA_MAX_CHANNELS], ma_channel_mix_mode mixingMode, ma_channel_router_read_deinterleaved_proc onRead, void* pUserData); + + +/************************************************************************************************************************************************************ + +Sample Rate Conversion +====================== + +************************************************************************************************************************************************************/ + +/* +Initializes a sample rate conversion object. +*/ +ma_result ma_src_init(const ma_src_config* pConfig, ma_src* pSRC); + +/* +Dynamically adjusts the sample rate. + +This is useful for dynamically adjust pitch. Keep in mind, however, that this will speed up or slow down the sound. If this +is not acceptable you will need to use your own algorithm. +*/ +ma_result ma_src_set_sample_rate(ma_src* pSRC, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut); + +/* +Reads a number of frames. + +Returns the number of frames actually read. +*/ +ma_uint64 ma_src_read_deinterleaved(ma_src* pSRC, ma_uint64 frameCount, void** ppSamplesOut, void* pUserData); + +/* +Helper for creating a sample rate conversion config. +*/ +ma_src_config ma_src_config_init_new(void); +ma_src_config ma_src_config_init(ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_uint32 channels, ma_src_read_deinterleaved_proc onReadDeinterleaved, void* pUserData); + + +/************************************************************************************************************************************************************ + +Conversion + +************************************************************************************************************************************************************/ + +/* +Initializes a DSP object. +*/ +ma_result ma_pcm_converter_init(const ma_pcm_converter_config* pConfig, ma_pcm_converter* pDSP); + +/* +Dynamically adjusts the input sample rate. + +This will fail is the DSP was not initialized with allowDynamicSampleRate. + +DEPRECATED. Use ma_pcm_converter_set_sample_rate() instead. +*/ +ma_result ma_pcm_converter_set_input_sample_rate(ma_pcm_converter* pDSP, ma_uint32 sampleRateOut); + +/* +Dynamically adjusts the output sample rate. + +This is useful for dynamically adjust pitch. Keep in mind, however, that this will speed up or slow down the sound. If this +is not acceptable you will need to use your own algorithm. + +This will fail is the DSP was not initialized with allowDynamicSampleRate. + +DEPRECATED. Use ma_pcm_converter_set_sample_rate() instead. +*/ +ma_result ma_pcm_converter_set_output_sample_rate(ma_pcm_converter* pDSP, ma_uint32 sampleRateOut); + +/* +Dynamically adjusts the output sample rate. + +This is useful for dynamically adjust pitch. Keep in mind, however, that this will speed up or slow down the sound. If this +is not acceptable you will need to use your own algorithm. + +This will fail if the DSP was not initialized with allowDynamicSampleRate. +*/ +ma_result ma_pcm_converter_set_sample_rate(ma_pcm_converter* pDSP, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut); + +/* +Reads a number of frames and runs them through the DSP processor. +*/ +ma_uint64 ma_pcm_converter_read(ma_pcm_converter* pDSP, void* pFramesOut, ma_uint64 frameCount); + +/* +Helper for initializing a ma_pcm_converter_config object. +*/ +ma_pcm_converter_config ma_pcm_converter_config_init_new(void); +ma_pcm_converter_config ma_pcm_converter_config_init(ma_format formatIn, ma_uint32 channelsIn, ma_uint32 sampleRateIn, ma_format formatOut, ma_uint32 channelsOut, ma_uint32 sampleRateOut, ma_pcm_converter_read_proc onRead, void* pUserData); +ma_pcm_converter_config ma_pcm_converter_config_init_ex(ma_format formatIn, ma_uint32 channelsIn, ma_uint32 sampleRateIn, ma_channel channelMapIn[MA_MAX_CHANNELS], ma_format formatOut, ma_uint32 channelsOut, ma_uint32 sampleRateOut, ma_channel channelMapOut[MA_MAX_CHANNELS], ma_pcm_converter_read_proc onRead, void* pUserData); + +/* +High-level helper for doing a full format conversion in one go. Returns the number of output frames. Call this with pOut set to NULL to +determine the required size of the output buffer. + +A return value of 0 indicates an error. + +This function is useful for one-off bulk conversions, but if you're streaming data you should use the ma_pcm_converter APIs instead. +*/ +ma_uint64 ma_convert_frames(void* pOut, ma_format formatOut, ma_uint32 channelsOut, ma_uint32 sampleRateOut, const void* pIn, ma_format formatIn, ma_uint32 channelsIn, ma_uint32 sampleRateIn, ma_uint64 frameCount); +ma_uint64 ma_convert_frames_ex(void* pOut, ma_format formatOut, ma_uint32 channelsOut, ma_uint32 sampleRateOut, ma_channel channelMapOut[MA_MAX_CHANNELS], const void* pIn, ma_format formatIn, ma_uint32 channelsIn, ma_uint32 sampleRateIn, ma_channel channelMapIn[MA_MAX_CHANNELS], ma_uint64 frameCount); + + +/************************************************************************************************************************************************************ + +Ring Buffer +=========== + +Features +-------- +- Lock free (assuming single producer, single consumer) +- Support for interleaved and deinterleaved streams +- Allows the caller to allocate their own block of memory + +Usage +----- +- Call ma_rb_init() to initialize a simple buffer, with an optional pre-allocated buffer. If you pass in NULL + for the pre-allocated buffer, it will be allocated for you and free()'d in ma_rb_uninit(). If you pass in + your own pre-allocated buffer, free()-ing is left to you. + +- Call ma_rb_init_ex() if you need a deinterleaved buffer. The data for each sub-buffer is offset from each + other based on the stride. Use ma_rb_get_subbuffer_stride(), ma_rb_get_subbuffer_offset() and + ma_rb_get_subbuffer_ptr() to manage your sub-buffers. + +- Use ma_rb_acquire_read() and ma_rb_acquire_write() to retrieve a pointer to a section of the ring buffer. + You specify the number of bytes 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 bytes requested will require a loop, it will be + clamped to the end of the buffer. Therefore, the number of bytes you're given may be less than the number + you requested. + +- After calling ma_rb_acquire_read/write(), you do your work on the buffer and then "commit" it with + ma_rb_commit_read/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_rb_acquire_read/write() and is only used + for validation. The number of bytes passed to ma_rb_commit_read/write() is what's used to increment the + pointers. + +- If you want to correct for drift between the write pointer and the read pointer you can use a combination + of ma_rb_pointer_distance(), ma_rb_seek_read() and ma_rb_seek_write(). Note that you can only move the + pointers forward, and you should only move the read pointer forward via the consumer thread, and the write + pointer forward by the producer thread. If there is too much space between the pointers, move the read + pointer forward. If there is too little space between the pointers, move the write pointer forward. + + +Notes +----- +- Thread safety depends on a single producer, single consumer model. Only one thread is allowed to write, and + only one thread is allowed to read. The producer is the only one allowed to move the write pointer, and the + consumer is the only one allowed to move the read pointer. +- Operates on bytes. Use ma_pcm_rb to operate in terms of PCM frames. +- Maximum buffer size in bytes is 0x7FFFFFFF-(MA_SIMD_ALIGNMENT-1) because of reasons. + + +PCM Ring Buffer +=============== +This is the same as the regular ring buffer, except that it works on PCM frames instead of bytes. + +************************************************************************************************************************************************************/ +typedef struct +{ + void* pBuffer; + 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_rb; + +ma_result ma_rb_init_ex(size_t subbufferSizeInBytes, size_t subbufferCount, size_t subbufferStrideInBytes, void* pOptionalPreallocatedBuffer, ma_rb* pRB); +ma_result ma_rb_init(size_t bufferSizeInBytes, void* pOptionalPreallocatedBuffer, ma_rb* pRB); +void ma_rb_uninit(ma_rb* pRB); +ma_result ma_rb_acquire_read(ma_rb* pRB, size_t* pSizeInBytes, void** ppBufferOut); +ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes, void* pBufferOut); +ma_result ma_rb_acquire_write(ma_rb* pRB, size_t* pSizeInBytes, void** ppBufferOut); +ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes, void* pBufferOut); +ma_result ma_rb_seek_read(ma_rb* pRB, size_t offsetInBytes); +ma_result ma_rb_seek_write(ma_rb* pRB, size_t offsetInBytes); +ma_int32 ma_rb_pointer_distance(ma_rb* pRB); /* Returns the distance between the write pointer and the read pointer. Should never be negative for a correct program. */ +size_t ma_rb_get_subbuffer_size(ma_rb* pRB); +size_t ma_rb_get_subbuffer_stride(ma_rb* pRB); +size_t ma_rb_get_subbuffer_offset(ma_rb* pRB, size_t subbufferIndex); +void* ma_rb_get_subbuffer_ptr(ma_rb* pRB, size_t subbufferIndex, void* pBuffer); + + +typedef struct +{ + ma_rb rb; + ma_format format; + ma_uint32 channels; +} ma_pcm_rb; + +ma_result ma_pcm_rb_init_ex(ma_format format, ma_uint32 channels, ma_uint32 subbufferSizeInFrames, ma_uint32 subbufferCount, ma_uint32 subbufferStrideInFrames, void* pOptionalPreallocatedBuffer, ma_pcm_rb* pRB); +ma_result ma_pcm_rb_init(ma_format format, ma_uint32 channels, ma_uint32 bufferSizeInFrames, void* pOptionalPreallocatedBuffer, ma_pcm_rb* pRB); +void ma_pcm_rb_uninit(ma_pcm_rb* pRB); +ma_result ma_pcm_rb_acquire_read(ma_pcm_rb* pRB, ma_uint32* pSizeInFrames, void** ppBufferOut); +ma_result ma_pcm_rb_commit_read(ma_pcm_rb* pRB, ma_uint32 sizeInFrames, void* pBufferOut); +ma_result ma_pcm_rb_acquire_write(ma_pcm_rb* pRB, ma_uint32* pSizeInFrames, void** ppBufferOut); +ma_result ma_pcm_rb_commit_write(ma_pcm_rb* pRB, ma_uint32 sizeInFrames, void* pBufferOut); +ma_result ma_pcm_rb_seek_read(ma_pcm_rb* pRB, ma_uint32 offsetInFrames); +ma_result ma_pcm_rb_seek_write(ma_pcm_rb* pRB, ma_uint32 offsetInFrames); +ma_int32 ma_pcm_rb_pointer_disance(ma_pcm_rb* pRB); /* Return value is in frames. */ +ma_uint32 ma_pcm_rb_get_subbuffer_size(ma_pcm_rb* pRB); +ma_uint32 ma_pcm_rb_get_subbuffer_stride(ma_pcm_rb* pRB); +ma_uint32 ma_pcm_rb_get_subbuffer_offset(ma_pcm_rb* pRB, ma_uint32 subbufferIndex); +void* ma_pcm_rb_get_subbuffer_ptr(ma_pcm_rb* pRB, ma_uint32 subbufferIndex, void* pBuffer); + + +/************************************************************************************************************************************************************ + +Miscellaneous Helpers + +************************************************************************************************************************************************************/ + +/* +malloc(). Calls MA_MALLOC(). +*/ +void* ma_malloc(size_t sz); + +/* +realloc(). Calls MA_REALLOC(). +*/ +void* ma_realloc(void* p, size_t sz); + +/* +free(). Calls MA_FREE(). +*/ +void ma_free(void* p); + +/* +Performs an aligned malloc, with the assumption that the alignment is a power of 2. +*/ +void* ma_aligned_malloc(size_t sz, size_t alignment); + +/* +Free's an aligned malloc'd buffer. +*/ +void ma_aligned_free(void* p); + +/* +Retrieves a friendly name for a format. +*/ +const char* ma_get_format_name(ma_format format); + +/* +Blends two frames in floating point format. +*/ +void ma_blend_f32(float* pOut, float* pInA, float* pInB, float factor, ma_uint32 channels); + +/* +Retrieves the size of a sample in bytes for the given format. + +This API is efficient and is implemented using a lookup table. + +Thread Safety: SAFE + This API is pure. +*/ +ma_uint32 ma_get_bytes_per_sample(ma_format format); +static MA_INLINE ma_uint32 ma_get_bytes_per_frame(ma_format format, ma_uint32 channels) { return ma_get_bytes_per_sample(format) * channels; } + +/* +Converts a log level to a string. +*/ +const char* ma_log_level_to_string(ma_uint32 logLevel); + + +/************************************************************************************************************************************************************ + +Format Conversion + +************************************************************************************************************************************************************/ +void ma_pcm_u8_to_s16(void* pOut, const void* pIn, ma_uint64 count, ma_dither_mode ditherMode); +void ma_pcm_u8_to_s24(void* pOut, const void* pIn, ma_uint64 count, ma_dither_mode ditherMode); +void ma_pcm_u8_to_s32(void* pOut, const void* pIn, ma_uint64 count, ma_dither_mode ditherMode); +void ma_pcm_u8_to_f32(void* pOut, const void* pIn, ma_uint64 count, ma_dither_mode ditherMode); +void ma_pcm_s16_to_u8(void* pOut, const void* pIn, ma_uint64 count, ma_dither_mode ditherMode); +void ma_pcm_s16_to_s24(void* pOut, const void* pIn, ma_uint64 count, ma_dither_mode ditherMode); +void ma_pcm_s16_to_s32(void* pOut, const void* pIn, ma_uint64 count, ma_dither_mode ditherMode); +void ma_pcm_s16_to_f32(void* pOut, const void* pIn, ma_uint64 count, ma_dither_mode ditherMode); +void ma_pcm_s24_to_u8(void* pOut, const void* pIn, ma_uint64 count, ma_dither_mode ditherMode); +void ma_pcm_s24_to_s16(void* pOut, const void* pIn, ma_uint64 count, ma_dither_mode ditherMode); +void ma_pcm_s24_to_s32(void* pOut, const void* pIn, ma_uint64 count, ma_dither_mode ditherMode); +void ma_pcm_s24_to_f32(void* pOut, const void* pIn, ma_uint64 count, ma_dither_mode ditherMode); +void ma_pcm_s32_to_u8(void* pOut, const void* pIn, ma_uint64 count, ma_dither_mode ditherMode); +void ma_pcm_s32_to_s16(void* pOut, const void* pIn, ma_uint64 count, ma_dither_mode ditherMode); +void ma_pcm_s32_to_s24(void* pOut, const void* pIn, ma_uint64 count, ma_dither_mode ditherMode); +void ma_pcm_s32_to_f32(void* pOut, const void* pIn, ma_uint64 count, ma_dither_mode ditherMode); +void ma_pcm_f32_to_u8(void* pOut, const void* pIn, ma_uint64 count, ma_dither_mode ditherMode); +void ma_pcm_f32_to_s16(void* pOut, const void* pIn, ma_uint64 count, ma_dither_mode ditherMode); +void ma_pcm_f32_to_s24(void* pOut, const void* pIn, ma_uint64 count, ma_dither_mode ditherMode); +void ma_pcm_f32_to_s32(void* pOut, const void* pIn, ma_uint64 count, ma_dither_mode ditherMode); +void ma_pcm_convert(void* pOut, ma_format formatOut, const void* pIn, ma_format formatIn, ma_uint64 sampleCount, ma_dither_mode ditherMode); + +/* +Deinterleaves an interleaved buffer. +*/ +void ma_deinterleave_pcm_frames(ma_format format, ma_uint32 channels, ma_uint64 frameCount, const void* pInterleavedPCMFrames, void** ppDeinterleavedPCMFrames); + +/* +Interleaves a group of deinterleaved buffers. +*/ +void ma_interleave_pcm_frames(ma_format format, ma_uint32 channels, ma_uint64 frameCount, const void** ppDeinterleavedPCMFrames, void* pInterleavedPCMFrames); + + +/************************************************************************************************************************************************************ +************************************************************************************************************************************************************* + +DEVICE I/O +========== + +This section contains the APIs for device playback and capture. Here is where you'll find ma_device_init(), etc. + +************************************************************************************************************************************************************* +************************************************************************************************************************************************************/ +#ifndef MA_NO_DEVICE_IO +/* Some backends are only supported on certain platforms. */ +#if defined(MA_WIN32) + #define MA_SUPPORT_WASAPI + #if defined(MA_WIN32_DESKTOP) /* DirectSound and WinMM backends are only supported on desktops. */ + #define MA_SUPPORT_DSOUND + #define MA_SUPPORT_WINMM + #define MA_SUPPORT_JACK /* JACK is technically supported on Windows, but I don't know how many people use it in practice... */ + #endif +#endif +#if defined(MA_UNIX) + #if defined(MA_LINUX) + #if !defined(MA_ANDROID) /* ALSA is not supported on Android. */ + #define MA_SUPPORT_ALSA + #endif + #endif + #if !defined(MA_BSD) && !defined(MA_ANDROID) && !defined(MA_EMSCRIPTEN) + #define MA_SUPPORT_PULSEAUDIO + #define MA_SUPPORT_JACK + #endif + #if defined(MA_ANDROID) + #define MA_SUPPORT_AAUDIO + #define MA_SUPPORT_OPENSL + #endif + #if defined(__OpenBSD__) /* <-- Change this to "#if defined(MA_BSD)" to enable sndio on all BSD flavors. */ + #define MA_SUPPORT_SNDIO /* sndio is only supported on OpenBSD for now. May be expanded later if there's demand. */ + #endif + #if defined(__NetBSD__) || defined(__OpenBSD__) + #define MA_SUPPORT_AUDIO4 /* Only support audio(4) on platforms with known support. */ + #endif + #if defined(__FreeBSD__) || defined(__DragonFly__) + #define MA_SUPPORT_OSS /* Only support OSS on specific platforms with known support. */ + #endif +#endif +#if defined(MA_APPLE) + #define MA_SUPPORT_COREAUDIO +#endif +#if defined(MA_EMSCRIPTEN) + #define MA_SUPPORT_WEBAUDIO +#endif + +/* 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 +#endif +#if !defined(MA_NO_DSOUND) && defined(MA_SUPPORT_DSOUND) + #define MA_ENABLE_DSOUND +#endif +#if !defined(MA_NO_WINMM) && defined(MA_SUPPORT_WINMM) + #define MA_ENABLE_WINMM +#endif +#if !defined(MA_NO_ALSA) && defined(MA_SUPPORT_ALSA) + #define MA_ENABLE_ALSA +#endif +#if !defined(MA_NO_PULSEAUDIO) && defined(MA_SUPPORT_PULSEAUDIO) + #define MA_ENABLE_PULSEAUDIO +#endif +#if !defined(MA_NO_JACK) && defined(MA_SUPPORT_JACK) + #define MA_ENABLE_JACK +#endif +#if !defined(MA_NO_COREAUDIO) && defined(MA_SUPPORT_COREAUDIO) + #define MA_ENABLE_COREAUDIO +#endif +#if !defined(MA_NO_SNDIO) && defined(MA_SUPPORT_SNDIO) + #define MA_ENABLE_SNDIO +#endif +#if !defined(MA_NO_AUDIO4) && defined(MA_SUPPORT_AUDIO4) + #define MA_ENABLE_AUDIO4 +#endif +#if !defined(MA_NO_OSS) && defined(MA_SUPPORT_OSS) + #define MA_ENABLE_OSS +#endif +#if !defined(MA_NO_AAUDIO) && defined(MA_SUPPORT_AAUDIO) + #define MA_ENABLE_AAUDIO +#endif +#if !defined(MA_NO_OPENSL) && defined(MA_SUPPORT_OPENSL) + #define MA_ENABLE_OPENSL +#endif +#if !defined(MA_NO_WEBAUDIO) && defined(MA_SUPPORT_WEBAUDIO) + #define MA_ENABLE_WEBAUDIO +#endif +#if !defined(MA_NO_NULL) && defined(MA_SUPPORT_NULL) + #define MA_ENABLE_NULL +#endif + +#ifdef MA_SUPPORT_WASAPI +/* We need a IMMNotificationClient object for WASAPI. */ +typedef struct +{ + void* lpVtbl; + ma_uint32 counter; + ma_device* pDevice; +} ma_IMMNotificationClient; +#endif + +/* Backend enums must be in priority order. */ +typedef enum +{ + ma_backend_wasapi, + ma_backend_dsound, + ma_backend_winmm, + ma_backend_coreaudio, + ma_backend_sndio, + ma_backend_audio4, + ma_backend_oss, + ma_backend_pulseaudio, + ma_backend_alsa, + ma_backend_jack, + 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; + +/* Thread priorties should be ordered such that the default priority of the worker thread is 0. */ +typedef enum +{ + ma_thread_priority_idle = -5, + ma_thread_priority_lowest = -4, + ma_thread_priority_low = -3, + ma_thread_priority_normal = -2, + ma_thread_priority_high = -1, + ma_thread_priority_highest = 0, + ma_thread_priority_realtime = 1, + ma_thread_priority_default = 0 +} ma_thread_priority; + +typedef struct +{ + ma_context* pContext; + + union + { +#ifdef MA_WIN32 + struct + { + /*HANDLE*/ ma_handle hThread; + } win32; +#endif +#ifdef MA_POSIX + struct + { + pthread_t thread; + } posix; +#endif + int _unused; + }; +} ma_thread; + +typedef struct +{ + ma_context* pContext; + + union + { +#ifdef MA_WIN32 + struct + { + /*HANDLE*/ ma_handle hMutex; + } win32; +#endif +#ifdef MA_POSIX + struct + { + pthread_mutex_t mutex; + } posix; +#endif + int _unused; + }; +} ma_mutex; + +typedef struct +{ + ma_context* pContext; + + union + { +#ifdef MA_WIN32 + struct + { + /*HANDLE*/ ma_handle hEvent; + } win32; +#endif +#ifdef MA_POSIX + struct + { + pthread_mutex_t mutex; + pthread_cond_t condition; + ma_uint32 value; + } posix; +#endif + int _unused; + }; +} ma_event; + + +/* +The callback for processing audio data from the device. + +pOutput is a pointer to a buffer that will receive audio data that will later be played back through the speakers. This will be non-null +for a playback or full-duplex device and null for a capture device. + +pInput is a pointer to a buffer containing input data from the device. This will be non-null for a capture or full-duplex device, and +null for a playback device. + +frameCount is the number of PCM frames to process. If an output buffer is provided (pOutput is not null), applications should write out +to the entire output buffer. Note that frameCount will not necessarily be exactly what you asked for when you initialized the deviced. +The bufferSizeInFrames and bufferSizeInMilliseconds members of the device config are just hints, and are not necessarily exactly what +you'll get. + +Do _not_ call any miniaudio APIs from the callback. Attempting the stop the device can result in a deadlock. The proper way to stop the +device is to call ma_device_stop() from a different thread, normally the main application thread. +*/ +typedef void (* ma_device_callback_proc)(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); + +/* +The callback for when the device has been stopped. + +This will be called when the device is stopped explicitly with ma_device_stop() and also called implicitly when the device is stopped +through external forces such as being unplugged or an internal error occuring. + +Do not restart the device from the callback. +*/ +typedef void (* ma_stop_proc)(ma_device* pDevice); + +/* +The callback for handling log messages. + +It is possible for pDevice to be null in which case the log originated from the context. If it is non-null you can assume the message +came from the device. + +logLevel is one of the following: + MA_LOG_LEVEL_VERBOSE + MA_LOG_LEVEL_INFO + MA_LOG_LEVEL_WARNING + MA_LOG_LEVEL_ERROR +*/ +typedef void (* ma_log_proc)(ma_context* pContext, ma_device* pDevice, ma_uint32 logLevel, const char* message); + +typedef enum +{ + ma_device_type_playback = 1, + ma_device_type_capture = 2, + ma_device_type_duplex = ma_device_type_playback | ma_device_type_capture, +} ma_device_type; + +typedef enum +{ + ma_share_mode_shared = 0, + ma_share_mode_exclusive, +} ma_share_mode; + +typedef union +{ +#ifdef MA_SUPPORT_WASAPI + wchar_t wasapi[64]; /* WASAPI uses a wchar_t string for identification. */ +#endif +#ifdef MA_SUPPORT_DSOUND + ma_uint8 dsound[16]; /* DirectSound uses a GUID for identification. */ +#endif +#ifdef MA_SUPPORT_WINMM + /*UINT_PTR*/ ma_uint32 winmm; /* When creating a device, WinMM expects a Win32 UINT_PTR for device identification. In practice it's actually just a UINT. */ +#endif +#ifdef MA_SUPPORT_ALSA + char alsa[256]; /* ALSA uses a name string for identification. */ +#endif +#ifdef MA_SUPPORT_PULSEAUDIO + char pulse[256]; /* PulseAudio uses a name string for identification. */ +#endif +#ifdef MA_SUPPORT_JACK + int jack; /* JACK always uses default devices. */ +#endif +#ifdef MA_SUPPORT_COREAUDIO + char coreaudio[256]; /* Core Audio uses a string for identification. */ +#endif +#ifdef MA_SUPPORT_SNDIO + char sndio[256]; /* "snd/0", etc. */ +#endif +#ifdef MA_SUPPORT_AUDIO4 + char audio4[256]; /* "/dev/audio", etc. */ +#endif +#ifdef MA_SUPPORT_OSS + char oss[64]; /* "dev/dsp0", etc. "dev/dsp" for the default device. */ +#endif +#ifdef MA_SUPPORT_AAUDIO + ma_int32 aaudio; /* AAudio uses a 32-bit integer for identification. */ +#endif +#ifdef MA_SUPPORT_OPENSL + ma_uint32 opensl; /* OpenSL|ES uses a 32-bit unsigned integer for identification. */ +#endif +#ifdef MA_SUPPORT_WEBAUDIO + char webaudio[32]; /* Web Audio always uses default devices for now, but if this changes it'll be a GUID. */ +#endif +#ifdef MA_SUPPORT_NULL + int nullbackend; /* The null backend uses an integer for device IDs. */ +#endif +} ma_device_id; + +typedef struct +{ + /* Basic info. This is the only information guaranteed to be filled in during device enumeration. */ + ma_device_id id; + char name[256]; + + /* + 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; + ma_format formats[ma_format_count]; + ma_uint32 minChannels; + ma_uint32 maxChannels; + ma_uint32 minSampleRate; + ma_uint32 maxSampleRate; +} ma_device_info; + +typedef union +{ + ma_int64 counter; + double counterD; +} ma_timer; + +typedef struct +{ + ma_device_type deviceType; + ma_uint32 sampleRate; + ma_uint32 bufferSizeInFrames; + ma_uint32 bufferSizeInMilliseconds; + ma_uint32 periods; + ma_performance_profile performanceProfile; + ma_device_callback_proc dataCallback; + ma_stop_proc stopCallback; + void* pUserData; + struct + { + ma_device_id* pDeviceID; + ma_format format; + ma_uint32 channels; + ma_channel channelMap[MA_MAX_CHANNELS]; + ma_share_mode shareMode; + } playback; + struct + { + ma_device_id* pDeviceID; + ma_format format; + ma_uint32 channels; + ma_channel channelMap[MA_MAX_CHANNELS]; + ma_share_mode shareMode; + } capture; + + struct + { + ma_bool32 noMMap; /* Disables MMap mode. */ + } alsa; + struct + { + const char* pStreamNamePlayback; + const char* pStreamNameCapture; + } pulse; +} ma_device_config; + +typedef struct +{ + ma_log_proc logCallback; + ma_thread_priority threadPriority; + void* pUserData; + + struct + { + ma_bool32 useVerboseDeviceEnumeration; + } alsa; + struct + { + const char* pApplicationName; + const char* pServerName; + ma_bool32 tryAutoSpawn; /* Enables autospawning of the PulseAudio daemon if necessary. */ + } pulse; + struct + { + const char* pClientName; + ma_bool32 tryStartServer; + } jack; +} ma_context_config; + +typedef ma_bool32 (* ma_enum_devices_callback_proc)(ma_context* pContext, ma_device_type deviceType, const ma_device_info* pInfo, void* pUserData); + +struct ma_context +{ + ma_backend backend; /* DirectSound, ALSA, etc. */ + ma_log_proc logCallback; + ma_thread_priority threadPriority; + void* pUserData; + 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 (* onDeviceWrite )(ma_device* pDevice, const void* pPCMFrames, ma_uint32 frameCount); /* Data is in internal device format. */ + ma_result (* onDeviceRead )(ma_device* pDevice, void* pPCMFrames, ma_uint32 frameCount); /* Data is in internal device format. */ + ma_result (* onDeviceMainLoop)(ma_device* pDevice); + + union + { +#ifdef MA_SUPPORT_WASAPI + struct + { + int _unused; + } wasapi; +#endif +#ifdef MA_SUPPORT_DSOUND + struct + { + ma_handle hDSoundDLL; + ma_proc DirectSoundCreate; + ma_proc DirectSoundEnumerateA; + ma_proc DirectSoundCaptureCreate; + ma_proc DirectSoundCaptureEnumerateA; + } dsound; +#endif +#ifdef MA_SUPPORT_WINMM + struct + { + ma_handle hWinMM; + ma_proc waveOutGetNumDevs; + ma_proc waveOutGetDevCapsA; + ma_proc waveOutOpen; + ma_proc waveOutClose; + ma_proc waveOutPrepareHeader; + ma_proc waveOutUnprepareHeader; + ma_proc waveOutWrite; + ma_proc waveOutReset; + ma_proc waveInGetNumDevs; + ma_proc waveInGetDevCapsA; + ma_proc waveInOpen; + ma_proc waveInClose; + ma_proc waveInPrepareHeader; + ma_proc waveInUnprepareHeader; + ma_proc waveInAddBuffer; + ma_proc waveInStart; + ma_proc waveInReset; + } winmm; +#endif +#ifdef MA_SUPPORT_ALSA + struct + { + ma_handle asoundSO; + ma_proc snd_pcm_open; + ma_proc snd_pcm_close; + ma_proc snd_pcm_hw_params_sizeof; + ma_proc snd_pcm_hw_params_any; + 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_near; + ma_proc snd_pcm_hw_params_set_rate_resample; + 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; + ma_proc snd_pcm_hw_params_set_access; + ma_proc snd_pcm_hw_params_get_format; + ma_proc snd_pcm_hw_params_get_channels; + ma_proc snd_pcm_hw_params_get_channels_min; + ma_proc snd_pcm_hw_params_get_channels_max; + ma_proc snd_pcm_hw_params_get_rate; + ma_proc snd_pcm_hw_params_get_rate_min; + ma_proc snd_pcm_hw_params_get_rate_max; + 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; + ma_proc snd_pcm_sw_params_sizeof; + ma_proc snd_pcm_sw_params_current; + ma_proc snd_pcm_sw_params_get_boundary; + ma_proc snd_pcm_sw_params_set_avail_min; + ma_proc snd_pcm_sw_params_set_start_threshold; + ma_proc snd_pcm_sw_params_set_stop_threshold; + ma_proc snd_pcm_sw_params; + ma_proc snd_pcm_format_mask_sizeof; + ma_proc snd_pcm_format_mask_test; + ma_proc snd_pcm_get_chmap; + ma_proc snd_pcm_state; + ma_proc snd_pcm_prepare; + ma_proc snd_pcm_start; + ma_proc snd_pcm_drop; + ma_proc snd_pcm_drain; + ma_proc snd_device_name_hint; + ma_proc snd_device_name_get_hint; + ma_proc snd_card_get_index; + ma_proc snd_device_name_free_hint; + ma_proc snd_pcm_mmap_begin; + ma_proc snd_pcm_mmap_commit; + ma_proc snd_pcm_recover; + ma_proc snd_pcm_readi; + ma_proc snd_pcm_writei; + ma_proc snd_pcm_avail; + ma_proc snd_pcm_avail_update; + ma_proc snd_pcm_wait; + ma_proc snd_pcm_info; + ma_proc snd_pcm_info_sizeof; + ma_proc snd_pcm_info_get_name; + ma_proc snd_config_update_free_global; + + ma_mutex internalDeviceEnumLock; + ma_bool32 useVerboseDeviceEnumeration; + } alsa; +#endif +#ifdef MA_SUPPORT_PULSEAUDIO + struct + { + ma_handle pulseSO; + ma_proc pa_mainloop_new; + ma_proc pa_mainloop_free; + ma_proc pa_mainloop_get_api; + ma_proc pa_mainloop_iterate; + ma_proc pa_mainloop_wakeup; + ma_proc pa_context_new; + ma_proc pa_context_unref; + ma_proc pa_context_connect; + ma_proc pa_context_disconnect; + ma_proc pa_context_set_state_callback; + ma_proc pa_context_get_state; + ma_proc pa_context_get_sink_info_list; + ma_proc pa_context_get_source_info_list; + ma_proc pa_context_get_sink_info_by_name; + ma_proc pa_context_get_source_info_by_name; + ma_proc pa_operation_unref; + ma_proc pa_operation_get_state; + ma_proc pa_channel_map_init_extend; + ma_proc pa_channel_map_valid; + ma_proc pa_channel_map_compatible; + ma_proc pa_stream_new; + ma_proc pa_stream_unref; + ma_proc pa_stream_connect_playback; + ma_proc pa_stream_connect_record; + ma_proc pa_stream_disconnect; + ma_proc pa_stream_get_state; + ma_proc pa_stream_get_sample_spec; + ma_proc pa_stream_get_channel_map; + ma_proc pa_stream_get_buffer_attr; + ma_proc pa_stream_set_buffer_attr; + ma_proc pa_stream_get_device_name; + ma_proc pa_stream_set_write_callback; + ma_proc pa_stream_set_read_callback; + ma_proc pa_stream_flush; + ma_proc pa_stream_drain; + ma_proc pa_stream_is_corked; + ma_proc pa_stream_cork; + ma_proc pa_stream_trigger; + ma_proc pa_stream_begin_write; + ma_proc pa_stream_write; + ma_proc pa_stream_peek; + ma_proc pa_stream_drop; + ma_proc pa_stream_writable_size; + ma_proc pa_stream_readable_size; + + char* pApplicationName; + char* pServerName; + ma_bool32 tryAutoSpawn; + } pulse; +#endif +#ifdef MA_SUPPORT_JACK + struct + { + ma_handle jackSO; + ma_proc jack_client_open; + ma_proc jack_client_close; + ma_proc jack_client_name_size; + ma_proc jack_set_process_callback; + ma_proc jack_set_buffer_size_callback; + ma_proc jack_on_shutdown; + ma_proc jack_get_sample_rate; + ma_proc jack_get_buffer_size; + ma_proc jack_get_ports; + ma_proc jack_activate; + ma_proc jack_deactivate; + ma_proc jack_connect; + ma_proc jack_port_register; + ma_proc jack_port_name; + ma_proc jack_port_get_buffer; + ma_proc jack_free; + + char* pClientName; + ma_bool32 tryStartServer; + } jack; +#endif +#ifdef MA_SUPPORT_COREAUDIO + struct + { + ma_handle hCoreFoundation; + ma_proc CFStringGetCString; + + ma_handle hCoreAudio; + ma_proc AudioObjectGetPropertyData; + ma_proc AudioObjectGetPropertyDataSize; + ma_proc AudioObjectSetPropertyData; + ma_proc AudioObjectAddPropertyListener; + + ma_handle hAudioUnit; /* Could possibly be set to AudioToolbox on later versions of macOS. */ + ma_proc AudioComponentFindNext; + ma_proc AudioComponentInstanceDispose; + ma_proc AudioComponentInstanceNew; + ma_proc AudioOutputUnitStart; + ma_proc AudioOutputUnitStop; + ma_proc AudioUnitAddPropertyListener; + ma_proc AudioUnitGetPropertyInfo; + ma_proc AudioUnitGetProperty; + ma_proc AudioUnitSetProperty; + ma_proc AudioUnitInitialize; + ma_proc AudioUnitRender; + + /*AudioComponent*/ ma_ptr component; + } coreaudio; +#endif +#ifdef MA_SUPPORT_SNDIO + struct + { + ma_handle sndioSO; + ma_proc sio_open; + ma_proc sio_close; + ma_proc sio_setpar; + ma_proc sio_getpar; + ma_proc sio_getcap; + ma_proc sio_start; + ma_proc sio_stop; + ma_proc sio_read; + ma_proc sio_write; + ma_proc sio_onmove; + ma_proc sio_nfds; + ma_proc sio_pollfd; + ma_proc sio_revents; + ma_proc sio_eof; + ma_proc sio_setvol; + ma_proc sio_onvol; + ma_proc sio_initpar; + } sndio; +#endif +#ifdef MA_SUPPORT_AUDIO4 + struct + { + int _unused; + } audio4; +#endif +#ifdef MA_SUPPORT_OSS + struct + { + int versionMajor; + int versionMinor; + } oss; +#endif +#ifdef MA_SUPPORT_AAUDIO + struct + { + ma_handle hAAudio; /* libaaudio.so */ + ma_proc AAudio_createStreamBuilder; + ma_proc AAudioStreamBuilder_delete; + ma_proc AAudioStreamBuilder_setDeviceId; + ma_proc AAudioStreamBuilder_setDirection; + ma_proc AAudioStreamBuilder_setSharingMode; + ma_proc AAudioStreamBuilder_setFormat; + ma_proc AAudioStreamBuilder_setChannelCount; + ma_proc AAudioStreamBuilder_setSampleRate; + ma_proc AAudioStreamBuilder_setBufferCapacityInFrames; + ma_proc AAudioStreamBuilder_setFramesPerDataCallback; + ma_proc AAudioStreamBuilder_setDataCallback; + ma_proc AAudioStreamBuilder_setPerformanceMode; + ma_proc AAudioStreamBuilder_openStream; + ma_proc AAudioStream_close; + ma_proc AAudioStream_getState; + ma_proc AAudioStream_waitForStateChange; + ma_proc AAudioStream_getFormat; + ma_proc AAudioStream_getChannelCount; + ma_proc AAudioStream_getSampleRate; + ma_proc AAudioStream_getBufferCapacityInFrames; + ma_proc AAudioStream_getFramesPerDataCallback; + ma_proc AAudioStream_getFramesPerBurst; + ma_proc AAudioStream_requestStart; + ma_proc AAudioStream_requestStop; + } aaudio; +#endif +#ifdef MA_SUPPORT_OPENSL + struct + { + int _unused; + } opensl; +#endif +#ifdef MA_SUPPORT_WEBAUDIO + struct + { + int _unused; + } webaudio; +#endif +#ifdef MA_SUPPORT_NULL + struct + { + int _unused; + } null_backend; +#endif + }; + + union + { +#ifdef MA_WIN32 + struct + { + /*HMODULE*/ ma_handle hOle32DLL; + ma_proc CoInitializeEx; + ma_proc CoUninitialize; + ma_proc CoCreateInstance; + ma_proc CoTaskMemFree; + ma_proc PropVariantClear; + ma_proc StringFromGUID2; + + /*HMODULE*/ ma_handle hUser32DLL; + ma_proc GetForegroundWindow; + ma_proc GetDesktopWindow; + + /*HMODULE*/ ma_handle hAdvapi32DLL; + ma_proc RegOpenKeyExA; + ma_proc RegCloseKey; + ma_proc RegQueryValueExA; + } win32; +#endif +#ifdef MA_POSIX + struct + { + ma_handle pthreadSO; + ma_proc pthread_create; + ma_proc pthread_join; + ma_proc pthread_mutex_init; + ma_proc pthread_mutex_destroy; + ma_proc pthread_mutex_lock; + ma_proc pthread_mutex_unlock; + ma_proc pthread_cond_init; + ma_proc pthread_cond_destroy; + ma_proc pthread_cond_wait; + ma_proc pthread_cond_signal; + ma_proc pthread_attr_init; + ma_proc pthread_attr_destroy; + ma_proc pthread_attr_setschedpolicy; + ma_proc pthread_attr_getschedparam; + ma_proc pthread_attr_setschedparam; + } posix; +#endif + int _unused; + }; +}; + +MA_ALIGNED_STRUCT(MA_SIMD_ALIGNMENT) ma_device +{ + ma_context* pContext; + ma_device_type type; + ma_uint32 sampleRate; + ma_uint32 state; + ma_device_callback_proc onData; + ma_stop_proc onStop; + void* pUserData; /* Application defined data. */ + ma_mutex lock; + 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(). */ + struct + { + 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]; + ma_format internalFormat; + ma_uint32 internalChannels; + ma_uint32 internalSampleRate; + ma_channel internalChannelMap[MA_MAX_CHANNELS]; + ma_uint32 internalBufferSizeInFrames; + ma_uint32 internalPeriods; + ma_pcm_converter converter; + ma_uint32 _dspFrameCount; /* Internal use only. Used as the data source when reading from the device. */ + const ma_uint8* _dspFrames; /* ^^^ AS ABOVE ^^^ */ + } playback; + struct + { + 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]; + ma_format internalFormat; + ma_uint32 internalChannels; + ma_uint32 internalSampleRate; + ma_channel internalChannelMap[MA_MAX_CHANNELS]; + ma_uint32 internalBufferSizeInFrames; + ma_uint32 internalPeriods; + ma_pcm_converter converter; + ma_uint32 _dspFrameCount; /* Internal use only. Used as the data source when reading from the device. */ + const ma_uint8* _dspFrames; /* ^^^ AS ABOVE ^^^ */ + } capture; + + union + { +#ifdef MA_SUPPORT_WASAPI + struct + { + /*IAudioClient**/ ma_ptr pAudioClientPlayback; + /*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. */ + ma_IMMNotificationClient notificationClient; + /*HANDLE*/ ma_handle hEventPlayback; /* Auto reset. Initialized to signaled. */ + /*HANDLE*/ ma_handle hEventCapture; /* Auto reset. Initialized to unsignaled. */ + ma_uint32 actualBufferSizeInFramesPlayback; /* Value from GetBufferSize(). internalBufferSizeInFrames 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 actualBufferSizeInFramesCapture; + ma_uint32 originalBufferSizeInFrames; + ma_uint32 originalBufferSizeInMilliseconds; + 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_uint32 periodSizeInFramesPlayback; + ma_uint32 periodSizeInFramesCapture; + ma_bool32 isStartedCapture; + ma_bool32 isStartedPlayback; + } wasapi; +#endif +#ifdef MA_SUPPORT_DSOUND + struct + { + /*LPDIRECTSOUND*/ ma_ptr pPlayback; + /*LPDIRECTSOUNDBUFFER*/ ma_ptr pPlaybackPrimaryBuffer; + /*LPDIRECTSOUNDBUFFER*/ ma_ptr pPlaybackBuffer; + /*LPDIRECTSOUNDCAPTURE*/ ma_ptr pCapture; + /*LPDIRECTSOUNDCAPTUREBUFFER*/ ma_ptr pCaptureBuffer; + } dsound; +#endif +#ifdef MA_SUPPORT_WINMM + struct + { + /*HWAVEOUT*/ ma_handle hDevicePlayback; + /*HWAVEIN*/ ma_handle hDeviceCapture; + /*HANDLE*/ ma_handle hEventPlayback; + /*HANDLE*/ ma_handle hEventCapture; + ma_uint32 fragmentSizeInFrames; + ma_uint32 fragmentSizeInBytes; + ma_uint32 iNextHeaderPlayback; /* [0,periods). Used as an index into pWAVEHDRPlayback. */ + ma_uint32 iNextHeaderCapture; /* [0,periods). Used as an index into pWAVEHDRCapture. */ + ma_uint32 headerFramesConsumedPlayback; /* The number of PCM frames consumed in the buffer in pWAVEHEADER[iNextHeader]. */ + ma_uint32 headerFramesConsumedCapture; /* ^^^ */ + /*WAVEHDR**/ ma_uint8* pWAVEHDRPlayback; /* One instantiation for each period. */ + /*WAVEHDR**/ ma_uint8* pWAVEHDRCapture; /* One instantiation for each period. */ + ma_uint8* pIntermediaryBufferPlayback; + ma_uint8* pIntermediaryBufferCapture; + ma_uint8* _pHeapData; /* Used internally and is used for the heap allocated data for the intermediary buffer and the WAVEHDR structures. */ + ma_bool32 isStarted; + } winmm; +#endif +#ifdef MA_SUPPORT_ALSA + struct + { + /*snd_pcm_t**/ ma_ptr pPCMPlayback; + /*snd_pcm_t**/ ma_ptr pPCMCapture; + ma_bool32 isUsingMMapPlayback : 1; + ma_bool32 isUsingMMapCapture : 1; + } 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 + struct + { + /*jack_client_t**/ ma_ptr pClient; + /*jack_port_t**/ ma_ptr pPortsPlayback[MA_MAX_CHANNELS]; + /*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 + struct + { + ma_uint32 deviceObjectIDPlayback; + ma_uint32 deviceObjectIDCapture; + /*AudioUnit*/ ma_ptr audioUnitPlayback; + /*AudioUnit*/ ma_ptr audioUnitCapture; + /*AudioBufferList**/ ma_ptr pAudioBufferList; /* Only used for input devices. */ + ma_event stopEvent; + ma_uint32 originalBufferSizeInFrames; + ma_uint32 originalBufferSizeInMilliseconds; + ma_uint32 originalPeriods; + 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; + } coreaudio; +#endif +#ifdef MA_SUPPORT_SNDIO + struct + { + ma_ptr handlePlayback; + ma_ptr handleCapture; + ma_bool32 isStartedPlayback; + ma_bool32 isStartedCapture; + } sndio; +#endif +#ifdef MA_SUPPORT_AUDIO4 + struct + { + int fdPlayback; + int fdCapture; + } audio4; +#endif +#ifdef MA_SUPPORT_OSS + struct + { + int fdPlayback; + int fdCapture; + } oss; +#endif +#ifdef MA_SUPPORT_AAUDIO + struct + { + /*AAudioStream**/ ma_ptr pStreamPlayback; + /*AAudioStream**/ ma_ptr pStreamCapture; + ma_pcm_rb duplexRB; + } aaudio; +#endif +#ifdef MA_SUPPORT_OPENSL + struct + { + /*SLObjectItf*/ ma_ptr pOutputMixObj; + /*SLOutputMixItf*/ ma_ptr pOutputMix; + /*SLObjectItf*/ ma_ptr pAudioPlayerObj; + /*SLPlayItf*/ ma_ptr pAudioPlayer; + /*SLObjectItf*/ ma_ptr pAudioRecorderObj; + /*SLRecordItf*/ ma_ptr pAudioRecorder; + /*SLAndroidSimpleBufferQueueItf*/ ma_ptr pBufferQueuePlayback; + /*SLAndroidSimpleBufferQueueItf*/ ma_ptr pBufferQueueCapture; + ma_uint32 currentBufferIndexPlayback; + 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 + struct + { + 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 + struct + { + ma_thread deviceThread; + ma_event operationEvent; + ma_event operationCompletionEvent; + ma_uint32 operation; + ma_result operationResult; + ma_timer timer; + double priorRunTime; + ma_uint32 currentPeriodFramesRemainingPlayback; + ma_uint32 currentPeriodFramesRemainingCapture; + ma_uint64 lastProcessedFramePlayback; + ma_uint32 lastProcessedFrameCapture; + ma_bool32 isStarted; + } null_device; +#endif + }; +}; +#if defined(_MSC_VER) + #pragma warning(pop) +#else + #pragma GCC diagnostic pop /* For ISO C99 doesn't support unnamed structs/unions [-Wpedantic] */ +#endif + +/* +Initializes a context. + +The context is used for selecting and initializing the relevant backends. + +Note that the location of the context cannot change throughout it's lifetime. Consider allocating +the ma_context object with malloc() if this is an issue. The reason for this is that a pointer +to the context is stored in the ma_device structure. + + is used to allow the application to prioritize backends depending on it's specific +requirements. This can be null in which case it uses the default priority, which is as follows: + - WASAPI + - DirectSound + - WinMM + - Core Audio (Apple) + - sndio + - audio(4) + - OSS + - PulseAudio + - ALSA + - JACK + - AAudio + - OpenSL|ES + - Web Audio / Emscripten + - Null + + is used to configure the context. Use the logCallback config to set a callback for whenever a +log message is posted. The priority of the worker thread can be set with the threadPriority config. + +It is recommended that only a single context is active at any given time because it's a bulky data +structure which performs run-time linking for the relevant backends every time it's initialized. + +Return Value: + MA_SUCCESS if successful; any other error code otherwise. + +Thread Safety: UNSAFE +*/ +ma_result ma_context_init(const ma_backend backends[], ma_uint32 backendCount, const ma_context_config* pConfig, ma_context* pContext); + +/* +Uninitializes a context. + +Results are undefined if you call this while any device created by this context is still active. + +Return Value: + MA_SUCCESS if successful; any other error code otherwise. + +Thread Safety: UNSAFE +*/ +ma_result ma_context_uninit(ma_context* pContext); + +/* +Enumerates over every device (both playback and capture). + +This is a lower-level enumeration function to the easier to use ma_context_get_devices(). Use +ma_context_enumerate_devices() if you would rather not incur an internal heap allocation, or +it simply suits your code better. + +Do _not_ assume the first enumerated device of a given type is the default device. + +Some backends and platforms may only support default playback and capture devices. + +Note that this only retrieves the ID and name/description of the device. The reason for only +retrieving basic information is that it would otherwise require opening the backend device in +order to probe it for more detailed information which can be inefficient. Consider using +ma_context_get_device_info() for this, but don't call it from within the enumeration callback. + +In general, you should not do anything complicated from within the callback. In particular, do +not try initializing a device from within the callback. + +Consider using ma_context_get_devices() for a simpler and safer API, albeit at the expense of +an internal heap allocation. + +Returning false from the callback will stop enumeration. Returning true will continue enumeration. + +Return Value: + MA_SUCCESS if successful; any other error code otherwise. + +Thread Safety: SAFE + This is guarded using a simple mutex lock. +*/ +ma_result ma_context_enumerate_devices(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData); + +/* +Retrieves basic information about every active playback and/or capture device. + +You can pass in NULL for the playback or capture lists in which case they'll be ignored. + +It is _not_ safe to assume the first device in the list is the default device. + +The returned pointers will become invalid upon the next call this this function, or when the +context is uninitialized. Do not free the returned pointers. + +This function follows the same enumeration rules as ma_context_enumerate_devices(). See +documentation for ma_context_enumerate_devices() for more information. + +Return Value: + MA_SUCCESS if successful; any other error code otherwise. + +Thread Safety: SAFE + Since each call to this function invalidates the pointers from the previous call, you + should not be calling this simultaneously across multiple threads. Instead, you need to + make a copy of the returned data with your own higher level synchronization. +*/ +ma_result ma_context_get_devices(ma_context* pContext, ma_device_info** ppPlaybackDeviceInfos, ma_uint32* pPlaybackDeviceCount, ma_device_info** ppCaptureDeviceInfos, ma_uint32* pCaptureDeviceCount); + +/* +Retrieves information about a device with the given ID. + +Do _not_ call this from within the ma_context_enumerate_devices() callback. + +It's possible for a device to have different information and capabilities depending on whether +or not it's opened in shared or exclusive mode. For example, in shared mode, WASAPI always uses +floating point samples for mixing, but in exclusive mode it can be anything. Therefore, this +function allows you to specify which share mode you want information for. Note that not all +backends and devices support shared or exclusive mode, in which case this function will fail +if the requested share mode is unsupported. + +This leaves pDeviceInfo unmodified in the result of an error. + +Return Value: + MA_SUCCESS if successful; any other error code otherwise. + +Thread Safety: SAFE + This is guarded using a simple mutex lock. +*/ +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); + +/* +Initializes a device. + +The context can be null in which case it uses the default. This is equivalent to passing in a +context that was initialized like so: + + ma_context_init(NULL, 0, NULL, &context); + +Do not pass in null for the context if you are needing to open multiple devices. You can, +however, use null when initializing the first device, and then use device.pContext for the +initialization of other devices. + +The device's configuration is controlled with pConfig. This allows you to configure the sample +format, channel count, sample rate, etc. Before calling ma_device_init(), you will need to +initialize a ma_device_config object using ma_device_config_init(). You must set the callback in +the device config. Once initialized, the device's config is immutable. If you need to change the +config you will need to initialize a new device. + +Passing in 0 to any property in pConfig will force the use of a default value. In the case of +sample format, channel count, sample rate and channel map it will default to the values used by +the backend's internal device. For the size of the buffer you can set bufferSizeInFrames or +bufferSizeInMilliseconds (if both are set it will prioritize bufferSizeInFrames). If both are +set to zero, it will default to MA_BASE_BUFFER_SIZE_IN_MILLISECONDS_LOW_LATENCY or +MA_BASE_BUFFER_SIZE_IN_MILLISECONDS_CONSERVATIVE, depending on whether or not performanceProfile +is set to ma_performance_profile_low_latency or ma_performance_profile_conservative. + +If you request exclusive mode and the backend does not support it an error will be returned. For +robustness, you may want to first try initializing the device in exclusive mode, and then fall back +to shared mode if required. Alternatively you can just request shared mode (the default if you +leave it unset in the config) which is the most reliable option. Some backends do not have a +practical way of choosing whether or not the device should be exclusive or not (ALSA, for example) +in which case it just acts as a hint. Unless you have special requirements you should try avoiding +exclusive mode as it's intrusive to the user. Starting with Windows 10, miniaudio will use low-latency +shared mode where possible which may make exclusive mode unnecessary. + +When sending or receiving data to/from a device, miniaudio will internally perform a format +conversion to convert between the format specified by pConfig and the format used internally by +the backend. If you pass in NULL for pConfig or 0 for the sample format, channel count, +sample rate _and_ channel map, data transmission will run on an optimized pass-through fast path. + +The buffer size should be treated as a hint. miniaudio will try it's best to use exactly what you +ask for, but it may differ. You should not assume the number of frames specified in each call to +the data callback is exactly what you originally specified. + +The property controls how frequently the background thread is woken to check for more +data. It's tied to the buffer size, so as an example, if your buffer size is equivalent to 10 +milliseconds and you have 2 periods, the CPU will wake up approximately every 5 milliseconds. + +When compiling for UWP you must ensure you call this function on the main UI thread because the +operating system may need to present the user with a message asking for permissions. Please refer +to the official documentation for ActivateAudioInterfaceAsync() for more information. + +ALSA Specific: When initializing the default device, requesting shared mode will try using the +"dmix" device for playback and the "dsnoop" device for capture. If these fail it will try falling +back to the "hw" device. + +Return Value: + MA_SUCCESS if successful; any other error code otherwise. + +Thread Safety: UNSAFE + It is not safe to call this function simultaneously for different devices because some backends + depend on and mutate global state (such as OpenSL|ES). The same applies to calling this at the + same time as ma_device_uninit(). +*/ +ma_result ma_device_init(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice); + +/* +Initializes a device without a context, with extra parameters for controlling the configuration +of the internal self-managed context. + +See ma_device_init() and ma_context_init(). +*/ +ma_result ma_device_init_ex(const ma_backend backends[], ma_uint32 backendCount, const ma_context_config* pContextConfig, const ma_device_config* pConfig, ma_device* pDevice); + +/* +Uninitializes a device. + +This will explicitly stop the device. You do not need to call ma_device_stop() beforehand, but it's +harmless if you do. + +Do not call this in any callback. + +Return Value: + MA_SUCCESS if successful; any other error code otherwise. + +Thread Safety: UNSAFE + As soon as this API is called the device should be considered undefined. All bets are off if you + try using the device at the same time as uninitializing it. +*/ +void ma_device_uninit(ma_device* pDevice); + +/* +Sets the callback to use when the device has stopped, either explicitly or as a result of an error. + +Thread Safety: SAFE + This API is implemented as a simple atomic assignment. +*/ +void ma_device_set_stop_callback(ma_device* pDevice, ma_stop_proc proc); + +/* +Activates the device. For playback devices this begins playback. For capture devices it begins +recording. + +For a playback device, this will retrieve an initial chunk of audio data from the client before +returning. The reason for this is to ensure there is valid audio data in the buffer, which needs +to be done _before_ the device begins playback. + +This API waits until the backend device has been started for real by the worker thread. It also +waits on a mutex for thread-safety. + +Do not call this in any callback. + +Return Value: + MA_SUCCESS if successful; any other error code otherwise. + +Thread Safety: SAFE +*/ +ma_result ma_device_start(ma_device* pDevice); + +/* +Puts the device to sleep, but does not uninitialize it. Use ma_device_start() to start it up again. + +This API needs to wait on the worker thread to stop the backend device properly before returning. It +also waits on a mutex for thread-safety. In addition, some backends need to wait for the device to +finish playback/recording of the current fragment which can take some time (usually proportionate to +the buffer size that was specified at initialization time). + +This should not drop unprocessed samples. Backends are required to either pause the stream in-place +or drain the buffer if pausing is not possible. The reason for this is that stopping the device and +the resuming it with ma_device_start() (which you might do when your program loses focus) may result +in a situation where those samples are never output to the speakers or received from the microphone +which can in turn result in de-syncs. + +Do not call this in any callback. + +Return Value: + MA_SUCCESS if successful; any other error code otherwise. + +Thread Safety: SAFE +*/ +ma_result ma_device_stop(ma_device* pDevice); + +/* +Determines whether or not the device is started. + +This is implemented as a simple accessor. + +Return Value: + True if the device is started, false otherwise. + +Thread Safety: SAFE + If another thread calls ma_device_start() or ma_device_stop() at this same time as this function + is called, there's a very small chance the return value will be out of sync. +*/ +ma_bool32 ma_device_is_started(ma_device* pDevice); + + +/* +Helper function for initializing a ma_context_config object. +*/ +ma_context_config ma_context_config_init(void); + +/* +Initializes a device config. + +By default, the device config will use native device settings (format, channels, sample rate, etc.). Using native +settings means you will get an optimized pass-through data transmission pipeline to and from the device, but you will +need to do all format conversions manually. Normally you would want to use a known format that your program can handle +natively, which you can do by specifying it after this function returns, like so: + + ma_device_config config = ma_device_config_init(ma_device_type_playback); + config.callback = my_data_callback; + config.pUserData = pMyUserData; + config.format = ma_format_f32; + config.channels = 2; + config.sampleRate = 44100; + +In this case miniaudio will perform all of the necessary data conversion for you behind the scenes. + +Currently miniaudio only supports asynchronous, callback based data delivery which means you must specify callback. A +pointer to user data can also be specified which is set in the pUserData member of the ma_device object. + +To specify a channel map you can use ma_get_standard_channel_map(): + + ma_get_standard_channel_map(ma_standard_channel_map_default, config.channels, config.channelMap); + +Alternatively you can set the channel map manually if you need something specific or something that isn't one of miniaudio's +stock channel maps. + +By default the system's default device will be used. Set the pDeviceID member to a pointer to a ma_device_id object to +use a specific device. You can enumerate over the devices with ma_context_enumerate_devices() or ma_context_get_devices() +which will give you access to the device ID. Set pDeviceID to NULL to use the default device. + +The device type can be one of the ma_device_type's: + ma_device_type_playback + ma_device_type_capture + ma_device_type_duplex + +Thread Safety: SAFE +*/ +ma_device_config ma_device_config_init(ma_device_type deviceType); + + +/************************************************************************************************************************************************************ + +Utiltities + +************************************************************************************************************************************************************/ + +/* +Creates a mutex. + +A mutex must be created from a valid context. A mutex is initially unlocked. +*/ +ma_result ma_mutex_init(ma_context* pContext, ma_mutex* pMutex); + +/* +Deletes a mutex. +*/ +void ma_mutex_uninit(ma_mutex* pMutex); + +/* +Locks a mutex with an infinite timeout. +*/ +void ma_mutex_lock(ma_mutex* pMutex); + +/* +Unlocks a mutex. +*/ +void ma_mutex_unlock(ma_mutex* pMutex); + + +/* +Retrieves a friendly name for a backend. +*/ +const char* ma_get_backend_name(ma_backend backend); + +/* +Adjust buffer size based on a scaling factor. + +This just multiplies the base size by the scaling factor, making sure it's a size of at least 1. +*/ +ma_uint32 ma_scale_buffer_size(ma_uint32 baseBufferSize, float scale); + +/* +Calculates a buffer size in milliseconds from the specified number of frames and sample rate. +*/ +ma_uint32 ma_calculate_buffer_size_in_milliseconds_from_frames(ma_uint32 bufferSizeInFrames, ma_uint32 sampleRate); + +/* +Calculates a buffer size in frames from the specified number of milliseconds and sample rate. +*/ +ma_uint32 ma_calculate_buffer_size_in_frames_from_milliseconds(ma_uint32 bufferSizeInMilliseconds, ma_uint32 sampleRate); + +/* +Retrieves the default buffer size in milliseconds based on the specified performance profile. +*/ +ma_uint32 ma_get_default_buffer_size_in_milliseconds(ma_performance_profile performanceProfile); + +/* +Calculates a buffer size in frames for the specified performance profile and scale factor. +*/ +ma_uint32 ma_get_default_buffer_size_in_frames(ma_performance_profile performanceProfile, ma_uint32 sampleRate); + +/* +Copies silent frames into the given buffer. +*/ +void ma_zero_pcm_frames(void* p, ma_uint32 frameCount, ma_format format, ma_uint32 channels); + +#endif /* MA_NO_DEVICE_IO */ + + + + +/************************************************************************************************************************************************************ + +Decoding + +************************************************************************************************************************************************************/ +#ifndef MA_NO_DECODING + +typedef struct ma_decoder ma_decoder; + +typedef enum +{ + ma_seek_origin_start, + ma_seek_origin_current +} ma_seek_origin; + +typedef size_t (* ma_decoder_read_proc) (ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead); /* Returns the number of bytes read. */ +typedef ma_bool32 (* ma_decoder_seek_proc) (ma_decoder* pDecoder, int byteOffset, ma_seek_origin origin); +typedef ma_result (* ma_decoder_seek_to_pcm_frame_proc) (ma_decoder* pDecoder, ma_uint64 frameIndex); +typedef ma_result (* ma_decoder_uninit_proc) (ma_decoder* pDecoder); +typedef ma_uint64 (* ma_decoder_get_length_in_pcm_frames_proc)(ma_decoder* pDecoder); + +typedef struct +{ + ma_format format; /* Set to 0 or ma_format_unknown to use the stream's internal format. */ + ma_uint32 channels; /* Set to 0 to use the stream's internal channels. */ + ma_uint32 sampleRate; /* Set to 0 to use the stream's internal sample rate. */ + ma_channel channelMap[MA_MAX_CHANNELS]; + ma_channel_mix_mode channelMixMode; + ma_dither_mode ditherMode; + ma_src_algorithm srcAlgorithm; + union + { + ma_src_config_sinc sinc; + } src; +} ma_decoder_config; + +struct ma_decoder +{ + ma_decoder_read_proc onRead; + ma_decoder_seek_proc onSeek; + void* pUserData; + ma_uint64 readPointer; /* Used for returning back to a previous position after analysing the stream or whatnot. */ + ma_format internalFormat; + ma_uint32 internalChannels; + ma_uint32 internalSampleRate; + ma_channel internalChannelMap[MA_MAX_CHANNELS]; + ma_format outputFormat; + ma_uint32 outputChannels; + ma_uint32 outputSampleRate; + ma_channel outputChannelMap[MA_MAX_CHANNELS]; + ma_pcm_converter dsp; /* <-- Format conversion is achieved by running frames through this. */ + ma_decoder_seek_to_pcm_frame_proc onSeekToPCMFrame; + ma_decoder_uninit_proc onUninit; + ma_decoder_get_length_in_pcm_frames_proc onGetLengthInPCMFrames; + void* pInternalDecoder; /* <-- The drwav/drflac/stb_vorbis/etc. objects. */ + struct + { + const ma_uint8* pData; + size_t dataSize; + size_t currentReadPos; + } memory; /* Only used for decoders that were opened against a block of memory. */ +}; + +ma_decoder_config ma_decoder_config_init(ma_format outputFormat, ma_uint32 outputChannels, ma_uint32 outputSampleRate); + +ma_result ma_decoder_init(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder); +ma_result ma_decoder_init_wav(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder); +ma_result ma_decoder_init_flac(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder); +ma_result ma_decoder_init_vorbis(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder); +ma_result ma_decoder_init_mp3(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder); +ma_result ma_decoder_init_raw(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfigIn, const ma_decoder_config* pConfigOut, ma_decoder* pDecoder); + +ma_result ma_decoder_init_memory(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder); +ma_result ma_decoder_init_memory_wav(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder); +ma_result ma_decoder_init_memory_flac(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder); +ma_result ma_decoder_init_memory_vorbis(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder); +ma_result ma_decoder_init_memory_mp3(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder); +ma_result ma_decoder_init_memory_raw(const void* pData, size_t dataSize, const ma_decoder_config* pConfigIn, const ma_decoder_config* pConfigOut, ma_decoder* pDecoder); + +#ifndef MA_NO_STDIO +ma_result ma_decoder_init_file(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); +ma_result ma_decoder_init_file_wav(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); +ma_result ma_decoder_init_file_flac(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); +ma_result ma_decoder_init_file_vorbis(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); +ma_result ma_decoder_init_file_mp3(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); + +ma_result ma_decoder_init_file_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); +ma_result ma_decoder_init_file_wav_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); +ma_result ma_decoder_init_file_flac_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); +ma_result ma_decoder_init_file_vorbis_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); +ma_result ma_decoder_init_file_mp3_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); +#endif + +ma_result ma_decoder_uninit(ma_decoder* pDecoder); + +/* +Retrieves the length of the decoder in PCM frames. + +Do not call this on streams of an undefined length, such as internet radio. + +If the length is unknown or an error occurs, 0 will be returned. + +This will always return 0 for Vorbis decoders. This is due to a limitation with stb_vorbis in push mode which is what miniaudio +uses internally. + +This will run in linear time for MP3 decoders. Do not call this in time critical scenarios. +*/ +ma_uint64 ma_decoder_get_length_in_pcm_frames(ma_decoder* pDecoder); + +ma_uint64 ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount); +ma_result ma_decoder_seek_to_pcm_frame(ma_decoder* pDecoder, ma_uint64 frameIndex); + +/* +Helper for opening and decoding a file into a heap allocated block of memory. Free the returned pointer with ma_free(). On input, +pConfig should be set to what you want. On output it will be set to what you got. +*/ +#ifndef MA_NO_STDIO +ma_result ma_decode_file(const char* pFilePath, ma_decoder_config* pConfig, ma_uint64* pFrameCountOut, void** ppDataOut); +#endif +ma_result ma_decode_memory(const void* pData, size_t dataSize, ma_decoder_config* pConfig, ma_uint64* pFrameCountOut, void** ppDataOut); + +#endif /* MA_NO_DECODING */ + + +/************************************************************************************************************************************************************ + +Generation + +************************************************************************************************************************************************************/ +typedef struct +{ + double amplitude; + double periodsPerSecond; + double delta; + double time; +} ma_sine_wave; + +ma_result ma_sine_wave_init(double amplitude, double period, ma_uint32 sampleRate, ma_sine_wave* pSineWave); +ma_uint64 ma_sine_wave_read_f32(ma_sine_wave* pSineWave, ma_uint64 count, float* pSamples); +ma_uint64 ma_sine_wave_read_f32_ex(ma_sine_wave* pSineWave, ma_uint64 frameCount, ma_uint32 channels, ma_stream_layout layout, float** ppFrames); + +#ifdef __cplusplus +} +#endif +#endif /* miniaudio_h */ + + + +/************************************************************************************************************************************************************ +************************************************************************************************************************************************************* + +IMPLEMENTATION + +************************************************************************************************************************************************************* +************************************************************************************************************************************************************/ +#if defined(MINIAUDIO_IMPLEMENTATION) || defined(MA_IMPLEMENTATION) +#include +#include /* For INT_MAX */ +#include /* sin(), etc. */ + +#if defined(MA_DEBUG_OUTPUT) +#include /* for printf() for debug output */ +#endif + +#ifdef MA_WIN32 +/* + @raysan5: To avoid conflicting windows.h symbols with raylib, so flags are defined + WARNING: Those flags avoid inclusion of some Win32 headers that could be required + by user at some point and won't be included... +*/ + +/* If defined, the following flags inhibit definition of the indicated items.*/ +#define NOGDICAPMASKS // CC_*, LC_*, PC_*, CP_*, TC_*, RC_ +#define NOVIRTUALKEYCODES // VK_* +#define NOWINMESSAGES // WM_*, EM_*, LB_*, CB_* +#define NOWINSTYLES // WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_* +#define NOSYSMETRICS // SM_* +#define NOMENUS // MF_* +#define NOICONS // IDI_* +#define NOKEYSTATES // MK_* +#define NOSYSCOMMANDS // SC_* +#define NORASTEROPS // Binary and Tertiary raster ops +#define NOSHOWWINDOW // SW_* +#define OEMRESOURCE // OEM Resource values +#define NOATOM // Atom Manager routines +#define NOCLIPBOARD // Clipboard routines +#define NOCOLOR // Screen colors +#define NOCTLMGR // Control and Dialog routines +#define NODRAWTEXT // DrawText() and DT_* +#define NOGDI // All GDI defines and routines +#define NOKERNEL // All KERNEL defines and routines +#define NOUSER // All USER defines and routines +/*#define NONLS // All NLS defines and routines*/ +#define NOMB // MB_* and MessageBox() +#define NOMEMMGR // GMEM_*, LMEM_*, GHND, LHND, associated routines +#define NOMETAFILE // typedef METAFILEPICT +#define NOMINMAX // Macros min(a,b) and max(a,b) +#define NOMSG // typedef MSG and associated routines +#define NOOPENFILE // OpenFile(), OemToAnsi, AnsiToOem, and OF_* +#define NOSCROLL // SB_* and scrolling routines +#define NOSERVICE // All Service Controller routines, SERVICE_ equates, etc. +#define NOSOUND // Sound driver routines +#define NOTEXTMETRIC // typedef TEXTMETRIC and associated routines +#define NOWH // SetWindowsHook and WH_* +#define NOWINOFFSETS // GWL_*, GCL_*, associated routines +#define NOCOMM // COMM driver routines +#define NOKANJI // Kanji support stuff. +#define NOHELP // Help engine interface. +#define NOPROFILER // Profiler interface. +#define NODEFERWINDOWPOS // DeferWindowPos routines +#define NOMCX // Modem Configuration Extensions + +/* Type required before windows.h inclusion */ +typedef struct tagMSG *LPMSG; + +#include + +/* Type required by some unused function... */ +typedef struct tagBITMAPINFOHEADER { + DWORD biSize; + LONG biWidth; + LONG biHeight; + WORD biPlanes; + WORD biBitCount; + DWORD biCompression; + DWORD biSizeImage; + LONG biXPelsPerMeter; + LONG biYPelsPerMeter; + DWORD biClrUsed; + DWORD biClrImportant; +} BITMAPINFOHEADER, *PBITMAPINFOHEADER; + +#include +#include +#include + +/* @raysan5: Some required types defined for MSVC/TinyC compiler */ +#if defined(_MSC_VER) || defined(__TINYC__) + #include "propidl.h" +#endif + +#else +#include /* For malloc(), free(), wcstombs(). */ +#include /* For memset() */ +#endif + +#if defined(MA_APPLE) && (__MAC_OS_X_VERSION_MIN_REQUIRED < 101200) +#include /* For mach_absolute_time() */ +#endif + +#ifdef MA_POSIX +#include +#include +#include +#include +#endif + +#ifdef MA_EMSCRIPTEN +#include +#endif + +#if !defined(MA_64BIT) && !defined(MA_32BIT) +#ifdef _WIN32 +#ifdef _WIN64 +#define MA_64BIT +#else +#define MA_32BIT +#endif +#endif +#endif + +#if !defined(MA_64BIT) && !defined(MA_32BIT) +#ifdef __GNUC__ +#ifdef __LP64__ +#define MA_64BIT +#else +#define MA_32BIT +#endif +#endif +#endif + +#if !defined(MA_64BIT) && !defined(MA_32BIT) +#include +#if INTPTR_MAX == INT64_MAX +#define MA_64BIT +#else +#define MA_32BIT +#endif +#endif + +/* Architecture Detection */ +#if defined(__x86_64__) || defined(_M_X64) +#define MA_X64 +#elif defined(__i386) || defined(_M_IX86) +#define MA_X86 +#elif defined(__arm__) || defined(_M_ARM) +#define MA_ARM +#endif + +/* Cannot currently support AVX-512 if AVX is disabled. */ +#if !defined(MA_NO_AVX512) && defined(MA_NO_AVX2) +#define MA_NO_AVX512 +#endif + +/* Intrinsics Support */ +#if defined(MA_X64) || defined(MA_X86) + #if defined(_MSC_VER) && !defined(__clang__) + /* MSVC. */ + #if _MSC_VER >= 1400 && !defined(MA_NO_SSE2) /* 2005 */ + #define MA_SUPPORT_SSE2 + #endif + /*#if _MSC_VER >= 1600 && !defined(MA_NO_AVX)*/ /* 2010 */ + /* #define MA_SUPPORT_AVX*/ + /*#endif*/ + #if _MSC_VER >= 1700 && !defined(MA_NO_AVX2) /* 2012 */ + #define MA_SUPPORT_AVX2 + #endif + #if _MSC_VER >= 1910 && !defined(MA_NO_AVX512) /* 2017 */ + #define MA_SUPPORT_AVX512 + #endif + #else + /* Assume GNUC-style. */ + #if defined(__SSE2__) && !defined(MA_NO_SSE2) + #define MA_SUPPORT_SSE2 + #endif + /*#if defined(__AVX__) && !defined(MA_NO_AVX)*/ + /* #define MA_SUPPORT_AVX*/ + /*#endif*/ + #if defined(__AVX2__) && !defined(MA_NO_AVX2) + #define MA_SUPPORT_AVX2 + #endif + #if defined(__AVX512F__) && !defined(MA_NO_AVX512) + #define MA_SUPPORT_AVX512 + #endif + #endif + + /* If at this point we still haven't determined compiler support for the intrinsics just fall back to __has_include. */ + #if !defined(__GNUC__) && !defined(__clang__) && defined(__has_include) + #if !defined(MA_SUPPORT_SSE2) && !defined(MA_NO_SSE2) && __has_include() + #define MA_SUPPORT_SSE2 + #endif + /*#if !defined(MA_SUPPORT_AVX) && !defined(MA_NO_AVX) && __has_include()*/ + /* #define MA_SUPPORT_AVX*/ + /*#endif*/ + #if !defined(MA_SUPPORT_AVX2) && !defined(MA_NO_AVX2) && __has_include() + #define MA_SUPPORT_AVX2 + #endif + #if !defined(MA_SUPPORT_AVX512) && !defined(MA_NO_AVX512) && __has_include() + #define MA_SUPPORT_AVX512 + #endif + #endif + + #if defined(MA_SUPPORT_AVX512) + #include /* Not a mistake. Intentionally including instead of because otherwise the compiler will complain. */ + #elif defined(MA_SUPPORT_AVX2) || defined(MA_SUPPORT_AVX) + #include + #elif defined(MA_SUPPORT_SSE2) + #include + #endif +#endif + +#if defined(MA_ARM) + #if !defined(MA_NO_NEON) && (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64)) + #define MA_SUPPORT_NEON + #endif + + /* Fall back to looking for the #include file. */ + #if !defined(__GNUC__) && !defined(__clang__) && defined(__has_include) + #if !defined(MA_SUPPORT_NEON) && !defined(MA_NO_NEON) && __has_include() + #define MA_SUPPORT_NEON + #endif + #endif + + #if defined(MA_SUPPORT_NEON) + #include + #endif +#endif + +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable:4752) /* found Intel(R) Advanced Vector Extensions; consider using /arch:AVX */ +#endif + +#if defined(MA_X64) || defined(MA_X86) + #if defined(_MSC_VER) && !defined(__clang__) + #if _MSC_VER >= 1400 + #include + static MA_INLINE void ma_cpuid(int info[4], int fid) + { + __cpuid(info, fid); + } + #else + #define MA_NO_CPUID + #endif + + #if _MSC_VER >= 1600 && (defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 160040219) + static MA_INLINE unsigned __int64 ma_xgetbv(int reg) + { + return _xgetbv(reg); + } + #else + #define MA_NO_XGETBV + #endif + #elif (defined(__GNUC__) || defined(__clang__)) && !defined(MA_ANDROID) + static MA_INLINE void ma_cpuid(int info[4], int fid) + { + /* + 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__) + __asm__ __volatile__ ( + "xchg{l} {%%}ebx, %k1;" + "cpuid;" + "xchg{l} {%%}ebx, %k1;" + : "=a"(info[0]), "=&r"(info[1]), "=c"(info[2]), "=d"(info[3]) : "a"(fid), "c"(0) + ); + #else + __asm__ __volatile__ ( + "cpuid" : "=a"(info[0]), "=b"(info[1]), "=c"(info[2]), "=d"(info[3]) : "a"(fid), "c"(0) + ); + #endif + } + + static MA_INLINE ma_uint64 ma_xgetbv(int reg) + { + unsigned int hi; + unsigned int lo; + + __asm__ __volatile__ ( + "xgetbv" : "=a"(lo), "=d"(hi) : "c"(reg) + ); + + return ((ma_uint64)hi << 32) | (ma_uint64)lo; + } + #else + #define MA_NO_CPUID + #define MA_NO_XGETBV + #endif +#else + #define MA_NO_CPUID + #define MA_NO_XGETBV +#endif + +static MA_INLINE ma_bool32 ma_has_sse2() +{ +#if defined(MA_SUPPORT_SSE2) + #if (defined(MA_X64) || defined(MA_X86)) && !defined(MA_NO_SSE2) + #if defined(MA_X64) + return MA_TRUE; /* 64-bit targets always support SSE2. */ + #elif (defined(_M_IX86_FP) && _M_IX86_FP == 2) || defined(__SSE2__) + return MA_TRUE; /* If the compiler is allowed to freely generate SSE2 code we can assume support. */ + #else + #if defined(MA_NO_CPUID) + return MA_FALSE; + #else + int info[4]; + ma_cpuid(info, 1); + return (info[3] & (1 << 26)) != 0; + #endif + #endif + #else + return MA_FALSE; /* SSE2 is only supported on x86 and x64 architectures. */ + #endif +#else + return MA_FALSE; /* No compiler support. */ +#endif +} + +#if 0 +static MA_INLINE ma_bool32 ma_has_avx() +{ +#if defined(MA_SUPPORT_AVX) + #if (defined(MA_X64) || defined(MA_X86)) && !defined(MA_NO_AVX) + #if defined(_AVX_) || defined(__AVX__) + return MA_TRUE; /* If the compiler is allowed to freely generate AVX code we can assume support. */ + #else + /* AVX requires both CPU and OS support. */ + #if defined(MA_NO_CPUID) || defined(MA_NO_XGETBV) + return MA_FALSE; + #else + int info[4]; + ma_cpuid(info, 1); + if (((info[2] & (1 << 27)) != 0) && ((info[2] & (1 << 28)) != 0)) { + ma_uint64 xrc = ma_xgetbv(0); + if ((xrc & 0x06) == 0x06) { + return MA_TRUE; + } else { + return MA_FALSE; + } + } else { + return MA_FALSE; + } + #endif + #endif + #else + return MA_FALSE; /* AVX is only supported on x86 and x64 architectures. */ + #endif +#else + return MA_FALSE; /* No compiler support. */ +#endif +} +#endif + +static MA_INLINE ma_bool32 ma_has_avx2() +{ +#if defined(MA_SUPPORT_AVX2) + #if (defined(MA_X64) || defined(MA_X86)) && !defined(MA_NO_AVX2) + #if defined(_AVX2_) || defined(__AVX2__) + return MA_TRUE; /* If the compiler is allowed to freely generate AVX2 code we can assume support. */ + #else + /* AVX2 requires both CPU and OS support. */ + #if defined(MA_NO_CPUID) || defined(MA_NO_XGETBV) + return MA_FALSE; + #else + int info1[4]; + int info7[4]; + ma_cpuid(info1, 1); + ma_cpuid(info7, 7); + if (((info1[2] & (1 << 27)) != 0) && ((info7[1] & (1 << 5)) != 0)) { + ma_uint64 xrc = ma_xgetbv(0); + if ((xrc & 0x06) == 0x06) { + return MA_TRUE; + } else { + return MA_FALSE; + } + } else { + return MA_FALSE; + } + #endif + #endif + #else + return MA_FALSE; /* AVX2 is only supported on x86 and x64 architectures. */ + #endif +#else + return MA_FALSE; /* No compiler support. */ +#endif +} + +static MA_INLINE ma_bool32 ma_has_avx512f() +{ +#if defined(MA_SUPPORT_AVX512) + #if (defined(MA_X64) || defined(MA_X86)) && !defined(MA_NO_AVX512) + #if defined(__AVX512F__) + return MA_TRUE; /* If the compiler is allowed to freely generate AVX-512F code we can assume support. */ + #else + /* AVX-512 requires both CPU and OS support. */ + #if defined(MA_NO_CPUID) || defined(MA_NO_XGETBV) + return MA_FALSE; + #else + int info1[4]; + int info7[4]; + ma_cpuid(info1, 1); + ma_cpuid(info7, 7); + if (((info1[2] & (1 << 27)) != 0) && ((info7[1] & (1 << 16)) != 0)) { + ma_uint64 xrc = ma_xgetbv(0); + if ((xrc & 0xE6) == 0xE6) { + return MA_TRUE; + } else { + return MA_FALSE; + } + } else { + return MA_FALSE; + } + #endif + #endif + #else + return MA_FALSE; /* AVX-512F is only supported on x86 and x64 architectures. */ + #endif +#else + return MA_FALSE; /* No compiler support. */ +#endif +} + +static MA_INLINE ma_bool32 ma_has_neon() +{ +#if defined(MA_SUPPORT_NEON) + #if defined(MA_ARM) && !defined(MA_NO_NEON) + #if (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64)) + return MA_TRUE; /* If the compiler is allowed to freely generate NEON code we can assume support. */ + #else + /* TODO: Runtime check. */ + return MA_FALSE; + #endif + #else + return MA_FALSE; /* NEON is only supported on ARM architectures. */ + #endif +#else + return MA_FALSE; /* No compiler support. */ +#endif +} + + +static MA_INLINE ma_bool32 ma_is_little_endian() +{ +#if defined(MA_X86) || defined(MA_X64) + return MA_TRUE; +#else + int n = 1; + return (*(char*)&n) == 1; +#endif +} + +static MA_INLINE ma_bool32 ma_is_big_endian() +{ + return !ma_is_little_endian(); +} + + +#ifndef MA_COINIT_VALUE +#define MA_COINIT_VALUE 0 /* 0 = COINIT_MULTITHREADED*/ +#endif + + + +#ifndef MA_PI +#define MA_PI 3.14159265358979323846264f +#endif +#ifndef MA_PI_D +#define MA_PI_D 3.14159265358979323846264 +#endif +#ifndef MA_TAU +#define MA_TAU 6.28318530717958647693f +#endif +#ifndef MA_TAU_D +#define MA_TAU_D 6.28318530717958647693 +#endif + + +/* The default format when ma_format_unknown (0) is requested when initializing a device. */ +#ifndef MA_DEFAULT_FORMAT +#define MA_DEFAULT_FORMAT ma_format_f32 +#endif + +/* The default channel count to use when 0 is used when initializing a device. */ +#ifndef MA_DEFAULT_CHANNELS +#define MA_DEFAULT_CHANNELS 2 +#endif + +/* The default sample rate to use when 0 is used when initializing a device. */ +#ifndef MA_DEFAULT_SAMPLE_RATE +#define MA_DEFAULT_SAMPLE_RATE 48000 +#endif + +/* Default periods when none is specified in ma_device_init(). More periods means more work on the CPU. */ +#ifndef MA_DEFAULT_PERIODS +#define MA_DEFAULT_PERIODS 3 +#endif + +/* The base buffer size in milliseconds for low latency mode. */ +#ifndef MA_BASE_BUFFER_SIZE_IN_MILLISECONDS_LOW_LATENCY +#define MA_BASE_BUFFER_SIZE_IN_MILLISECONDS_LOW_LATENCY (10*MA_DEFAULT_PERIODS) +#endif + +/* The base buffer size in milliseconds for conservative mode. */ +#ifndef MA_BASE_BUFFER_SIZE_IN_MILLISECONDS_CONSERVATIVE +#define MA_BASE_BUFFER_SIZE_IN_MILLISECONDS_CONSERVATIVE (100*MA_DEFAULT_PERIODS) +#endif + + +/* Standard sample rates, in order of priority. */ +ma_uint32 g_maStandardSampleRatePriorities[] = { + MA_SAMPLE_RATE_48000, /* Most common */ + MA_SAMPLE_RATE_44100, + + MA_SAMPLE_RATE_32000, /* Lows */ + MA_SAMPLE_RATE_24000, + MA_SAMPLE_RATE_22050, + + MA_SAMPLE_RATE_88200, /* Highs */ + MA_SAMPLE_RATE_96000, + MA_SAMPLE_RATE_176400, + MA_SAMPLE_RATE_192000, + + MA_SAMPLE_RATE_16000, /* Extreme lows */ + MA_SAMPLE_RATE_11025, + MA_SAMPLE_RATE_8000, + + MA_SAMPLE_RATE_352800, /* Extreme highs */ + MA_SAMPLE_RATE_384000 +}; + +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 */ +}; + + + +/****************************************************************************** + +Standard Library Stuff + +******************************************************************************/ +#ifndef MA_MALLOC +#ifdef MA_WIN32 +#define MA_MALLOC(sz) HeapAlloc(GetProcessHeap(), 0, (sz)) +#else +#define MA_MALLOC(sz) malloc((sz)) +#endif +#endif + +#ifndef MA_REALLOC +#ifdef MA_WIN32 +#define MA_REALLOC(p, sz) (((sz) > 0) ? ((p) ? HeapReAlloc(GetProcessHeap(), 0, (p), (sz)) : HeapAlloc(GetProcessHeap(), 0, (sz))) : ((VOID*)(size_t)(HeapFree(GetProcessHeap(), 0, (p)) & 0))) +#else +#define MA_REALLOC(p, sz) realloc((p), (sz)) +#endif +#endif + +#ifndef MA_FREE +#ifdef MA_WIN32 +#define MA_FREE(p) HeapFree(GetProcessHeap(), 0, (p)) +#else +#define MA_FREE(p) free((p)) +#endif +#endif + +#ifndef MA_ZERO_MEMORY +#ifdef MA_WIN32 +#define MA_ZERO_MEMORY(p, sz) ZeroMemory((p), (sz)) +#else +#define MA_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) +#endif +#endif + +#ifndef MA_COPY_MEMORY +#ifdef MA_WIN32 +#define MA_COPY_MEMORY(dst, src, sz) CopyMemory((dst), (src), (sz)) +#else +#define MA_COPY_MEMORY(dst, src, sz) memcpy((dst), (src), (sz)) +#endif +#endif + +#ifndef MA_ASSERT +#ifdef MA_WIN32 +#define MA_ASSERT(condition) assert(condition) +#else +#define MA_ASSERT(condition) assert(condition) +#endif +#endif + +#define ma_zero_memory MA_ZERO_MEMORY +#define ma_copy_memory MA_COPY_MEMORY +#define ma_assert MA_ASSERT + +#define ma_zero_object(p) ma_zero_memory((p), sizeof(*(p))) +#define ma_countof(x) (sizeof(x) / sizeof(x[0])) +#define ma_max(x, y) (((x) > (y)) ? (x) : (y)) +#define ma_min(x, y) (((x) < (y)) ? (x) : (y)) +#define ma_clamp(x, lo, hi) (ma_max(lo, ma_min(x, hi))) +#define ma_offset_ptr(p, offset) (((ma_uint8*)(p)) + (offset)) + +#define ma_buffer_frame_capacity(buffer, channels, format) (sizeof(buffer) / ma_get_bytes_per_sample(format) / (channels)) + +/* +Return Values: + 0: Success + 22: EINVAL + 34: ERANGE + +Not using symbolic constants for errors because I want to avoid #including errno.h +*/ +int ma_strcpy_s(char* dst, size_t dstSizeInBytes, const char* src) +{ + size_t i; + + if (dst == 0) { + return 22; + } + if (dstSizeInBytes == 0) { + return 34; + } + if (src == 0) { + dst[0] = '\0'; + return 22; + } + + for (i = 0; i < dstSizeInBytes && src[i] != '\0'; ++i) { + dst[i] = src[i]; + } + + if (i < dstSizeInBytes) { + dst[i] = '\0'; + return 0; + } + + dst[0] = '\0'; + return 34; +} + +int ma_strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count) +{ + size_t maxcount; + size_t i; + + if (dst == 0) { + return 22; + } + if (dstSizeInBytes == 0) { + return 34; + } + if (src == 0) { + dst[0] = '\0'; + return 22; + } + + maxcount = count; + if (count == ((size_t)-1) || count >= dstSizeInBytes) { /* -1 = _TRUNCATE */ + maxcount = dstSizeInBytes - 1; + } + + for (i = 0; i < maxcount && src[i] != '\0'; ++i) { + dst[i] = src[i]; + } + + if (src[i] == '\0' || i == count || count == ((size_t)-1)) { + dst[i] = '\0'; + return 0; + } + + dst[0] = '\0'; + return 34; +} + +int ma_strcat_s(char* dst, size_t dstSizeInBytes, const char* src) +{ + char* dstorig; + + if (dst == 0) { + return 22; + } + if (dstSizeInBytes == 0) { + return 34; + } + if (src == 0) { + dst[0] = '\0'; + return 22; + } + + dstorig = dst; + + while (dstSizeInBytes > 0 && dst[0] != '\0') { + dst += 1; + dstSizeInBytes -= 1; + } + + if (dstSizeInBytes == 0) { + return 22; /* Unterminated. */ + } + + + while (dstSizeInBytes > 0 && src[0] != '\0') { + *dst++ = *src++; + dstSizeInBytes -= 1; + } + + if (dstSizeInBytes > 0) { + dst[0] = '\0'; + } else { + dstorig[0] = '\0'; + return 34; + } + + return 0; +} + +int ma_strncat_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count) +{ + char* dstorig; + + if (dst == 0) { + return 22; + } + if (dstSizeInBytes == 0) { + return 34; + } + if (src == 0) { + return 22; + } + + dstorig = dst; + + while (dstSizeInBytes > 0 && dst[0] != '\0') { + dst += 1; + dstSizeInBytes -= 1; + } + + if (dstSizeInBytes == 0) { + return 22; /* Unterminated. */ + } + + + if (count == ((size_t)-1)) { /* _TRUNCATE */ + count = dstSizeInBytes - 1; + } + + while (dstSizeInBytes > 0 && src[0] != '\0' && count > 0) { + *dst++ = *src++; + dstSizeInBytes -= 1; + count -= 1; + } + + if (dstSizeInBytes > 0) { + dst[0] = '\0'; + } else { + dstorig[0] = '\0'; + return 34; + } + + return 0; +} + +int ma_itoa_s(int value, char* dst, size_t dstSizeInBytes, int radix) +{ + int sign; + unsigned int valueU; + char* dstEnd; + + if (dst == NULL || dstSizeInBytes == 0) { + return 22; + } + if (radix < 2 || radix > 36) { + dst[0] = '\0'; + return 22; + } + + sign = (value < 0 && radix == 10) ? -1 : 1; /* The negative sign is only used when the base is 10. */ + + if (value < 0) { + valueU = -value; + } else { + valueU = value; + } + + dstEnd = dst; + do + { + int remainder = valueU % radix; + if (remainder > 9) { + *dstEnd = (char)((remainder - 10) + 'a'); + } else { + *dstEnd = (char)(remainder + '0'); + } + + dstEnd += 1; + dstSizeInBytes -= 1; + valueU /= radix; + } while (dstSizeInBytes > 0 && valueU > 0); + + if (dstSizeInBytes == 0) { + dst[0] = '\0'; + return 22; /* Ran out of room in the output buffer. */ + } + + if (sign < 0) { + *dstEnd++ = '-'; + dstSizeInBytes -= 1; + } + + if (dstSizeInBytes == 0) { + dst[0] = '\0'; + return 22; /* Ran out of room in the output buffer. */ + } + + *dstEnd = '\0'; + + + /* At this point the string will be reversed. */ + dstEnd -= 1; + while (dst < dstEnd) { + char temp = *dst; + *dst = *dstEnd; + *dstEnd = temp; + + dst += 1; + dstEnd -= 1; + } + + return 0; +} + +int ma_strcmp(const char* str1, const char* str2) +{ + if (str1 == str2) return 0; + + /* These checks differ from the standard implementation. It's not important, but I prefer it just for sanity. */ + if (str1 == NULL) return -1; + if (str2 == NULL) return 1; + + for (;;) { + if (str1[0] == '\0') { + break; + } + if (str1[0] != str2[0]) { + break; + } + + str1 += 1; + str2 += 1; + } + + return ((unsigned char*)str1)[0] - ((unsigned char*)str2)[0]; +} + +int ma_strappend(char* dst, size_t dstSize, const char* srcA, const char* srcB) +{ + int result; + + result = ma_strncpy_s(dst, dstSize, srcA, (size_t)-1); + if (result != 0) { + return result; + } + + result = ma_strncat_s(dst, dstSize, srcB, (size_t)-1); + if (result != 0) { + return result; + } + + return result; +} + +char* ma_copy_string(const char* src) +{ + size_t sz = strlen(src)+1; + char* dst = (char*)ma_malloc(sz); + if (dst == NULL) { + return NULL; + } + + ma_strcpy_s(dst, sz, src); + + return dst; +} + + +/* Thanks to good old Bit Twiddling Hacks for this one: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 */ +static MA_INLINE unsigned int ma_next_power_of_2(unsigned int x) +{ + x--; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + x++; + + return x; +} + +static MA_INLINE unsigned int ma_prev_power_of_2(unsigned int x) +{ + return ma_next_power_of_2(x) >> 1; +} + +static MA_INLINE unsigned int ma_round_to_power_of_2(unsigned int x) +{ + unsigned int prev = ma_prev_power_of_2(x); + unsigned int next = ma_next_power_of_2(x); + if ((next - x) > (x - prev)) { + return prev; + } else { + return next; + } +} + +static MA_INLINE unsigned int ma_count_set_bits(unsigned int x) +{ + unsigned int count = 0; + while (x != 0) { + if (x & 1) { + count += 1; + } + + x = x >> 1; + } + + return count; +} + + + +/* Clamps an f32 sample to -1..1 */ +static MA_INLINE float ma_clip_f32(float x) +{ + if (x < -1) return -1; + if (x > +1) return +1; + return x; +} + +static MA_INLINE float ma_mix_f32(float x, float y, float a) +{ + return x*(1-a) + y*a; +} +static MA_INLINE float ma_mix_f32_fast(float x, float y, float a) +{ + float r0 = (y - x); + float r1 = r0*a; + return x + r1; + /*return x + (y - x)*a;*/ +} + +#if defined(MA_SUPPORT_SSE2) +static MA_INLINE __m128 ma_mix_f32_fast__sse2(__m128 x, __m128 y, __m128 a) +{ + return _mm_add_ps(x, _mm_mul_ps(_mm_sub_ps(y, x), a)); +} +#endif +#if defined(MA_SUPPORT_AVX2) +static MA_INLINE __m256 ma_mix_f32_fast__avx2(__m256 x, __m256 y, __m256 a) +{ + return _mm256_add_ps(x, _mm256_mul_ps(_mm256_sub_ps(y, x), a)); +} +#endif +#if defined(MA_SUPPORT_AVX512) +static MA_INLINE __m512 ma_mix_f32_fast__avx512(__m512 x, __m512 y, __m512 a) +{ + return _mm512_add_ps(x, _mm512_mul_ps(_mm512_sub_ps(y, x), a)); +} +#endif +#if defined(MA_SUPPORT_NEON) +static MA_INLINE float32x4_t ma_mix_f32_fast__neon(float32x4_t x, float32x4_t y, float32x4_t a) +{ + return vaddq_f32(x, vmulq_f32(vsubq_f32(y, x), a)); +} +#endif + + +static MA_INLINE double ma_mix_f64(double x, double y, double a) +{ + return x*(1-a) + y*a; +} +static MA_INLINE double ma_mix_f64_fast(double x, double y, double a) +{ + return x + (y - x)*a; +} + +static MA_INLINE float ma_scale_to_range_f32(float x, float lo, float hi) +{ + return lo + x*(hi-lo); +} + + +/* +Random Number Generation + +miniaudio uses the LCG random number generation algorithm. This is good enough for audio. + +Note that miniaudio's LCG implementation uses global state which is _not_ thread-local. When this is called across +multiple threads, results will be unpredictable. However, it won't crash and results will still be random enough +for miniaudio's purposes. +*/ +#define MA_LCG_M 2147483647 +#define MA_LCG_A 48271 +#define MA_LCG_C 0 +static ma_int32 g_maLCG; + +void ma_seed(ma_int32 seed) +{ + g_maLCG = seed; +} + +ma_int32 ma_rand_s32() +{ + ma_int32 lcg = g_maLCG; + ma_int32 r = (MA_LCG_A * lcg + MA_LCG_C) % MA_LCG_M; + g_maLCG = r; + return r; +} + +double ma_rand_f64() +{ + return (ma_rand_s32() + 0x80000000) / (double)0x7FFFFFFF; +} + +float ma_rand_f32() +{ + return (float)ma_rand_f64(); +} + +static MA_INLINE float ma_rand_range_f32(float lo, float hi) +{ + return ma_scale_to_range_f32(ma_rand_f32(), lo, hi); +} + +static MA_INLINE ma_int32 ma_rand_range_s32(ma_int32 lo, ma_int32 hi) +{ + double x = ma_rand_f64(); + return lo + (ma_int32)(x*(hi-lo)); +} + + +static MA_INLINE float ma_dither_f32_rectangle(float ditherMin, float ditherMax) +{ + return ma_rand_range_f32(ditherMin, ditherMax); +} + +static MA_INLINE float ma_dither_f32_triangle(float ditherMin, float ditherMax) +{ + float a = ma_rand_range_f32(ditherMin, 0); + float b = ma_rand_range_f32(0, ditherMax); + return a + b; +} + +static MA_INLINE float ma_dither_f32(ma_dither_mode ditherMode, float ditherMin, float ditherMax) +{ + if (ditherMode == ma_dither_mode_rectangle) { + return ma_dither_f32_rectangle(ditherMin, ditherMax); + } + if (ditherMode == ma_dither_mode_triangle) { + return ma_dither_f32_triangle(ditherMin, ditherMax); + } + + return 0; +} + +static MA_INLINE ma_int32 ma_dither_s32(ma_dither_mode ditherMode, ma_int32 ditherMin, ma_int32 ditherMax) +{ + if (ditherMode == ma_dither_mode_rectangle) { + ma_int32 a = ma_rand_range_s32(ditherMin, ditherMax); + return a; + } + if (ditherMode == ma_dither_mode_triangle) { + ma_int32 a = ma_rand_range_s32(ditherMin, 0); + ma_int32 b = ma_rand_range_s32(0, ditherMax); + return a + b; + } + + return 0; +} + + +/* +Splits a buffer into parts of equal length and of the given alignment. The returned size of the split buffers will be a +multiple of the alignment. The alignment must be a power of 2. +*/ +void ma_split_buffer(void* pBuffer, size_t bufferSize, size_t splitCount, size_t alignment, void** ppBuffersOut, size_t* pSplitSizeOut) +{ + ma_uintptr pBufferUnaligned; + ma_uintptr pBufferAligned; + size_t unalignedBytes; + size_t splitSize; + + if (pSplitSizeOut) { + *pSplitSizeOut = 0; + } + + if (pBuffer == NULL || bufferSize == 0 || splitCount == 0) { + return; + } + + if (alignment == 0) { + alignment = 1; + } + + pBufferUnaligned = (ma_uintptr)pBuffer; + pBufferAligned = (pBufferUnaligned + (alignment-1)) & ~(alignment-1); + unalignedBytes = (size_t)(pBufferAligned - pBufferUnaligned); + + splitSize = 0; + if (bufferSize >= unalignedBytes) { + splitSize = (bufferSize - unalignedBytes) / splitCount; + splitSize = splitSize & ~(alignment-1); + } + + if (ppBuffersOut != NULL) { + size_t i; + for (i = 0; i < splitCount; ++i) { + ppBuffersOut[i] = (ma_uint8*)(pBufferAligned + (splitSize*i)); + } + } + + if (pSplitSizeOut) { + *pSplitSizeOut = splitSize; + } +} + + +/****************************************************************************** + +Atomics + +******************************************************************************/ +#if defined(_WIN32) && !defined(__GNUC__) +#define ma_memory_barrier() MemoryBarrier() +#define ma_atomic_exchange_32(a, b) InterlockedExchange((LONG*)a, (LONG)b) +#define ma_atomic_exchange_64(a, b) InterlockedExchange64((LONGLONG*)a, (LONGLONG)b) +#define ma_atomic_increment_32(a) InterlockedIncrement((LONG*)a) +#define ma_atomic_decrement_32(a) InterlockedDecrement((LONG*)a) +#else +#define ma_memory_barrier() __sync_synchronize() +#define ma_atomic_exchange_32(a, b) (void)__sync_lock_test_and_set(a, b); __sync_synchronize() +#define ma_atomic_exchange_64(a, b) (void)__sync_lock_test_and_set(a, b); __sync_synchronize() +#define ma_atomic_increment_32(a) __sync_add_and_fetch(a, 1) +#define ma_atomic_decrement_32(a) __sync_sub_and_fetch(a, 1) +#endif + +#ifdef MA_64BIT +#define ma_atomic_exchange_ptr ma_atomic_exchange_64 +#endif +#ifdef MA_32BIT +#define ma_atomic_exchange_ptr ma_atomic_exchange_32 +#endif + + +ma_uint32 ma_get_standard_sample_rate_priority_index(ma_uint32 sampleRate) /* Lower = higher priority */ +{ + ma_uint32 i; + for (i = 0; i < ma_countof(g_maStandardSampleRatePriorities); ++i) { + if (g_maStandardSampleRatePriorities[i] == sampleRate) { + return i; + } + } + + return (ma_uint32)-1; +} + +ma_uint64 ma_calculate_frame_count_after_src(ma_uint32 sampleRateOut, ma_uint32 sampleRateIn, ma_uint64 frameCountIn) +{ + double srcRatio = (double)sampleRateOut / sampleRateIn; + double frameCountOutF = (ma_int64)frameCountIn * srcRatio; /* Cast to int64 required for VC6. */ + ma_uint64 frameCountOut = (ma_uint64)frameCountOutF; + + /* If the output frame count is fractional, make sure we add an extra frame to ensure there's enough room for that last sample. */ + if ((frameCountOutF - (ma_int64)frameCountOut) > 0.0) { + frameCountOut += 1; + } + + return frameCountOut; +} + + +/************************************************************************************************************************************************************ +************************************************************************************************************************************************************* + +DEVICE I/O +========== + +************************************************************************************************************************************************************* +************************************************************************************************************************************************************/ +#ifndef MA_NO_DEVICE_IO +/* +Unfortunately using runtime linking for pthreads causes problems. This has occurred for me when testing on FreeBSD. When +using runtime linking, deadlocks can occur (for me it happens when loading data from fread()). It turns out that doing +compile-time linking fixes this. I'm not sure why this happens, but the safest way I can think of to fix this is to simply +disable runtime linking by default. To enable runtime linking, #define this before the implementation of this file. I am +not officially supporting this, but I'm leaving it here in case it's useful for somebody, somewhere. +*/ +/*#define MA_USE_RUNTIME_LINKING_FOR_PTHREAD*/ + +/* Disable run-time linking on certain backends. */ +#ifndef MA_NO_RUNTIME_LINKING + #if defined(MA_ANDROID) || 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 + +const char* ma_get_backend_name(ma_backend backend) +{ + switch (backend) + { + case ma_backend_wasapi: return "WASAPI"; + case ma_backend_dsound: return "DirectSound"; + case ma_backend_winmm: return "WinMM"; + case ma_backend_coreaudio: return "Core Audio"; + case ma_backend_sndio: return "sndio"; + case ma_backend_audio4: return "audio(4)"; + case ma_backend_oss: return "OSS"; + case ma_backend_pulseaudio: return "PulseAudio"; + case ma_backend_alsa: return "ALSA"; + case ma_backend_jack: return "JACK"; + case ma_backend_aaudio: return "AAudio"; + case ma_backend_opensl: return "OpenSL|ES"; + case ma_backend_webaudio: return "Web Audio"; + case ma_backend_null: return "Null"; + default: return "Unknown"; + } +} + + + +#ifdef MA_WIN32 + #define MA_THREADCALL WINAPI + typedef unsigned long ma_thread_result; +#else + #define MA_THREADCALL + typedef void* ma_thread_result; +#endif +typedef ma_thread_result (MA_THREADCALL * ma_thread_entry_proc)(void* pData); + +#ifdef MA_WIN32 +typedef HRESULT (WINAPI * MA_PFN_CoInitializeEx)(LPVOID pvReserved, DWORD dwCoInit); +typedef void (WINAPI * MA_PFN_CoUninitialize)(); +typedef HRESULT (WINAPI * MA_PFN_CoCreateInstance)(REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID *ppv); +typedef void (WINAPI * MA_PFN_CoTaskMemFree)(LPVOID pv); +typedef HRESULT (WINAPI * MA_PFN_PropVariantClear)(PROPVARIANT *pvar); +typedef int (WINAPI * MA_PFN_StringFromGUID2)(const GUID* const rguid, LPOLESTR lpsz, int cchMax); + +typedef HWND (WINAPI * MA_PFN_GetForegroundWindow)(); +typedef HWND (WINAPI * MA_PFN_GetDesktopWindow)(); + +/* Microsoft documents these APIs as returning LSTATUS, but the Win32 API shipping with some compilers do not define it. It's just a LONG. */ +typedef LONG (WINAPI * MA_PFN_RegOpenKeyExA)(HKEY hKey, LPCSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult); +typedef LONG (WINAPI * MA_PFN_RegCloseKey)(HKEY hKey); +typedef LONG (WINAPI * MA_PFN_RegQueryValueExA)(HKEY hKey, LPCSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData); +#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" + + +const char* ma_log_level_to_string(ma_uint32 logLevel) +{ + switch (logLevel) + { + case MA_LOG_LEVEL_VERBOSE: return ""; + case MA_LOG_LEVEL_INFO: return "INFO"; + case MA_LOG_LEVEL_WARNING: return "WARNING"; + case MA_LOG_LEVEL_ERROR: return "ERROR"; + default: return "ERROR"; + } +} + +/* Posts a log message. */ +void ma_log(ma_context* pContext, ma_device* pDevice, ma_uint32 logLevel, const char* message) +{ + if (pContext == NULL) { + return; + } + +#if defined(MA_LOG_LEVEL) + if (logLevel <= MA_LOG_LEVEL) { + ma_log_proc onLog; + + #if defined(MA_DEBUG_OUTPUT) + if (logLevel <= MA_LOG_LEVEL) { + printf("%s: %s\n", ma_log_level_to_string(logLevel), message); + } + #endif + + onLog = pContext->logCallback; + if (onLog) { + onLog(pContext, pDevice, logLevel, message); + } + } +#endif +} + +/* Posts an log message. Throw a breakpoint in here if you're needing to debug. The return value is always "resultCode". */ +ma_result ma_context_post_error(ma_context* pContext, ma_device* pDevice, ma_uint32 logLevel, const char* message, ma_result resultCode) +{ + /* Derive the context from the device if necessary. */ + if (pContext == NULL) { + if (pDevice != NULL) { + pContext = pDevice->pContext; + } + } + + ma_log(pContext, pDevice, logLevel, message); + return resultCode; +} + +ma_result ma_post_error(ma_device* pDevice, ma_uint32 logLevel, const char* message, ma_result resultCode) +{ + return ma_context_post_error(NULL, pDevice, logLevel, message, resultCode); +} + + +/******************************************************************************* + +Timing + +*******************************************************************************/ +#ifdef MA_WIN32 +LARGE_INTEGER g_ma_TimerFrequency = {{0}}; +void ma_timer_init(ma_timer* pTimer) +{ + LARGE_INTEGER counter; + + if (g_ma_TimerFrequency.QuadPart == 0) { + QueryPerformanceFrequency(&g_ma_TimerFrequency); + } + + QueryPerformanceCounter(&counter); + pTimer->counter = counter.QuadPart; +} + +double ma_timer_get_time_in_seconds(ma_timer* pTimer) +{ + LARGE_INTEGER counter; + if (!QueryPerformanceCounter(&counter)) { + return 0; + } + + return (double)(counter.QuadPart - pTimer->counter) / g_ma_TimerFrequency.QuadPart; +} +#elif defined(MA_APPLE) && (__MAC_OS_X_VERSION_MIN_REQUIRED < 101200) +ma_uint64 g_ma_TimerFrequency = 0; +void ma_timer_init(ma_timer* pTimer) +{ + mach_timebase_info_data_t baseTime; + mach_timebase_info(&baseTime); + g_ma_TimerFrequency = (baseTime.denom * 1e9) / baseTime.numer; + + pTimer->counter = mach_absolute_time(); +} + +double ma_timer_get_time_in_seconds(ma_timer* pTimer) +{ + ma_uint64 newTimeCounter = mach_absolute_time(); + ma_uint64 oldTimeCounter = pTimer->counter; + + return (newTimeCounter - oldTimeCounter) / g_ma_TimerFrequency; +} +#elif defined(MA_EMSCRIPTEN) +void ma_timer_init(ma_timer* pTimer) +{ + pTimer->counterD = emscripten_get_now(); +} + +double ma_timer_get_time_in_seconds(ma_timer* pTimer) +{ + return (emscripten_get_now() - pTimer->counterD) / 1000; /* Emscripten is in milliseconds. */ +} +#else +#if _POSIX_C_SOURCE >= 199309L +#if defined(CLOCK_MONOTONIC) + #define MA_CLOCK_ID CLOCK_MONOTONIC +#else + #define MA_CLOCK_ID CLOCK_REALTIME +#endif + +void ma_timer_init(ma_timer* pTimer) +{ + struct timespec newTime; + clock_gettime(MA_CLOCK_ID, &newTime); + + pTimer->counter = (newTime.tv_sec * 1000000000) + newTime.tv_nsec; +} + +double ma_timer_get_time_in_seconds(ma_timer* pTimer) +{ + ma_uint64 newTimeCounter; + ma_uint64 oldTimeCounter; + + struct timespec newTime; + clock_gettime(MA_CLOCK_ID, &newTime); + + newTimeCounter = (newTime.tv_sec * 1000000000) + newTime.tv_nsec; + oldTimeCounter = pTimer->counter; + + return (newTimeCounter - oldTimeCounter) / 1000000000.0; +} +#else +void ma_timer_init(ma_timer* pTimer) +{ + struct timeval newTime; + gettimeofday(&newTime, NULL); + + pTimer->counter = (newTime.tv_sec * 1000000) + newTime.tv_usec; +} + +double ma_timer_get_time_in_seconds(ma_timer* pTimer) +{ + ma_uint64 newTimeCounter; + ma_uint64 oldTimeCounter; + + struct timeval newTime; + gettimeofday(&newTime, NULL); + + newTimeCounter = (newTime.tv_sec * 1000000) + newTime.tv_usec; + oldTimeCounter = pTimer->counter; + + return (newTimeCounter - oldTimeCounter) / 1000000.0; +} +#endif +#endif + + +/******************************************************************************* + +Dynamic Linking + +*******************************************************************************/ +ma_handle ma_dlopen(ma_context* pContext, const char* filename) +{ + ma_handle handle; + +#if MA_LOG_LEVEL >= MA_LOG_LEVEL_VERBOSE + if (pContext != NULL) { + char message[256]; + ma_strappend(message, sizeof(message), "Loading library: ", filename); + ma_log(pContext, NULL, MA_LOG_LEVEL_VERBOSE, message); + } +#endif + +#ifdef _WIN32 +#ifdef MA_WIN32_DESKTOP + handle = (ma_handle)LoadLibraryA(filename); +#else + /* *sigh* It appears there is no ANSI version of LoadPackagedLibrary()... */ + WCHAR filenameW[4096]; + if (MultiByteToWideChar(CP_UTF8, 0, filename, -1, filenameW, sizeof(filenameW)) == 0) { + handle = NULL; + } else { + handle = (ma_handle)LoadPackagedLibrary(filenameW, 0); + } +#endif +#else + handle = (ma_handle)dlopen(filename, RTLD_NOW); +#endif + + /* + I'm not considering failure to load a library an error nor a warning because seamlessly falling through to a lower-priority + backend is a deliberate design choice. Instead I'm logging it as an informational message. + */ +#if MA_LOG_LEVEL >= MA_LOG_LEVEL_INFO + if (handle == NULL) { + char message[256]; + ma_strappend(message, sizeof(message), "Failed to load library: ", filename); + ma_log(pContext, NULL, MA_LOG_LEVEL_INFO, message); + } +#endif + + (void)pContext; /* It's possible for pContext to be unused. */ + return handle; +} + +void ma_dlclose(ma_context* pContext, ma_handle handle) +{ +#ifdef _WIN32 + FreeLibrary((HMODULE)handle); +#else + dlclose((void*)handle); +#endif + + (void)pContext; +} + +ma_proc ma_dlsym(ma_context* pContext, ma_handle handle, const char* symbol) +{ + ma_proc proc; + +#if MA_LOG_LEVEL >= MA_LOG_LEVEL_VERBOSE + if (pContext != NULL) { + char message[256]; + ma_strappend(message, sizeof(message), "Loading symbol: ", symbol); + ma_log(pContext, NULL, MA_LOG_LEVEL_VERBOSE, message); + } +#endif + +#ifdef _WIN32 + proc = (ma_proc)GetProcAddress((HMODULE)handle, symbol); +#else +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) + #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)) + #pragma GCC diagnostic pop +#endif +#endif + +#if MA_LOG_LEVEL >= MA_LOG_LEVEL_WARNING + if (handle == NULL) { + char message[256]; + ma_strappend(message, sizeof(message), "Failed to load symbol: ", symbol); + ma_log(pContext, NULL, MA_LOG_LEVEL_WARNING, message); + } +#endif + + (void)pContext; /* It's possible for pContext to be unused. */ + return proc; +} + + +/******************************************************************************* + +Threading + +*******************************************************************************/ +#ifdef MA_WIN32 +int ma_thread_priority_to_win32(ma_thread_priority priority) +{ + switch (priority) { + case ma_thread_priority_idle: return THREAD_PRIORITY_IDLE; + case ma_thread_priority_lowest: return THREAD_PRIORITY_LOWEST; + case ma_thread_priority_low: return THREAD_PRIORITY_BELOW_NORMAL; + case ma_thread_priority_normal: return THREAD_PRIORITY_NORMAL; + case ma_thread_priority_high: return THREAD_PRIORITY_ABOVE_NORMAL; + case ma_thread_priority_highest: return THREAD_PRIORITY_HIGHEST; + case ma_thread_priority_realtime: return THREAD_PRIORITY_TIME_CRITICAL; + default: return THREAD_PRIORITY_NORMAL; + } +} + +ma_result ma_thread_create__win32(ma_context* pContext, ma_thread* pThread, ma_thread_entry_proc entryProc, void* pData) +{ + pThread->win32.hThread = CreateThread(NULL, 0, entryProc, pData, 0, NULL); + if (pThread->win32.hThread == NULL) { + return MA_FAILED_TO_CREATE_THREAD; + } + + SetThreadPriority((HANDLE)pThread->win32.hThread, ma_thread_priority_to_win32(pContext->threadPriority)); + + return MA_SUCCESS; +} + +void ma_thread_wait__win32(ma_thread* pThread) +{ + WaitForSingleObject(pThread->win32.hThread, INFINITE); +} + +void ma_sleep__win32(ma_uint32 milliseconds) +{ + Sleep((DWORD)milliseconds); +} + + +ma_result ma_mutex_init__win32(ma_context* pContext, ma_mutex* pMutex) +{ + (void)pContext; + + pMutex->win32.hMutex = CreateEventA(NULL, FALSE, TRUE, NULL); + if (pMutex->win32.hMutex == NULL) { + return MA_FAILED_TO_CREATE_MUTEX; + } + + return MA_SUCCESS; +} + +void ma_mutex_uninit__win32(ma_mutex* pMutex) +{ + CloseHandle(pMutex->win32.hMutex); +} + +void ma_mutex_lock__win32(ma_mutex* pMutex) +{ + WaitForSingleObject(pMutex->win32.hMutex, INFINITE); +} + +void ma_mutex_unlock__win32(ma_mutex* pMutex) +{ + SetEvent(pMutex->win32.hMutex); +} + + +ma_result ma_event_init__win32(ma_context* pContext, ma_event* pEvent) +{ + (void)pContext; + + pEvent->win32.hEvent = CreateEventW(NULL, FALSE, FALSE, NULL); + if (pEvent->win32.hEvent == NULL) { + return MA_FAILED_TO_CREATE_EVENT; + } + + return MA_SUCCESS; +} + +void ma_event_uninit__win32(ma_event* pEvent) +{ + CloseHandle(pEvent->win32.hEvent); +} + +ma_bool32 ma_event_wait__win32(ma_event* pEvent) +{ + return WaitForSingleObject(pEvent->win32.hEvent, INFINITE) == WAIT_OBJECT_0; +} + +ma_bool32 ma_event_signal__win32(ma_event* pEvent) +{ + return SetEvent(pEvent->win32.hEvent); +} +#endif + + +#ifdef MA_POSIX +#include + +typedef int (* ma_pthread_create_proc)(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); +typedef int (* ma_pthread_join_proc)(pthread_t thread, void **retval); +typedef int (* ma_pthread_mutex_init_proc)(pthread_mutex_t *__mutex, const pthread_mutexattr_t *__mutexattr); +typedef int (* ma_pthread_mutex_destroy_proc)(pthread_mutex_t *__mutex); +typedef int (* ma_pthread_mutex_lock_proc)(pthread_mutex_t *__mutex); +typedef int (* ma_pthread_mutex_unlock_proc)(pthread_mutex_t *__mutex); +typedef int (* ma_pthread_cond_init_proc)(pthread_cond_t *__restrict __cond, const pthread_condattr_t *__restrict __cond_attr); +typedef int (* ma_pthread_cond_destroy_proc)(pthread_cond_t *__cond); +typedef int (* ma_pthread_cond_signal_proc)(pthread_cond_t *__cond); +typedef int (* ma_pthread_cond_wait_proc)(pthread_cond_t *__restrict __cond, pthread_mutex_t *__restrict __mutex); +typedef int (* ma_pthread_attr_init_proc)(pthread_attr_t *attr); +typedef int (* ma_pthread_attr_destroy_proc)(pthread_attr_t *attr); +typedef int (* ma_pthread_attr_setschedpolicy_proc)(pthread_attr_t *attr, int policy); +typedef int (* ma_pthread_attr_getschedparam_proc)(const pthread_attr_t *attr, struct sched_param *param); +typedef int (* ma_pthread_attr_setschedparam_proc)(pthread_attr_t *attr, const struct sched_param *param); + +ma_result ma_thread_create__posix(ma_context* pContext, ma_thread* pThread, ma_thread_entry_proc entryProc, void* pData) +{ + int result; + pthread_attr_t* pAttr = NULL; + +#if !defined(__EMSCRIPTEN__) + /* Try setting the thread priority. It's not critical if anything fails here. */ + pthread_attr_t attr; + if (((ma_pthread_attr_init_proc)pContext->posix.pthread_attr_init)(&attr) == 0) { + int scheduler = -1; + if (pContext->threadPriority == ma_thread_priority_idle) { +#ifdef SCHED_IDLE + if (((ma_pthread_attr_setschedpolicy_proc)pContext->posix.pthread_attr_setschedpolicy)(&attr, SCHED_IDLE) == 0) { + scheduler = SCHED_IDLE; + } +#endif + } else if (pContext->threadPriority == ma_thread_priority_realtime) { +#ifdef SCHED_FIFO + if (((ma_pthread_attr_setschedpolicy_proc)pContext->posix.pthread_attr_setschedpolicy)(&attr, SCHED_FIFO) == 0) { + scheduler = SCHED_FIFO; + } +#endif +#ifdef MA_LINUX + } else { + scheduler = sched_getscheduler(0); +#endif + } + + if (scheduler != -1) { + int priorityMin = sched_get_priority_min(scheduler); + int priorityMax = sched_get_priority_max(scheduler); + int priorityStep = (priorityMax - priorityMin) / 7; /* 7 = number of priorities supported by miniaudio. */ + + struct sched_param sched; + if (((ma_pthread_attr_getschedparam_proc)pContext->posix.pthread_attr_getschedparam)(&attr, &sched) == 0) { + if (pContext->threadPriority == ma_thread_priority_idle) { + sched.sched_priority = priorityMin; + } else if (pContext->threadPriority == ma_thread_priority_realtime) { + sched.sched_priority = priorityMax; + } else { + sched.sched_priority += ((int)pContext->threadPriority + 5) * priorityStep; /* +5 because the lowest priority is -5. */ + if (sched.sched_priority < priorityMin) { + sched.sched_priority = priorityMin; + } + if (sched.sched_priority > priorityMax) { + sched.sched_priority = priorityMax; + } + } + + if (((ma_pthread_attr_setschedparam_proc)pContext->posix.pthread_attr_setschedparam)(&attr, &sched) == 0) { + pAttr = &attr; + } + } + } + + ((ma_pthread_attr_destroy_proc)pContext->posix.pthread_attr_destroy)(&attr); + } +#endif + + result = ((ma_pthread_create_proc)pContext->posix.pthread_create)(&pThread->posix.thread, pAttr, entryProc, pData); + if (result != 0) { + return MA_FAILED_TO_CREATE_THREAD; + } + + return MA_SUCCESS; +} + +void ma_thread_wait__posix(ma_thread* pThread) +{ + ((ma_pthread_join_proc)pThread->pContext->posix.pthread_join)(pThread->posix.thread, NULL); +} + +void ma_sleep__posix(ma_uint32 milliseconds) +{ +#ifdef MA_EMSCRIPTEN + (void)milliseconds; + ma_assert(MA_FALSE); /* The Emscripten build should never sleep. */ +#else + #if _POSIX_C_SOURCE >= 199309L + struct timespec ts; + ts.tv_sec = milliseconds / 1000000; + ts.tv_nsec = milliseconds % 1000000 * 1000000; + nanosleep(&ts, NULL); + #else + struct timeval tv; + tv.tv_sec = milliseconds / 1000; + tv.tv_usec = milliseconds % 1000 * 1000; + select(0, NULL, NULL, NULL, &tv); + #endif +#endif +} + + +ma_result ma_mutex_init__posix(ma_context* pContext, ma_mutex* pMutex) +{ + int result = ((ma_pthread_mutex_init_proc)pContext->posix.pthread_mutex_init)(&pMutex->posix.mutex, NULL); + if (result != 0) { + return MA_FAILED_TO_CREATE_MUTEX; + } + + return MA_SUCCESS; +} + +void ma_mutex_uninit__posix(ma_mutex* pMutex) +{ + ((ma_pthread_mutex_destroy_proc)pMutex->pContext->posix.pthread_mutex_destroy)(&pMutex->posix.mutex); +} + +void ma_mutex_lock__posix(ma_mutex* pMutex) +{ + ((ma_pthread_mutex_lock_proc)pMutex->pContext->posix.pthread_mutex_lock)(&pMutex->posix.mutex); +} + +void ma_mutex_unlock__posix(ma_mutex* pMutex) +{ + ((ma_pthread_mutex_unlock_proc)pMutex->pContext->posix.pthread_mutex_unlock)(&pMutex->posix.mutex); +} + + +ma_result ma_event_init__posix(ma_context* pContext, ma_event* pEvent) +{ + if (((ma_pthread_mutex_init_proc)pContext->posix.pthread_mutex_init)(&pEvent->posix.mutex, NULL) != 0) { + return MA_FAILED_TO_CREATE_MUTEX; + } + + if (((ma_pthread_cond_init_proc)pContext->posix.pthread_cond_init)(&pEvent->posix.condition, NULL) != 0) { + return MA_FAILED_TO_CREATE_EVENT; + } + + pEvent->posix.value = 0; + return MA_SUCCESS; +} + +void ma_event_uninit__posix(ma_event* pEvent) +{ + ((ma_pthread_cond_destroy_proc)pEvent->pContext->posix.pthread_cond_destroy)(&pEvent->posix.condition); + ((ma_pthread_mutex_destroy_proc)pEvent->pContext->posix.pthread_mutex_destroy)(&pEvent->posix.mutex); +} + +ma_bool32 ma_event_wait__posix(ma_event* pEvent) +{ + ((ma_pthread_mutex_lock_proc)pEvent->pContext->posix.pthread_mutex_lock)(&pEvent->posix.mutex); + { + while (pEvent->posix.value == 0) { + ((ma_pthread_cond_wait_proc)pEvent->pContext->posix.pthread_cond_wait)(&pEvent->posix.condition, &pEvent->posix.mutex); + } + pEvent->posix.value = 0; /* Auto-reset. */ + } + ((ma_pthread_mutex_unlock_proc)pEvent->pContext->posix.pthread_mutex_unlock)(&pEvent->posix.mutex); + + return MA_TRUE; +} + +ma_bool32 ma_event_signal__posix(ma_event* pEvent) +{ + ((ma_pthread_mutex_lock_proc)pEvent->pContext->posix.pthread_mutex_lock)(&pEvent->posix.mutex); + { + pEvent->posix.value = 1; + ((ma_pthread_cond_signal_proc)pEvent->pContext->posix.pthread_cond_signal)(&pEvent->posix.condition); + } + ((ma_pthread_mutex_unlock_proc)pEvent->pContext->posix.pthread_mutex_unlock)(&pEvent->posix.mutex); + + return MA_TRUE; +} +#endif + +ma_result ma_thread_create(ma_context* pContext, ma_thread* pThread, ma_thread_entry_proc entryProc, void* pData) +{ + if (pContext == NULL || pThread == NULL || entryProc == NULL) { + return MA_FALSE; + } + + pThread->pContext = pContext; + +#ifdef MA_WIN32 + return ma_thread_create__win32(pContext, pThread, entryProc, pData); +#endif +#ifdef MA_POSIX + return ma_thread_create__posix(pContext, pThread, entryProc, pData); +#endif +} + +void ma_thread_wait(ma_thread* pThread) +{ + if (pThread == NULL) { + return; + } + +#ifdef MA_WIN32 + ma_thread_wait__win32(pThread); +#endif +#ifdef MA_POSIX + ma_thread_wait__posix(pThread); +#endif +} + +void ma_sleep(ma_uint32 milliseconds) +{ +#ifdef MA_WIN32 + ma_sleep__win32(milliseconds); +#endif +#ifdef MA_POSIX + ma_sleep__posix(milliseconds); +#endif +} + + +ma_result ma_mutex_init(ma_context* pContext, ma_mutex* pMutex) +{ + if (pContext == NULL || pMutex == NULL) { + return MA_INVALID_ARGS; + } + + pMutex->pContext = pContext; + +#ifdef MA_WIN32 + return ma_mutex_init__win32(pContext, pMutex); +#endif +#ifdef MA_POSIX + return ma_mutex_init__posix(pContext, pMutex); +#endif +} + +void ma_mutex_uninit(ma_mutex* pMutex) +{ + if (pMutex == NULL || pMutex->pContext == NULL) { + return; + } + +#ifdef MA_WIN32 + ma_mutex_uninit__win32(pMutex); +#endif +#ifdef MA_POSIX + ma_mutex_uninit__posix(pMutex); +#endif +} + +void ma_mutex_lock(ma_mutex* pMutex) +{ + if (pMutex == NULL || pMutex->pContext == NULL) { + return; + } + +#ifdef MA_WIN32 + ma_mutex_lock__win32(pMutex); +#endif +#ifdef MA_POSIX + ma_mutex_lock__posix(pMutex); +#endif +} + +void ma_mutex_unlock(ma_mutex* pMutex) +{ + if (pMutex == NULL || pMutex->pContext == NULL) { + return; +} + +#ifdef MA_WIN32 + ma_mutex_unlock__win32(pMutex); +#endif +#ifdef MA_POSIX + ma_mutex_unlock__posix(pMutex); +#endif +} + + +ma_result ma_event_init(ma_context* pContext, ma_event* pEvent) +{ + if (pContext == NULL || pEvent == NULL) { + return MA_FALSE; + } + + pEvent->pContext = pContext; + +#ifdef MA_WIN32 + return ma_event_init__win32(pContext, pEvent); +#endif +#ifdef MA_POSIX + return ma_event_init__posix(pContext, pEvent); +#endif +} + +void ma_event_uninit(ma_event* pEvent) +{ + if (pEvent == NULL || pEvent->pContext == NULL) { + return; + } + +#ifdef MA_WIN32 + ma_event_uninit__win32(pEvent); +#endif +#ifdef MA_POSIX + ma_event_uninit__posix(pEvent); +#endif +} + +ma_bool32 ma_event_wait(ma_event* pEvent) +{ + if (pEvent == NULL || pEvent->pContext == NULL) { + return MA_FALSE; + } + +#ifdef MA_WIN32 + return ma_event_wait__win32(pEvent); +#endif +#ifdef MA_POSIX + return ma_event_wait__posix(pEvent); +#endif +} + +ma_bool32 ma_event_signal(ma_event* pEvent) +{ + if (pEvent == NULL || pEvent->pContext == NULL) { + return MA_FALSE; + } + +#ifdef MA_WIN32 + return ma_event_signal__win32(pEvent); +#endif +#ifdef MA_POSIX + return ma_event_signal__posix(pEvent); +#endif +} + + +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 (sampleRateMax > MA_MAX_SAMPLE_RATE) { + sampleRateMax = MA_MAX_SAMPLE_RATE; + } + if (sampleRateMin > sampleRateMax) { + sampleRateMin = sampleRateMax; + } + + if (sampleRateMin == sampleRateMax) { + return sampleRateMax; + } else { + size_t iStandardRate; + for (iStandardRate = 0; iStandardRate < ma_countof(g_maStandardSampleRatePriorities); ++iStandardRate) { + ma_uint32 standardRate = g_maStandardSampleRatePriorities[iStandardRate]; + if (standardRate >= sampleRateMin && standardRate <= sampleRateMax) { + return standardRate; + } + } + } + + /* Should never get here. */ + ma_assert(MA_FALSE); + return 0; +} + +ma_uint32 ma_get_closest_standard_sample_rate(ma_uint32 sampleRateIn) +{ + ma_uint32 closestRate = 0; + ma_uint32 closestDiff = 0xFFFFFFFF; + size_t iStandardRate; + + for (iStandardRate = 0; iStandardRate < ma_countof(g_maStandardSampleRatePriorities); ++iStandardRate) { + ma_uint32 standardRate = g_maStandardSampleRatePriorities[iStandardRate]; + ma_uint32 diff; + + if (sampleRateIn > standardRate) { + diff = sampleRateIn - standardRate; + } else { + diff = standardRate - sampleRateIn; + } + + if (diff == 0) { + return standardRate; /* The input sample rate is a standard rate. */ + } + + if (closestDiff > diff) { + closestDiff = diff; + closestRate = standardRate; + } + } + + return closestRate; +} + + +ma_uint32 ma_scale_buffer_size(ma_uint32 baseBufferSize, float scale) +{ + return ma_max(1, (ma_uint32)(baseBufferSize*scale)); +} + +ma_uint32 ma_calculate_buffer_size_in_milliseconds_from_frames(ma_uint32 bufferSizeInFrames, ma_uint32 sampleRate) +{ + return bufferSizeInFrames / (sampleRate/1000); +} + +ma_uint32 ma_calculate_buffer_size_in_frames_from_milliseconds(ma_uint32 bufferSizeInMilliseconds, ma_uint32 sampleRate) +{ + return bufferSizeInMilliseconds * (sampleRate/1000); +} + +ma_uint32 ma_get_default_buffer_size_in_milliseconds(ma_performance_profile performanceProfile) +{ + if (performanceProfile == ma_performance_profile_low_latency) { + return MA_BASE_BUFFER_SIZE_IN_MILLISECONDS_LOW_LATENCY; + } else { + return MA_BASE_BUFFER_SIZE_IN_MILLISECONDS_CONSERVATIVE; + } +} + +ma_uint32 ma_get_default_buffer_size_in_frames(ma_performance_profile performanceProfile, ma_uint32 sampleRate) +{ + ma_uint32 bufferSizeInMilliseconds; + ma_uint32 sampleRateMS; + + bufferSizeInMilliseconds = ma_get_default_buffer_size_in_milliseconds(performanceProfile); + if (bufferSizeInMilliseconds == 0) { + bufferSizeInMilliseconds = 1; + } + + sampleRateMS = (sampleRate/1000); + if (sampleRateMS == 0) { + sampleRateMS = 1; + } + + return bufferSizeInMilliseconds * sampleRateMS; +} + +ma_uint32 ma_get_fragment_size_in_bytes(ma_uint32 bufferSizeInFrames, ma_uint32 periods, ma_format format, ma_uint32 channels) +{ + ma_uint32 fragmentSizeInFrames = bufferSizeInFrames / periods; + return fragmentSizeInFrames * ma_get_bytes_per_frame(format, channels); +} + +void ma_zero_pcm_frames(void* p, ma_uint32 frameCount, ma_format format, ma_uint32 channels) +{ + ma_zero_memory(p, frameCount * ma_get_bytes_per_frame(format, channels)); +} + + + +/* The callback for reading from the client -> DSP -> device. */ +ma_uint32 ma_device__on_read_from_client(ma_pcm_converter* pDSP, void* pFramesOut, ma_uint32 frameCount, void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_device_callback_proc onData; + + ma_assert(pDevice != NULL); + + ma_zero_pcm_frames(pFramesOut, frameCount, pDevice->playback.format, pDevice->playback.channels); + + onData = pDevice->onData; + if (onData) { + onData(pDevice, pFramesOut, NULL, frameCount); + return frameCount; + } + + (void)pDSP; + return 0; +} + +/* The PCM converter callback for reading from a buffer. */ +ma_uint32 ma_device__pcm_converter__on_read_from_buffer_capture(ma_pcm_converter* pConverter, void* pFramesOut, ma_uint32 frameCount, void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_uint32 framesToRead; + ma_uint32 bytesToRead; + + ma_assert(pDevice != NULL); + + if (pDevice->capture._dspFrameCount == 0) { + return 0; /* Nothing left. */ + } + + framesToRead = frameCount; + if (framesToRead > pDevice->capture._dspFrameCount) { + framesToRead = pDevice->capture._dspFrameCount; + } + + bytesToRead = framesToRead * ma_get_bytes_per_frame(pConverter->formatConverterIn.config.formatIn, pConverter->channelRouter.config.channelsIn); + ma_copy_memory(pFramesOut, pDevice->capture._dspFrames, bytesToRead); + pDevice->capture._dspFrameCount -= framesToRead; + pDevice->capture._dspFrames += bytesToRead; + + return framesToRead; +} + +ma_uint32 ma_device__pcm_converter__on_read_from_buffer_playback(ma_pcm_converter* pConverter, void* pFramesOut, ma_uint32 frameCount, void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_uint32 framesToRead; + ma_uint32 bytesToRead; + + ma_assert(pDevice != NULL); + + if (pDevice->playback._dspFrameCount == 0) { + return 0; /* Nothing left. */ + } + + framesToRead = frameCount; + if (framesToRead > pDevice->playback._dspFrameCount) { + framesToRead = pDevice->playback._dspFrameCount; + } + + bytesToRead = framesToRead * ma_get_bytes_per_frame(pConverter->formatConverterIn.config.formatIn, pConverter->channelRouter.config.channelsIn); + ma_copy_memory(pFramesOut, pDevice->playback._dspFrames, bytesToRead); + pDevice->playback._dspFrameCount -= framesToRead; + pDevice->playback._dspFrames += bytesToRead; + + return framesToRead; +} + + + +/* A helper function for reading sample data from the client. */ +static MA_INLINE void ma_device__read_frames_from_client(ma_device* pDevice, ma_uint32 frameCount, void* pSamples) +{ + ma_device_callback_proc onData; + + ma_assert(pDevice != NULL); + ma_assert(frameCount > 0); + ma_assert(pSamples != NULL); + + onData = pDevice->onData; + if (onData) { + if (pDevice->playback.converter.isPassthrough) { + ma_zero_pcm_frames(pSamples, frameCount, pDevice->playback.format, pDevice->playback.channels); + onData(pDevice, pSamples, NULL, frameCount); + } else { + ma_pcm_converter_read(&pDevice->playback.converter, pSamples, frameCount); + } + } +} + +/* A helper for sending sample data to the client. */ +static MA_INLINE void ma_device__send_frames_to_client(ma_device* pDevice, ma_uint32 frameCount, const void* pSamples) +{ + ma_device_callback_proc onData; + + ma_assert(pDevice != NULL); + ma_assert(frameCount > 0); + ma_assert(pSamples != NULL); + + onData = pDevice->onData; + if (onData) { + if (pDevice->capture.converter.isPassthrough) { + onData(pDevice, NULL, pSamples, frameCount); + } else { + ma_uint8 chunkBuffer[4096]; + ma_uint32 chunkFrameCount; + + pDevice->capture._dspFrameCount = frameCount; + pDevice->capture._dspFrames = (const ma_uint8*)pSamples; + + chunkFrameCount = sizeof(chunkBuffer) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); + + for (;;) { + ma_uint32 framesJustRead = (ma_uint32)ma_pcm_converter_read(&pDevice->capture.converter, chunkBuffer, chunkFrameCount); + if (framesJustRead == 0) { + break; + } + + onData(pDevice, NULL, chunkBuffer, framesJustRead); + + if (framesJustRead < chunkFrameCount) { + break; + } + } + } + } +} + +static MA_INLINE ma_result ma_device__handle_duplex_callback_capture(ma_device* pDevice, ma_uint32 frameCount, const void* pFramesInInternalFormat, ma_pcm_rb* pRB) +{ + ma_result result; + + ma_assert(pDevice != NULL); + ma_assert(frameCount > 0); + ma_assert(pFramesInInternalFormat != NULL); + ma_assert(pRB != NULL); + + pDevice->capture._dspFrameCount = (ma_uint32)frameCount; + pDevice->capture._dspFrames = (const ma_uint8*)pFramesInInternalFormat; + + /* Write to the ring buffer. The ring buffer is in the external format. */ + for (;;) { + ma_uint32 framesProcessed; + ma_uint32 framesToProcess = 256; + void* pFramesInExternalFormat; + + result = ma_pcm_rb_acquire_write(pRB, &framesToProcess, &pFramesInExternalFormat); + if (result != MA_SUCCESS) { + ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "Failed to acquire capture PCM frames from ring buffer.", result); + break; + } + + if (framesToProcess == 0) { + if (ma_pcm_rb_pointer_disance(pRB) == (ma_int32)ma_pcm_rb_get_subbuffer_size(pRB)) { + break; /* Overrun. Not enough room in the ring buffer for input frame. Excess frames are dropped. */ + } + } + + /* Convert. */ + framesProcessed = (ma_uint32)ma_pcm_converter_read(&pDevice->capture.converter, pFramesInExternalFormat, framesToProcess); + + result = ma_pcm_rb_commit_write(pRB, framesProcessed, pFramesInExternalFormat); + if (result != MA_SUCCESS) { + ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "Failed to commit capture PCM frames to ring buffer.", result); + break; + } + + if (framesProcessed < framesToProcess) { + break; /* Done. */ + } + } + + return MA_SUCCESS; +} + +static MA_INLINE ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice, ma_uint32 frameCount, void* pFramesInInternalFormat, ma_pcm_rb* pRB) +{ + ma_result result; + ma_uint8 playbackFramesInExternalFormat[4096]; + ma_uint8 silentInputFrames[4096]; + ma_uint32 totalFramesToReadFromClient; + ma_uint32 totalFramesReadFromClient; + + ma_assert(pDevice != NULL); + 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. + */ + ma_zero_memory(silentInputFrames, sizeof(silentInputFrames)); + + /* We need to calculate how many output frames are required to be read from the client to completely fill frameCount internal frames. */ + totalFramesToReadFromClient = (ma_uint32)ma_calculate_frame_count_after_src(pDevice->sampleRate, pDevice->playback.internalSampleRate, frameCount); /* ma_pcm_converter_get_required_input_frame_count(&pDevice->playback.converter, (ma_uint32)frameCount); */ + totalFramesReadFromClient = 0; + while (totalFramesReadFromClient < totalFramesToReadFromClient && ma_device_is_started(pDevice)) { + ma_uint32 framesRemainingFromClient; + ma_uint32 framesToProcessFromClient; + ma_uint32 inputFrameCount; + void* pInputFrames; + + framesRemainingFromClient = (totalFramesToReadFromClient - totalFramesReadFromClient); + framesToProcessFromClient = sizeof(playbackFramesInExternalFormat) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); + if (framesToProcessFromClient > framesRemainingFromClient) { + framesToProcessFromClient = framesRemainingFromClient; + } + + /* We need to grab captured samples before firing the callback. If there's not enough input samples we just pass silence. */ + inputFrameCount = framesToProcessFromClient; + result = ma_pcm_rb_acquire_read(pRB, &inputFrameCount, &pInputFrames); + if (result == MA_SUCCESS) { + if (inputFrameCount > 0) { + /* Use actual input frames. */ + pDevice->onData(pDevice, playbackFramesInExternalFormat, pInputFrames, inputFrameCount); + } else { + if (ma_pcm_rb_pointer_disance(pRB) == 0) { + break; /* Underrun. */ + } + } + + /* We're done with the captured samples. */ + result = ma_pcm_rb_commit_read(pRB, inputFrameCount, pInputFrames); + if (result != MA_SUCCESS) { + break; /* Don't know what to do here... Just abandon ship. */ + } + } else { + /* Use silent input frames. */ + inputFrameCount = ma_min( + sizeof(playbackFramesInExternalFormat) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels), + sizeof(silentInputFrames) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels) + ); + + pDevice->onData(pDevice, playbackFramesInExternalFormat, silentInputFrames, inputFrameCount); + } + + /* We have samples in external format so now we need to convert to internal format and output to the device. */ + pDevice->playback._dspFrameCount = inputFrameCount; + pDevice->playback._dspFrames = (const ma_uint8*)playbackFramesInExternalFormat; + ma_pcm_converter_read(&pDevice->playback.converter, pFramesInInternalFormat, inputFrameCount); + + totalFramesReadFromClient += inputFrameCount; + pFramesInInternalFormat = ma_offset_ptr(pFramesInInternalFormat, inputFrameCount * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + } + + return MA_SUCCESS; +} + +/* A helper for changing the state of the device. */ +static MA_INLINE void ma_device__set_state(ma_device* pDevice, ma_uint32 newState) +{ + ma_atomic_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) +{ + ma_uint32 state; + ma_atomic_exchange_32(&state, pDevice->state); + + return state; +} + + +#ifdef MA_WIN32 + GUID MA_GUID_KSDATAFORMAT_SUBTYPE_PCM = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; + GUID MA_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; + /*GUID MA_GUID_KSDATAFORMAT_SUBTYPE_ALAW = {0x00000006, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};*/ + /*GUID MA_GUID_KSDATAFORMAT_SUBTYPE_MULAW = {0x00000007, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};*/ +#endif + + +ma_bool32 ma_context__device_id_equal(ma_context* pContext, const ma_device_id* pID0, const ma_device_id* pID1) +{ + ma_assert(pContext != NULL); + + if (pID0 == pID1) return MA_TRUE; + + if ((pID0 == NULL && pID1 != NULL) || + (pID0 != NULL && pID1 == NULL)) { + return MA_FALSE; + } + + if (pContext->onDeviceIDEqual) { + return pContext->onDeviceIDEqual(pContext, pID0, pID1); + } + + return MA_FALSE; +} + + +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; + +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. +*/ +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_uint32 ma_get_format_priority_index(ma_format format) /* Lower = better. */ +{ + ma_uint32 i; + for (i = 0; i < ma_countof(g_maFormatPriorities); ++i) { + if (g_maFormatPriorities[i] == format) { + return i; + } + } + + /* Getting here means the format could not be found or is equal to ma_format_unknown. */ + return (ma_uint32)-1; +} + +void ma_device__post_init_setup(ma_device* pDevice, ma_device_type deviceType); + + +/******************************************************************************* + +Null Backend + +*******************************************************************************/ +#ifdef MA_HAS_NULL + +#define MA_DEVICE_OP_NONE__NULL 0 +#define MA_DEVICE_OP_START__NULL 1 +#define MA_DEVICE_OP_SUSPEND__NULL 2 +#define MA_DEVICE_OP_KILL__NULL 3 + +ma_thread_result MA_THREADCALL ma_device_thread__null(void* pData) +{ + ma_device* pDevice = (ma_device*)pData; + ma_assert(pDevice != NULL); + + for (;;) { /* Keep the thread alive until the device is uninitialized. */ + /* Wait for an operation to be requested. */ + ma_event_wait(&pDevice->null_device.operationEvent); + + /* At this point an event should have been triggered. */ + + /* Starting the device needs to put the thread into a loop. */ + if (pDevice->null_device.operation == MA_DEVICE_OP_START__NULL) { + ma_atomic_exchange_32(&pDevice->null_device.operation, MA_DEVICE_OP_NONE__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. */ + ma_atomic_exchange_32(&pDevice->null_device.operationResult, MA_SUCCESS); + ma_event_signal(&pDevice->null_device.operationCompletionEvent); + 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) { + ma_atomic_exchange_32(&pDevice->null_device.operation, MA_DEVICE_OP_NONE__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. */ + ma_atomic_exchange_32(&pDevice->null_device.operationResult, MA_SUCCESS); + ma_event_signal(&pDevice->null_device.operationCompletionEvent); + 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) { + ma_atomic_exchange_32(&pDevice->null_device.operation, MA_DEVICE_OP_NONE__NULL); + ma_atomic_exchange_32(&pDevice->null_device.operationResult, MA_SUCCESS); + ma_event_signal(&pDevice->null_device.operationCompletionEvent); + 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) { + 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). */ + ma_atomic_exchange_32(&pDevice->null_device.operationResult, MA_INVALID_OPERATION); + ma_event_signal(&pDevice->null_device.operationCompletionEvent); + continue; /* Continue the loop. Don't terminate. */ + } + } + + return (ma_thread_result)0; +} + +ma_result ma_device_do_operation__null(ma_device* pDevice, ma_uint32 operation) +{ + ma_atomic_exchange_32(&pDevice->null_device.operation, operation); + if (!ma_event_signal(&pDevice->null_device.operationEvent)) { + return MA_ERROR; + } + + if (!ma_event_wait(&pDevice->null_device.operationCompletionEvent)) { + return MA_ERROR; + } + + return pDevice->null_device.operationResult; +} + +ma_uint64 ma_device_get_total_run_time_in_frames__null(ma_device* pDevice) +{ + ma_uint32 internalSampleRate; + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + internalSampleRate = pDevice->capture.internalSampleRate; + } else { + internalSampleRate = pDevice->playback.internalSampleRate; + } + + + return (ma_uint64)((pDevice->null_device.priorRunTime + ma_timer_get_time_in_seconds(&pDevice->null_device.timer)) * internalSampleRate); +} + +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; +} + +ma_result ma_context_enumerate_devices__null(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_bool32 cbResult = MA_TRUE; + + ma_assert(pContext != NULL); + ma_assert(callback != NULL); + + /* Playback. */ + if (cbResult) { + ma_device_info deviceInfo; + ma_zero_object(&deviceInfo); + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "NULL Playback Device", (size_t)-1); + cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); + } + + /* Capture. */ + if (cbResult) { + ma_device_info deviceInfo; + ma_zero_object(&deviceInfo); + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "NULL Capture Device", (size_t)-1); + cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); + } + + return MA_SUCCESS; +} + +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) +{ + ma_uint32 iFormat; + + ma_assert(pContext != NULL); + + if (pDeviceID != NULL && pDeviceID->nullbackend != 0) { + return MA_NO_DEVICE; /* Don't know the device. */ + } + + /* Name / Description */ + if (deviceType == ma_device_type_playback) { + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), "NULL Playback Device", (size_t)-1); + } else { + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), "NULL Capture Device", (size_t)-1); + } + + /* 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; + + (void)pContext; + (void)shareMode; + return MA_SUCCESS; +} + + +void 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); + + /* At this point the loop in the device thread is as good as terminated so we can uninitialize our events. */ + ma_event_uninit(&pDevice->null_device.operationCompletionEvent); + ma_event_uninit(&pDevice->null_device.operationEvent); +} + +ma_result ma_device_init__null(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +{ + ma_result result; + ma_uint32 bufferSizeInFrames; + + ma_assert(pDevice != NULL); + + ma_zero_object(&pDevice->null_device); + + bufferSizeInFrames = pConfig->bufferSizeInFrames; + if (bufferSizeInFrames == 0) { + bufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pConfig->bufferSizeInMilliseconds, pConfig->sampleRate); + } + + 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, pConfig->capture.channels); + pDevice->capture.internalBufferSizeInFrames = bufferSizeInFrames; + 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, pConfig->playback.channels); + pDevice->playback.internalBufferSizeInFrames = bufferSizeInFrames; + pDevice->playback.internalPeriods = pConfig->periods; + } + + /* + In order to get timing right, we need to create a thread that does nothing but keeps track of the timer. This timer is started when the + first period is "written" to it, and then stopped in ma_device_stop__null(). + */ + result = ma_event_init(pContext, &pDevice->null_device.operationEvent); + if (result != MA_SUCCESS) { + return result; + } + + result = ma_event_init(pContext, &pDevice->null_device.operationCompletionEvent); + if (result != MA_SUCCESS) { + return result; + } + + result = ma_thread_create(pContext, &pDevice->thread, ma_device_thread__null, pDevice); + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + +ma_result ma_device_start__null(ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + ma_device_do_operation__null(pDevice, MA_DEVICE_OP_START__NULL); + + ma_atomic_exchange_32(&pDevice->null_device.isStarted, MA_TRUE); + return MA_SUCCESS; +} + +ma_result ma_device_stop__null(ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + ma_device_do_operation__null(pDevice, MA_DEVICE_OP_SUSPEND__NULL); + + ma_atomic_exchange_32(&pDevice->null_device.isStarted, MA_FALSE); + return MA_SUCCESS; +} + +ma_result ma_device_write__null(ma_device* pDevice, const void* pPCMFrames, ma_uint32 frameCount) +{ + ma_result result = MA_SUCCESS; + ma_uint32 totalPCMFramesProcessed; + ma_bool32 wasStartedOnEntry; + + wasStartedOnEntry = pDevice->null_device.isStarted; + + /* Keep going until everything has been read. */ + totalPCMFramesProcessed = 0; + while (totalPCMFramesProcessed < frameCount) { + ma_uint64 targetFrame; + + /* If there are any frames remaining in the current period, consume those first. */ + if (pDevice->null_device.currentPeriodFramesRemainingPlayback > 0) { + ma_uint32 framesRemaining = (frameCount - totalPCMFramesProcessed); + ma_uint32 framesToProcess = pDevice->null_device.currentPeriodFramesRemainingPlayback; + if (framesToProcess > framesRemaining) { + framesToProcess = framesRemaining; + } + + /* We don't actually do anything with pPCMFrames, so just mark it as unused to prevent a warning. */ + (void)pPCMFrames; + + pDevice->null_device.currentPeriodFramesRemainingPlayback -= framesToProcess; + totalPCMFramesProcessed += framesToProcess; + } + + /* If we've consumed the current period we'll need to mark it as such an ensure the device is started if it's not already. */ + if (pDevice->null_device.currentPeriodFramesRemainingPlayback == 0) { + pDevice->null_device.currentPeriodFramesRemainingPlayback = 0; + + if (!pDevice->null_device.isStarted && !wasStartedOnEntry) { + result = ma_device_start__null(pDevice); + if (result != MA_SUCCESS) { + break; + } + } + } + + /* If we've consumed the whole buffer we can return now. */ + ma_assert(totalPCMFramesProcessed <= frameCount); + if (totalPCMFramesProcessed == frameCount) { + break; + } + + /* Getting here means we've still got more frames to consume, we but need to wait for it to become available. */ + targetFrame = pDevice->null_device.lastProcessedFramePlayback; + for (;;) { + ma_uint64 currentFrame; + + /* Stop waiting if the device has been stopped. */ + if (!pDevice->null_device.isStarted) { + break; + } + + currentFrame = ma_device_get_total_run_time_in_frames__null(pDevice); + if (currentFrame >= targetFrame) { + break; + } + + /* Getting here means we haven't yet reached the target sample, so continue waiting. */ + ma_sleep(10); + } + + pDevice->null_device.lastProcessedFramePlayback += pDevice->playback.internalBufferSizeInFrames / pDevice->playback.internalPeriods; + pDevice->null_device.currentPeriodFramesRemainingPlayback = pDevice->playback.internalBufferSizeInFrames / pDevice->playback.internalPeriods; + } + + return result; +} + +ma_result ma_device_read__null(ma_device* pDevice, void* pPCMFrames, ma_uint32 frameCount) +{ + ma_result result = MA_SUCCESS; + ma_uint32 totalPCMFramesProcessed; + + /* The device needs to be started immediately. */ + if (!pDevice->null_device.isStarted) { + result = ma_device_start__null(pDevice); + if (result != MA_SUCCESS) { + return result; + } + } + + /* Keep going until everything has been read. */ + totalPCMFramesProcessed = 0; + while (totalPCMFramesProcessed < frameCount) { + ma_uint64 targetFrame; + + /* If there are any frames remaining in the current period, consume those first. */ + if (pDevice->null_device.currentPeriodFramesRemainingCapture > 0) { + ma_uint32 bpf = ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + ma_uint32 framesRemaining = (frameCount - totalPCMFramesProcessed); + ma_uint32 framesToProcess = pDevice->null_device.currentPeriodFramesRemainingCapture; + if (framesToProcess > framesRemaining) { + framesToProcess = framesRemaining; + } + + /* We need to ensured the output buffer is zeroed. */ + ma_zero_memory(ma_offset_ptr(pPCMFrames, totalPCMFramesProcessed*bpf), framesToProcess*bpf); + + pDevice->null_device.currentPeriodFramesRemainingCapture -= framesToProcess; + totalPCMFramesProcessed += framesToProcess; + } + + /* If we've consumed the current period we'll need to mark it as such an ensure the device is started if it's not already. */ + if (pDevice->null_device.currentPeriodFramesRemainingCapture == 0) { + pDevice->null_device.currentPeriodFramesRemainingCapture = 0; + } + + /* If we've consumed the whole buffer we can return now. */ + ma_assert(totalPCMFramesProcessed <= frameCount); + if (totalPCMFramesProcessed == frameCount) { + break; + } + + /* Getting here means we've still got more frames to consume, we but need to wait for it to become available. */ + targetFrame = pDevice->null_device.lastProcessedFrameCapture + (pDevice->capture.internalBufferSizeInFrames / pDevice->capture.internalPeriods); + for (;;) { + ma_uint64 currentFrame; + + /* Stop waiting if the device has been stopped. */ + if (!pDevice->null_device.isStarted) { + break; + } + + currentFrame = ma_device_get_total_run_time_in_frames__null(pDevice); + if (currentFrame >= targetFrame) { + break; + } + + /* Getting here means we haven't yet reached the target sample, so continue waiting. */ + ma_sleep(10); + } + + pDevice->null_device.lastProcessedFrameCapture += pDevice->capture.internalBufferSizeInFrames / pDevice->capture.internalPeriods; + pDevice->null_device.currentPeriodFramesRemainingCapture = pDevice->capture.internalBufferSizeInFrames / pDevice->capture.internalPeriods; + } + + return result; +} + +ma_result ma_context_uninit__null(ma_context* pContext) +{ + ma_assert(pContext != NULL); + ma_assert(pContext->backend == ma_backend_null); + + (void)pContext; + return MA_SUCCESS; +} + +ma_result ma_context_init__null(const ma_context_config* pConfig, ma_context* pContext) +{ + ma_assert(pContext != NULL); + + (void)pConfig; + + 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 = ma_device_start__null; + pContext->onDeviceStop = ma_device_stop__null; + pContext->onDeviceWrite = ma_device_write__null; + pContext->onDeviceRead = ma_device_read__null; + + /* The null backend always works. */ + return MA_SUCCESS; +} +#endif + + +/******************************************************************************* + +WIN32 COMMON + +*******************************************************************************/ +#if defined(MA_WIN32) +#if defined(MA_WIN32_DESKTOP) + #define ma_CoInitializeEx(pContext, pvReserved, dwCoInit) ((MA_PFN_CoInitializeEx)pContext->win32.CoInitializeEx)(pvReserved, dwCoInit) + #define ma_CoUninitialize(pContext) ((MA_PFN_CoUninitialize)pContext->win32.CoUninitialize)() + #define ma_CoCreateInstance(pContext, rclsid, pUnkOuter, dwClsContext, riid, ppv) ((MA_PFN_CoCreateInstance)pContext->win32.CoCreateInstance)(rclsid, pUnkOuter, dwClsContext, riid, ppv) + #define ma_CoTaskMemFree(pContext, pv) ((MA_PFN_CoTaskMemFree)pContext->win32.CoTaskMemFree)(pv) + #define ma_PropVariantClear(pContext, pvar) ((MA_PFN_PropVariantClear)pContext->win32.PropVariantClear)(pvar) +#else + #define ma_CoInitializeEx(pContext, pvReserved, dwCoInit) CoInitializeEx(pvReserved, dwCoInit) + #define ma_CoUninitialize(pContext) CoUninitialize() + #define ma_CoCreateInstance(pContext, rclsid, pUnkOuter, dwClsContext, riid, ppv) CoCreateInstance(rclsid, pUnkOuter, dwClsContext, riid, ppv) + #define ma_CoTaskMemFree(pContext, pv) CoTaskMemFree(pv) + #define ma_PropVariantClear(pContext, pvar) PropVariantClear(pvar) +#endif + +#if !defined(MAXULONG_PTR) +typedef size_t DWORD_PTR; +#endif + +#if !defined(WAVE_FORMAT_44M08) +#define WAVE_FORMAT_44M08 0x00000100 +#define WAVE_FORMAT_44S08 0x00000200 +#define WAVE_FORMAT_44M16 0x00000400 +#define WAVE_FORMAT_44S16 0x00000800 +#define WAVE_FORMAT_48M08 0x00001000 +#define WAVE_FORMAT_48S08 0x00002000 +#define WAVE_FORMAT_48M16 0x00004000 +#define WAVE_FORMAT_48S16 0x00008000 +#define WAVE_FORMAT_96M08 0x00010000 +#define WAVE_FORMAT_96S08 0x00020000 +#define WAVE_FORMAT_96M16 0x00040000 +#define WAVE_FORMAT_96S16 0x00080000 +#endif + +#ifndef SPEAKER_FRONT_LEFT +#define SPEAKER_FRONT_LEFT 0x1 +#define SPEAKER_FRONT_RIGHT 0x2 +#define SPEAKER_FRONT_CENTER 0x4 +#define SPEAKER_LOW_FREQUENCY 0x8 +#define SPEAKER_BACK_LEFT 0x10 +#define SPEAKER_BACK_RIGHT 0x20 +#define SPEAKER_FRONT_LEFT_OF_CENTER 0x40 +#define SPEAKER_FRONT_RIGHT_OF_CENTER 0x80 +#define SPEAKER_BACK_CENTER 0x100 +#define SPEAKER_SIDE_LEFT 0x200 +#define SPEAKER_SIDE_RIGHT 0x400 +#define SPEAKER_TOP_CENTER 0x800 +#define SPEAKER_TOP_FRONT_LEFT 0x1000 +#define SPEAKER_TOP_FRONT_CENTER 0x2000 +#define SPEAKER_TOP_FRONT_RIGHT 0x4000 +#define SPEAKER_TOP_BACK_LEFT 0x8000 +#define SPEAKER_TOP_BACK_CENTER 0x10000 +#define SPEAKER_TOP_BACK_RIGHT 0x20000 +#endif + +/* +The SDK that comes with old versions of MSVC (VC6, for example) does not appear to define WAVEFORMATEXTENSIBLE. We +define our own implementation in this case. +*/ +#if (defined(_MSC_VER) && !defined(_WAVEFORMATEXTENSIBLE_)) || defined(__DMC__) +typedef struct +{ + WAVEFORMATEX Format; + union + { + WORD wValidBitsPerSample; + WORD wSamplesPerBlock; + WORD wReserved; + } Samples; + DWORD dwChannelMask; + GUID SubFormat; +} WAVEFORMATEXTENSIBLE; +#endif + +#ifndef WAVE_FORMAT_EXTENSIBLE +#define WAVE_FORMAT_EXTENSIBLE 0xFFFE +#endif + +#ifndef WAVE_FORMAT_IEEE_FLOAT +#define WAVE_FORMAT_IEEE_FLOAT 0x0003 +#endif + +GUID MA_GUID_NULL = {0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; + +/* Converts an individual Win32-style channel identifier (SPEAKER_FRONT_LEFT, etc.) to miniaudio. */ +ma_uint8 ma_channel_id_to_ma__win32(DWORD id) +{ + switch (id) + { + case SPEAKER_FRONT_LEFT: return MA_CHANNEL_FRONT_LEFT; + case SPEAKER_FRONT_RIGHT: return MA_CHANNEL_FRONT_RIGHT; + case SPEAKER_FRONT_CENTER: return MA_CHANNEL_FRONT_CENTER; + case SPEAKER_LOW_FREQUENCY: return MA_CHANNEL_LFE; + case SPEAKER_BACK_LEFT: return MA_CHANNEL_BACK_LEFT; + case SPEAKER_BACK_RIGHT: return MA_CHANNEL_BACK_RIGHT; + case SPEAKER_FRONT_LEFT_OF_CENTER: return MA_CHANNEL_FRONT_LEFT_CENTER; + case SPEAKER_FRONT_RIGHT_OF_CENTER: return MA_CHANNEL_FRONT_RIGHT_CENTER; + case SPEAKER_BACK_CENTER: return MA_CHANNEL_BACK_CENTER; + case SPEAKER_SIDE_LEFT: return MA_CHANNEL_SIDE_LEFT; + case SPEAKER_SIDE_RIGHT: return MA_CHANNEL_SIDE_RIGHT; + case SPEAKER_TOP_CENTER: return MA_CHANNEL_TOP_CENTER; + case SPEAKER_TOP_FRONT_LEFT: return MA_CHANNEL_TOP_FRONT_LEFT; + case SPEAKER_TOP_FRONT_CENTER: return MA_CHANNEL_TOP_FRONT_CENTER; + case SPEAKER_TOP_FRONT_RIGHT: return MA_CHANNEL_TOP_FRONT_RIGHT; + case SPEAKER_TOP_BACK_LEFT: return MA_CHANNEL_TOP_BACK_LEFT; + case SPEAKER_TOP_BACK_CENTER: return MA_CHANNEL_TOP_BACK_CENTER; + case SPEAKER_TOP_BACK_RIGHT: return MA_CHANNEL_TOP_BACK_RIGHT; + default: return 0; + } +} + +/* Converts an individual miniaudio channel identifier (MA_CHANNEL_FRONT_LEFT, etc.) to Win32-style. */ +DWORD ma_channel_id_to_win32(DWORD id) +{ + switch (id) + { + case MA_CHANNEL_MONO: return SPEAKER_FRONT_CENTER; + case MA_CHANNEL_FRONT_LEFT: return SPEAKER_FRONT_LEFT; + case MA_CHANNEL_FRONT_RIGHT: return SPEAKER_FRONT_RIGHT; + case MA_CHANNEL_FRONT_CENTER: return SPEAKER_FRONT_CENTER; + case MA_CHANNEL_LFE: return SPEAKER_LOW_FREQUENCY; + case MA_CHANNEL_BACK_LEFT: return SPEAKER_BACK_LEFT; + case MA_CHANNEL_BACK_RIGHT: return SPEAKER_BACK_RIGHT; + case MA_CHANNEL_FRONT_LEFT_CENTER: return SPEAKER_FRONT_LEFT_OF_CENTER; + case MA_CHANNEL_FRONT_RIGHT_CENTER: return SPEAKER_FRONT_RIGHT_OF_CENTER; + case MA_CHANNEL_BACK_CENTER: return SPEAKER_BACK_CENTER; + case MA_CHANNEL_SIDE_LEFT: return SPEAKER_SIDE_LEFT; + case MA_CHANNEL_SIDE_RIGHT: return SPEAKER_SIDE_RIGHT; + case MA_CHANNEL_TOP_CENTER: return SPEAKER_TOP_CENTER; + case MA_CHANNEL_TOP_FRONT_LEFT: return SPEAKER_TOP_FRONT_LEFT; + case MA_CHANNEL_TOP_FRONT_CENTER: return SPEAKER_TOP_FRONT_CENTER; + case MA_CHANNEL_TOP_FRONT_RIGHT: return SPEAKER_TOP_FRONT_RIGHT; + case MA_CHANNEL_TOP_BACK_LEFT: return SPEAKER_TOP_BACK_LEFT; + case MA_CHANNEL_TOP_BACK_CENTER: return SPEAKER_TOP_BACK_CENTER; + case MA_CHANNEL_TOP_BACK_RIGHT: return SPEAKER_TOP_BACK_RIGHT; + default: return 0; + } +} + +/* Converts a channel mapping to a Win32-style channel mask. */ +DWORD ma_channel_map_to_channel_mask__win32(const ma_channel channelMap[MA_MAX_CHANNELS], ma_uint32 channels) +{ + DWORD dwChannelMask = 0; + ma_uint32 iChannel; + + for (iChannel = 0; iChannel < channels; ++iChannel) { + dwChannelMask |= ma_channel_id_to_win32(channelMap[iChannel]); + } + + return dwChannelMask; +} + +/* Converts a Win32-style channel mask to a miniaudio channel map. */ +void ma_channel_mask_to_channel_map__win32(DWORD dwChannelMask, ma_uint32 channels, ma_channel channelMap[MA_MAX_CHANNELS]) +{ + if (channels == 1 && dwChannelMask == 0) { + channelMap[0] = MA_CHANNEL_MONO; + } else if (channels == 2 && dwChannelMask == 0) { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + } else { + if (channels == 1 && (dwChannelMask & SPEAKER_FRONT_CENTER) != 0) { + channelMap[0] = MA_CHANNEL_MONO; + } else { + /* Just iterate over each bit. */ + ma_uint32 iChannel = 0; + ma_uint32 iBit; + + for (iBit = 0; iBit < 32; ++iBit) { + DWORD bitValue = (dwChannelMask & (1UL << iBit)); + if (bitValue != 0) { + /* The bit is set. */ + channelMap[iChannel] = ma_channel_id_to_ma__win32(bitValue); + iChannel += 1; + } + } + } + } +} + +#ifdef __cplusplus +ma_bool32 ma_is_guid_equal(const void* a, const void* b) +{ + return IsEqualGUID(*(const GUID*)a, *(const GUID*)b); +} +#else +#define ma_is_guid_equal(a, b) IsEqualGUID((const GUID*)a, (const GUID*)b) +#endif + +ma_format ma_format_from_WAVEFORMATEX(const WAVEFORMATEX* pWF) +{ + ma_assert(pWF != NULL); + + if (pWF->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + const WAVEFORMATEXTENSIBLE* pWFEX = (const WAVEFORMATEXTENSIBLE*)pWF; + if (ma_is_guid_equal(&pWFEX->SubFormat, &MA_GUID_KSDATAFORMAT_SUBTYPE_PCM)) { + if (pWFEX->Samples.wValidBitsPerSample == 32) { + return ma_format_s32; + } + if (pWFEX->Samples.wValidBitsPerSample == 24) { + if (pWFEX->Format.wBitsPerSample == 32) { + /*return ma_format_s24_32;*/ + } + if (pWFEX->Format.wBitsPerSample == 24) { + return ma_format_s24; + } + } + if (pWFEX->Samples.wValidBitsPerSample == 16) { + return ma_format_s16; + } + if (pWFEX->Samples.wValidBitsPerSample == 8) { + return ma_format_u8; + } + } + if (ma_is_guid_equal(&pWFEX->SubFormat, &MA_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) { + if (pWFEX->Samples.wValidBitsPerSample == 32) { + return ma_format_f32; + } + /* + if (pWFEX->Samples.wValidBitsPerSample == 64) { + return ma_format_f64; + } + */ + } + } else { + if (pWF->wFormatTag == WAVE_FORMAT_PCM) { + if (pWF->wBitsPerSample == 32) { + return ma_format_s32; + } + if (pWF->wBitsPerSample == 24) { + return ma_format_s24; + } + if (pWF->wBitsPerSample == 16) { + return ma_format_s16; + } + if (pWF->wBitsPerSample == 8) { + return ma_format_u8; + } + } + if (pWF->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) { + if (pWF->wBitsPerSample == 32) { + return ma_format_f32; + } + if (pWF->wBitsPerSample == 64) { + /*return ma_format_f64;*/ + } + } + } + + return ma_format_unknown; +} +#endif + + +/******************************************************************************* + +WASAPI Backend + +*******************************************************************************/ +#ifdef MA_HAS_WASAPI +#if 0 +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable:4091) /* 'typedef ': ignored on left of '' when no variable is declared */ +#endif +#include +#include +#if defined(_MSC_VER) + #pragma warning(pop) +#endif +#endif /* 0 */ + + +/* Some compilers don't define VerifyVersionInfoW. Need to write this ourselves. */ +#define MA_WIN32_WINNT_VISTA 0x0600 +#define MA_VER_MINORVERSION 0x01 +#define MA_VER_MAJORVERSION 0x02 +#define MA_VER_SERVICEPACKMAJOR 0x20 +#define MA_VER_GREATER_EQUAL 0x03 + +typedef struct { + DWORD dwOSVersionInfoSize; + DWORD dwMajorVersion; + DWORD dwMinorVersion; + DWORD dwBuildNumber; + DWORD dwPlatformId; + WCHAR szCSDVersion[128]; + WORD wServicePackMajor; + WORD wServicePackMinor; + WORD wSuiteMask; + BYTE wProductType; + BYTE wReserved; +} ma_OSVERSIONINFOEXW; + +typedef BOOL (WINAPI * ma_PFNVerifyVersionInfoW) (ma_OSVERSIONINFOEXW* lpVersionInfo, DWORD dwTypeMask, DWORDLONG dwlConditionMask); +typedef ULONGLONG (WINAPI * ma_PFNVerSetConditionMask)(ULONGLONG dwlConditionMask, DWORD dwTypeBitMask, BYTE dwConditionMask); + + +#ifndef PROPERTYKEY_DEFINED +#define PROPERTYKEY_DEFINED +typedef struct +{ + GUID fmtid; + DWORD pid; +} PROPERTYKEY; +#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) +{ + ma_zero_object(pProp); +} + + +const PROPERTYKEY MA_PKEY_Device_FriendlyName = {{0xA45C254E, 0xDF1C, 0x4EFD, {0x80, 0x20, 0x67, 0xD1, 0x46, 0xA8, 0x50, 0xE0}}, 14}; +const PROPERTYKEY MA_PKEY_AudioEngine_DeviceFormat = {{0xF19F064D, 0x82C, 0x4E27, {0xBC, 0x73, 0x68, 0x82, 0xA1, 0xBB, 0x8E, 0x4C}}, 0}; + +const IID MA_IID_IUnknown = {0x00000000, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; /* 00000000-0000-0000-C000-000000000046 */ +const IID MA_IID_IAgileObject = {0x94EA2B94, 0xE9CC, 0x49E0, {0xC0, 0xFF, 0xEE, 0x64, 0xCA, 0x8F, 0x5B, 0x90}}; /* 94EA2B94-E9CC-49E0-C0FF-EE64CA8F5B90 */ + +const IID MA_IID_IAudioClient = {0x1CB9AD4C, 0xDBFA, 0x4C32, {0xB1, 0x78, 0xC2, 0xF5, 0x68, 0xA7, 0x03, 0xB2}}; /* 1CB9AD4C-DBFA-4C32-B178-C2F568A703B2 = __uuidof(IAudioClient) */ +const IID MA_IID_IAudioClient2 = {0x726778CD, 0xF60A, 0x4EDA, {0x82, 0xDE, 0xE4, 0x76, 0x10, 0xCD, 0x78, 0xAA}}; /* 726778CD-F60A-4EDA-82DE-E47610CD78AA = __uuidof(IAudioClient2) */ +const IID MA_IID_IAudioClient3 = {0x7ED4EE07, 0x8E67, 0x4CD4, {0x8C, 0x1A, 0x2B, 0x7A, 0x59, 0x87, 0xAD, 0x42}}; /* 7ED4EE07-8E67-4CD4-8C1A-2B7A5987AD42 = __uuidof(IAudioClient3) */ +const IID MA_IID_IAudioRenderClient = {0xF294ACFC, 0x3146, 0x4483, {0xA7, 0xBF, 0xAD, 0xDC, 0xA7, 0xC2, 0x60, 0xE2}}; /* F294ACFC-3146-4483-A7BF-ADDCA7C260E2 = __uuidof(IAudioRenderClient) */ +const IID MA_IID_IAudioCaptureClient = {0xC8ADBD64, 0xE71E, 0x48A0, {0xA4, 0xDE, 0x18, 0x5C, 0x39, 0x5C, 0xD3, 0x17}}; /* C8ADBD64-E71E-48A0-A4DE-185C395CD317 = __uuidof(IAudioCaptureClient) */ +const IID MA_IID_IMMNotificationClient = {0x7991EEC9, 0x7E89, 0x4D85, {0x83, 0x90, 0x6C, 0x70, 0x3C, 0xEC, 0x60, 0xC0}}; /* 7991EEC9-7E89-4D85-8390-6C703CEC60C0 = __uuidof(IMMNotificationClient) */ +#ifndef MA_WIN32_DESKTOP +const IID MA_IID_DEVINTERFACE_AUDIO_RENDER = {0xE6327CAD, 0xDCEC, 0x4949, {0xAE, 0x8A, 0x99, 0x1E, 0x97, 0x6A, 0x79, 0xD2}}; /* E6327CAD-DCEC-4949-AE8A-991E976A79D2 */ +const IID MA_IID_DEVINTERFACE_AUDIO_CAPTURE = {0x2EEF81BE, 0x33FA, 0x4800, {0x96, 0x70, 0x1C, 0xD4, 0x74, 0x97, 0x2C, 0x3F}}; /* 2EEF81BE-33FA-4800-9670-1CD474972C3F */ +const IID MA_IID_IActivateAudioInterfaceCompletionHandler = {0x41D949AB, 0x9862, 0x444A, {0x80, 0xF6, 0xC2, 0x61, 0x33, 0x4D, 0xA5, 0xEB}}; /* 41D949AB-9862-444A-80F6-C261334DA5EB */ +#endif + +const IID MA_CLSID_MMDeviceEnumerator_Instance = {0xBCDE0395, 0xE52F, 0x467C, {0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E}}; /* BCDE0395-E52F-467C-8E3D-C4579291692E = __uuidof(MMDeviceEnumerator) */ +const IID MA_IID_IMMDeviceEnumerator_Instance = {0xA95664D2, 0x9614, 0x4F35, {0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6}}; /* A95664D2-9614-4F35-A746-DE8DB63617E6 = __uuidof(IMMDeviceEnumerator) */ +#ifdef __cplusplus +#define MA_CLSID_MMDeviceEnumerator MA_CLSID_MMDeviceEnumerator_Instance +#define MA_IID_IMMDeviceEnumerator MA_IID_IMMDeviceEnumerator_Instance +#else +#define MA_CLSID_MMDeviceEnumerator &MA_CLSID_MMDeviceEnumerator_Instance +#define MA_IID_IMMDeviceEnumerator &MA_IID_IMMDeviceEnumerator_Instance +#endif + +typedef struct ma_IUnknown ma_IUnknown; +#ifdef MA_WIN32_DESKTOP +#define MA_MM_DEVICE_STATE_ACTIVE 1 +#define MA_MM_DEVICE_STATE_DISABLED 2 +#define MA_MM_DEVICE_STATE_NOTPRESENT 4 +#define MA_MM_DEVICE_STATE_UNPLUGGED 8 + +typedef struct ma_IMMDeviceEnumerator ma_IMMDeviceEnumerator; +typedef struct ma_IMMDeviceCollection ma_IMMDeviceCollection; +typedef struct ma_IMMDevice ma_IMMDevice; +#else +typedef struct ma_IActivateAudioInterfaceCompletionHandler ma_IActivateAudioInterfaceCompletionHandler; +typedef struct ma_IActivateAudioInterfaceAsyncOperation ma_IActivateAudioInterfaceAsyncOperation; +#endif +typedef struct ma_IPropertyStore ma_IPropertyStore; +typedef struct ma_IAudioClient ma_IAudioClient; +typedef struct ma_IAudioClient2 ma_IAudioClient2; +typedef struct ma_IAudioClient3 ma_IAudioClient3; +typedef struct ma_IAudioRenderClient ma_IAudioRenderClient; +typedef struct ma_IAudioCaptureClient ma_IAudioCaptureClient; + +typedef ma_int64 MA_REFERENCE_TIME; + +#define MA_AUDCLNT_STREAMFLAGS_CROSSPROCESS 0x00010000 +#define MA_AUDCLNT_STREAMFLAGS_LOOPBACK 0x00020000 +#define MA_AUDCLNT_STREAMFLAGS_EVENTCALLBACK 0x00040000 +#define MA_AUDCLNT_STREAMFLAGS_NOPERSIST 0x00080000 +#define MA_AUDCLNT_STREAMFLAGS_RATEADJUST 0x00100000 +#define MA_AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000 +#define MA_AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000 +#define MA_AUDCLNT_SESSIONFLAGS_EXPIREWHENUNOWNED 0x10000000 +#define MA_AUDCLNT_SESSIONFLAGS_DISPLAY_HIDE 0x20000000 +#define MA_AUDCLNT_SESSIONFLAGS_DISPLAY_HIDEWHENEXPIRED 0x40000000 + +/* We only care about a few error codes. */ +#define MA_AUDCLNT_E_INVALID_DEVICE_PERIOD (-2004287456) +#define MA_AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED (-2004287463) +#define MA_AUDCLNT_S_BUFFER_EMPTY (143196161) +#define MA_AUDCLNT_E_DEVICE_IN_USE (-2004287478) + +typedef enum +{ + ma_eRender = 0, + ma_eCapture = 1, + ma_eAll = 2 +} ma_EDataFlow; + +typedef enum +{ + ma_eConsole = 0, + ma_eMultimedia = 1, + ma_eCommunications = 2 +} ma_ERole; + +typedef enum +{ + MA_AUDCLNT_SHAREMODE_SHARED, + MA_AUDCLNT_SHAREMODE_EXCLUSIVE +} MA_AUDCLNT_SHAREMODE; + +typedef enum +{ + MA_AudioCategory_Other = 0 /* <-- miniaudio is only caring about Other. */ +} MA_AUDIO_STREAM_CATEGORY; + +typedef struct +{ + UINT32 cbSize; + BOOL bIsOffload; + MA_AUDIO_STREAM_CATEGORY eCategory; +} ma_AudioClientProperties; + +/* IUnknown */ +typedef struct +{ + /* IUnknown */ + HRESULT (STDMETHODCALLTYPE * QueryInterface)(ma_IUnknown* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (ma_IUnknown* pThis); + ULONG (STDMETHODCALLTYPE * Release) (ma_IUnknown* pThis); +} ma_IUnknownVtbl; +struct ma_IUnknown +{ + ma_IUnknownVtbl* lpVtbl; +}; +HRESULT ma_IUnknown_QueryInterface(ma_IUnknown* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +ULONG ma_IUnknown_AddRef(ma_IUnknown* pThis) { return pThis->lpVtbl->AddRef(pThis); } +ULONG ma_IUnknown_Release(ma_IUnknown* pThis) { return pThis->lpVtbl->Release(pThis); } + +#ifdef MA_WIN32_DESKTOP + /* IMMNotificationClient */ + typedef struct + { + /* IUnknown */ + HRESULT (STDMETHODCALLTYPE * QueryInterface)(ma_IMMNotificationClient* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (ma_IMMNotificationClient* pThis); + ULONG (STDMETHODCALLTYPE * Release) (ma_IMMNotificationClient* pThis); + + /* IMMNotificationClient */ + HRESULT (STDMETHODCALLTYPE * OnDeviceStateChanged) (ma_IMMNotificationClient* pThis, LPCWSTR pDeviceID, DWORD dwNewState); + HRESULT (STDMETHODCALLTYPE * OnDeviceAdded) (ma_IMMNotificationClient* pThis, LPCWSTR pDeviceID); + HRESULT (STDMETHODCALLTYPE * OnDeviceRemoved) (ma_IMMNotificationClient* pThis, LPCWSTR pDeviceID); + HRESULT (STDMETHODCALLTYPE * OnDefaultDeviceChanged)(ma_IMMNotificationClient* pThis, ma_EDataFlow dataFlow, ma_ERole role, LPCWSTR pDefaultDeviceID); + HRESULT (STDMETHODCALLTYPE * OnPropertyValueChanged)(ma_IMMNotificationClient* pThis, LPCWSTR pDeviceID, const PROPERTYKEY key); + } ma_IMMNotificationClientVtbl; + + /* IMMDeviceEnumerator */ + typedef struct + { + /* IUnknown */ + HRESULT (STDMETHODCALLTYPE * QueryInterface)(ma_IMMDeviceEnumerator* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (ma_IMMDeviceEnumerator* pThis); + ULONG (STDMETHODCALLTYPE * Release) (ma_IMMDeviceEnumerator* pThis); + + /* IMMDeviceEnumerator */ + HRESULT (STDMETHODCALLTYPE * EnumAudioEndpoints) (ma_IMMDeviceEnumerator* pThis, ma_EDataFlow dataFlow, DWORD dwStateMask, ma_IMMDeviceCollection** ppDevices); + HRESULT (STDMETHODCALLTYPE * GetDefaultAudioEndpoint) (ma_IMMDeviceEnumerator* pThis, ma_EDataFlow dataFlow, ma_ERole role, ma_IMMDevice** ppEndpoint); + HRESULT (STDMETHODCALLTYPE * GetDevice) (ma_IMMDeviceEnumerator* pThis, LPCWSTR pID, ma_IMMDevice** ppDevice); + HRESULT (STDMETHODCALLTYPE * RegisterEndpointNotificationCallback) (ma_IMMDeviceEnumerator* pThis, ma_IMMNotificationClient* pClient); + HRESULT (STDMETHODCALLTYPE * UnregisterEndpointNotificationCallback)(ma_IMMDeviceEnumerator* pThis, ma_IMMNotificationClient* pClient); + } ma_IMMDeviceEnumeratorVtbl; + struct ma_IMMDeviceEnumerator + { + ma_IMMDeviceEnumeratorVtbl* lpVtbl; + }; + HRESULT ma_IMMDeviceEnumerator_QueryInterface(ma_IMMDeviceEnumerator* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } + ULONG ma_IMMDeviceEnumerator_AddRef(ma_IMMDeviceEnumerator* pThis) { return pThis->lpVtbl->AddRef(pThis); } + ULONG ma_IMMDeviceEnumerator_Release(ma_IMMDeviceEnumerator* pThis) { return pThis->lpVtbl->Release(pThis); } + HRESULT ma_IMMDeviceEnumerator_EnumAudioEndpoints(ma_IMMDeviceEnumerator* pThis, ma_EDataFlow dataFlow, DWORD dwStateMask, ma_IMMDeviceCollection** ppDevices) { return pThis->lpVtbl->EnumAudioEndpoints(pThis, dataFlow, dwStateMask, ppDevices); } + HRESULT ma_IMMDeviceEnumerator_GetDefaultAudioEndpoint(ma_IMMDeviceEnumerator* pThis, ma_EDataFlow dataFlow, ma_ERole role, ma_IMMDevice** ppEndpoint) { return pThis->lpVtbl->GetDefaultAudioEndpoint(pThis, dataFlow, role, ppEndpoint); } + HRESULT ma_IMMDeviceEnumerator_GetDevice(ma_IMMDeviceEnumerator* pThis, LPCWSTR pID, ma_IMMDevice** ppDevice) { return pThis->lpVtbl->GetDevice(pThis, pID, ppDevice); } + HRESULT ma_IMMDeviceEnumerator_RegisterEndpointNotificationCallback(ma_IMMDeviceEnumerator* pThis, ma_IMMNotificationClient* pClient) { return pThis->lpVtbl->RegisterEndpointNotificationCallback(pThis, pClient); } + HRESULT ma_IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(ma_IMMDeviceEnumerator* pThis, ma_IMMNotificationClient* pClient) { return pThis->lpVtbl->UnregisterEndpointNotificationCallback(pThis, pClient); } + + + /* IMMDeviceCollection */ + typedef struct + { + /* IUnknown */ + HRESULT (STDMETHODCALLTYPE * QueryInterface)(ma_IMMDeviceCollection* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (ma_IMMDeviceCollection* pThis); + ULONG (STDMETHODCALLTYPE * Release) (ma_IMMDeviceCollection* pThis); + + /* IMMDeviceCollection */ + HRESULT (STDMETHODCALLTYPE * GetCount)(ma_IMMDeviceCollection* pThis, UINT* pDevices); + HRESULT (STDMETHODCALLTYPE * Item) (ma_IMMDeviceCollection* pThis, UINT nDevice, ma_IMMDevice** ppDevice); + } ma_IMMDeviceCollectionVtbl; + struct ma_IMMDeviceCollection + { + ma_IMMDeviceCollectionVtbl* lpVtbl; + }; + HRESULT ma_IMMDeviceCollection_QueryInterface(ma_IMMDeviceCollection* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } + ULONG ma_IMMDeviceCollection_AddRef(ma_IMMDeviceCollection* pThis) { return pThis->lpVtbl->AddRef(pThis); } + ULONG ma_IMMDeviceCollection_Release(ma_IMMDeviceCollection* pThis) { return pThis->lpVtbl->Release(pThis); } + HRESULT ma_IMMDeviceCollection_GetCount(ma_IMMDeviceCollection* pThis, UINT* pDevices) { return pThis->lpVtbl->GetCount(pThis, pDevices); } + HRESULT ma_IMMDeviceCollection_Item(ma_IMMDeviceCollection* pThis, UINT nDevice, ma_IMMDevice** ppDevice) { return pThis->lpVtbl->Item(pThis, nDevice, ppDevice); } + + + /* IMMDevice */ + typedef struct + { + /* IUnknown */ + HRESULT (STDMETHODCALLTYPE * QueryInterface)(ma_IMMDevice* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (ma_IMMDevice* pThis); + ULONG (STDMETHODCALLTYPE * Release) (ma_IMMDevice* pThis); + + /* IMMDevice */ + HRESULT (STDMETHODCALLTYPE * Activate) (ma_IMMDevice* pThis, const IID* const iid, DWORD dwClsCtx, PROPVARIANT* pActivationParams, void** ppInterface); + HRESULT (STDMETHODCALLTYPE * OpenPropertyStore)(ma_IMMDevice* pThis, DWORD stgmAccess, ma_IPropertyStore** ppProperties); + HRESULT (STDMETHODCALLTYPE * GetId) (ma_IMMDevice* pThis, LPWSTR *pID); + HRESULT (STDMETHODCALLTYPE * GetState) (ma_IMMDevice* pThis, DWORD *pState); + } ma_IMMDeviceVtbl; + struct ma_IMMDevice + { + ma_IMMDeviceVtbl* lpVtbl; + }; + HRESULT ma_IMMDevice_QueryInterface(ma_IMMDevice* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } + ULONG ma_IMMDevice_AddRef(ma_IMMDevice* pThis) { return pThis->lpVtbl->AddRef(pThis); } + ULONG ma_IMMDevice_Release(ma_IMMDevice* pThis) { return pThis->lpVtbl->Release(pThis); } + HRESULT ma_IMMDevice_Activate(ma_IMMDevice* pThis, const IID* const iid, DWORD dwClsCtx, PROPVARIANT* pActivationParams, void** ppInterface) { return pThis->lpVtbl->Activate(pThis, iid, dwClsCtx, pActivationParams, ppInterface); } + HRESULT ma_IMMDevice_OpenPropertyStore(ma_IMMDevice* pThis, DWORD stgmAccess, ma_IPropertyStore** ppProperties) { return pThis->lpVtbl->OpenPropertyStore(pThis, stgmAccess, ppProperties); } + HRESULT ma_IMMDevice_GetId(ma_IMMDevice* pThis, LPWSTR *pID) { return pThis->lpVtbl->GetId(pThis, pID); } + HRESULT ma_IMMDevice_GetState(ma_IMMDevice* pThis, DWORD *pState) { return pThis->lpVtbl->GetState(pThis, pState); } +#else + /* IActivateAudioInterfaceAsyncOperation */ + typedef struct + { + /* IUnknown */ + HRESULT (STDMETHODCALLTYPE * QueryInterface)(ma_IActivateAudioInterfaceAsyncOperation* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (ma_IActivateAudioInterfaceAsyncOperation* pThis); + ULONG (STDMETHODCALLTYPE * Release) (ma_IActivateAudioInterfaceAsyncOperation* pThis); + + /* IActivateAudioInterfaceAsyncOperation */ + HRESULT (STDMETHODCALLTYPE * GetActivateResult)(ma_IActivateAudioInterfaceAsyncOperation* pThis, HRESULT *pActivateResult, ma_IUnknown** ppActivatedInterface); + } ma_IActivateAudioInterfaceAsyncOperationVtbl; + struct ma_IActivateAudioInterfaceAsyncOperation + { + ma_IActivateAudioInterfaceAsyncOperationVtbl* lpVtbl; + }; + HRESULT ma_IActivateAudioInterfaceAsyncOperation_QueryInterface(ma_IActivateAudioInterfaceAsyncOperation* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } + ULONG ma_IActivateAudioInterfaceAsyncOperation_AddRef(ma_IActivateAudioInterfaceAsyncOperation* pThis) { return pThis->lpVtbl->AddRef(pThis); } + ULONG ma_IActivateAudioInterfaceAsyncOperation_Release(ma_IActivateAudioInterfaceAsyncOperation* pThis) { return pThis->lpVtbl->Release(pThis); } + HRESULT ma_IActivateAudioInterfaceAsyncOperation_GetActivateResult(ma_IActivateAudioInterfaceAsyncOperation* pThis, HRESULT *pActivateResult, ma_IUnknown** ppActivatedInterface) { return pThis->lpVtbl->GetActivateResult(pThis, pActivateResult, ppActivatedInterface); } +#endif + +/* IPropertyStore */ +typedef struct +{ + /* IUnknown */ + HRESULT (STDMETHODCALLTYPE * QueryInterface)(ma_IPropertyStore* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (ma_IPropertyStore* pThis); + ULONG (STDMETHODCALLTYPE * Release) (ma_IPropertyStore* pThis); + + /* IPropertyStore */ + HRESULT (STDMETHODCALLTYPE * GetCount)(ma_IPropertyStore* pThis, DWORD* pPropCount); + HRESULT (STDMETHODCALLTYPE * GetAt) (ma_IPropertyStore* pThis, DWORD propIndex, PROPERTYKEY* pPropKey); + HRESULT (STDMETHODCALLTYPE * GetValue)(ma_IPropertyStore* pThis, const PROPERTYKEY* const pKey, PROPVARIANT* pPropVar); + HRESULT (STDMETHODCALLTYPE * SetValue)(ma_IPropertyStore* pThis, const PROPERTYKEY* const pKey, const PROPVARIANT* const pPropVar); + HRESULT (STDMETHODCALLTYPE * Commit) (ma_IPropertyStore* pThis); +} ma_IPropertyStoreVtbl; +struct ma_IPropertyStore +{ + ma_IPropertyStoreVtbl* lpVtbl; +}; +HRESULT ma_IPropertyStore_QueryInterface(ma_IPropertyStore* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +ULONG ma_IPropertyStore_AddRef(ma_IPropertyStore* pThis) { return pThis->lpVtbl->AddRef(pThis); } +ULONG ma_IPropertyStore_Release(ma_IPropertyStore* pThis) { return pThis->lpVtbl->Release(pThis); } +HRESULT ma_IPropertyStore_GetCount(ma_IPropertyStore* pThis, DWORD* pPropCount) { return pThis->lpVtbl->GetCount(pThis, pPropCount); } +HRESULT ma_IPropertyStore_GetAt(ma_IPropertyStore* pThis, DWORD propIndex, PROPERTYKEY* pPropKey) { return pThis->lpVtbl->GetAt(pThis, propIndex, pPropKey); } +HRESULT ma_IPropertyStore_GetValue(ma_IPropertyStore* pThis, const PROPERTYKEY* const pKey, PROPVARIANT* pPropVar) { return pThis->lpVtbl->GetValue(pThis, pKey, pPropVar); } +HRESULT ma_IPropertyStore_SetValue(ma_IPropertyStore* pThis, const PROPERTYKEY* const pKey, const PROPVARIANT* const pPropVar) { return pThis->lpVtbl->SetValue(pThis, pKey, pPropVar); } +HRESULT ma_IPropertyStore_Commit(ma_IPropertyStore* pThis) { return pThis->lpVtbl->Commit(pThis); } + + +/* IAudioClient */ +typedef struct +{ + /* IUnknown */ + HRESULT (STDMETHODCALLTYPE * QueryInterface)(ma_IAudioClient* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (ma_IAudioClient* pThis); + ULONG (STDMETHODCALLTYPE * Release) (ma_IAudioClient* pThis); + + /* IAudioClient */ + HRESULT (STDMETHODCALLTYPE * Initialize) (ma_IAudioClient* pThis, MA_AUDCLNT_SHAREMODE shareMode, DWORD streamFlags, MA_REFERENCE_TIME bufferDuration, MA_REFERENCE_TIME periodicity, const WAVEFORMATEX* pFormat, const GUID* pAudioSessionGuid); + HRESULT (STDMETHODCALLTYPE * GetBufferSize) (ma_IAudioClient* pThis, ma_uint32* pNumBufferFrames); + HRESULT (STDMETHODCALLTYPE * GetStreamLatency) (ma_IAudioClient* pThis, MA_REFERENCE_TIME* pLatency); + HRESULT (STDMETHODCALLTYPE * GetCurrentPadding)(ma_IAudioClient* pThis, ma_uint32* pNumPaddingFrames); + HRESULT (STDMETHODCALLTYPE * IsFormatSupported)(ma_IAudioClient* pThis, MA_AUDCLNT_SHAREMODE shareMode, const WAVEFORMATEX* pFormat, WAVEFORMATEX** ppClosestMatch); + HRESULT (STDMETHODCALLTYPE * GetMixFormat) (ma_IAudioClient* pThis, WAVEFORMATEX** ppDeviceFormat); + HRESULT (STDMETHODCALLTYPE * GetDevicePeriod) (ma_IAudioClient* pThis, MA_REFERENCE_TIME* pDefaultDevicePeriod, MA_REFERENCE_TIME* pMinimumDevicePeriod); + HRESULT (STDMETHODCALLTYPE * Start) (ma_IAudioClient* pThis); + HRESULT (STDMETHODCALLTYPE * Stop) (ma_IAudioClient* pThis); + HRESULT (STDMETHODCALLTYPE * Reset) (ma_IAudioClient* pThis); + HRESULT (STDMETHODCALLTYPE * SetEventHandle) (ma_IAudioClient* pThis, HANDLE eventHandle); + HRESULT (STDMETHODCALLTYPE * GetService) (ma_IAudioClient* pThis, const IID* const riid, void** pp); +} ma_IAudioClientVtbl; +struct ma_IAudioClient +{ + ma_IAudioClientVtbl* lpVtbl; +}; +HRESULT ma_IAudioClient_QueryInterface(ma_IAudioClient* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +ULONG ma_IAudioClient_AddRef(ma_IAudioClient* pThis) { return pThis->lpVtbl->AddRef(pThis); } +ULONG ma_IAudioClient_Release(ma_IAudioClient* pThis) { return pThis->lpVtbl->Release(pThis); } +HRESULT ma_IAudioClient_Initialize(ma_IAudioClient* pThis, MA_AUDCLNT_SHAREMODE shareMode, DWORD streamFlags, MA_REFERENCE_TIME bufferDuration, MA_REFERENCE_TIME periodicity, const WAVEFORMATEX* pFormat, const GUID* pAudioSessionGuid) { return pThis->lpVtbl->Initialize(pThis, shareMode, streamFlags, bufferDuration, periodicity, pFormat, pAudioSessionGuid); } +HRESULT ma_IAudioClient_GetBufferSize(ma_IAudioClient* pThis, ma_uint32* pNumBufferFrames) { return pThis->lpVtbl->GetBufferSize(pThis, pNumBufferFrames); } +HRESULT ma_IAudioClient_GetStreamLatency(ma_IAudioClient* pThis, MA_REFERENCE_TIME* pLatency) { return pThis->lpVtbl->GetStreamLatency(pThis, pLatency); } +HRESULT ma_IAudioClient_GetCurrentPadding(ma_IAudioClient* pThis, ma_uint32* pNumPaddingFrames) { return pThis->lpVtbl->GetCurrentPadding(pThis, pNumPaddingFrames); } +HRESULT ma_IAudioClient_IsFormatSupported(ma_IAudioClient* pThis, MA_AUDCLNT_SHAREMODE shareMode, const WAVEFORMATEX* pFormat, WAVEFORMATEX** ppClosestMatch) { return pThis->lpVtbl->IsFormatSupported(pThis, shareMode, pFormat, ppClosestMatch); } +HRESULT ma_IAudioClient_GetMixFormat(ma_IAudioClient* pThis, WAVEFORMATEX** ppDeviceFormat) { return pThis->lpVtbl->GetMixFormat(pThis, ppDeviceFormat); } +HRESULT ma_IAudioClient_GetDevicePeriod(ma_IAudioClient* pThis, MA_REFERENCE_TIME* pDefaultDevicePeriod, MA_REFERENCE_TIME* pMinimumDevicePeriod) { return pThis->lpVtbl->GetDevicePeriod(pThis, pDefaultDevicePeriod, pMinimumDevicePeriod); } +HRESULT ma_IAudioClient_Start(ma_IAudioClient* pThis) { return pThis->lpVtbl->Start(pThis); } +HRESULT ma_IAudioClient_Stop(ma_IAudioClient* pThis) { return pThis->lpVtbl->Stop(pThis); } +HRESULT ma_IAudioClient_Reset(ma_IAudioClient* pThis) { return pThis->lpVtbl->Reset(pThis); } +HRESULT ma_IAudioClient_SetEventHandle(ma_IAudioClient* pThis, HANDLE eventHandle) { return pThis->lpVtbl->SetEventHandle(pThis, eventHandle); } +HRESULT ma_IAudioClient_GetService(ma_IAudioClient* pThis, const IID* const riid, void** pp) { return pThis->lpVtbl->GetService(pThis, riid, pp); } + +/* IAudioClient2 */ +typedef struct +{ + /* IUnknown */ + HRESULT (STDMETHODCALLTYPE * QueryInterface)(ma_IAudioClient2* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (ma_IAudioClient2* pThis); + ULONG (STDMETHODCALLTYPE * Release) (ma_IAudioClient2* pThis); + + /* IAudioClient */ + HRESULT (STDMETHODCALLTYPE * Initialize) (ma_IAudioClient2* pThis, MA_AUDCLNT_SHAREMODE shareMode, DWORD streamFlags, MA_REFERENCE_TIME bufferDuration, MA_REFERENCE_TIME periodicity, const WAVEFORMATEX* pFormat, const GUID* pAudioSessionGuid); + HRESULT (STDMETHODCALLTYPE * GetBufferSize) (ma_IAudioClient2* pThis, ma_uint32* pNumBufferFrames); + HRESULT (STDMETHODCALLTYPE * GetStreamLatency) (ma_IAudioClient2* pThis, MA_REFERENCE_TIME* pLatency); + HRESULT (STDMETHODCALLTYPE * GetCurrentPadding)(ma_IAudioClient2* pThis, ma_uint32* pNumPaddingFrames); + HRESULT (STDMETHODCALLTYPE * IsFormatSupported)(ma_IAudioClient2* pThis, MA_AUDCLNT_SHAREMODE shareMode, const WAVEFORMATEX* pFormat, WAVEFORMATEX** ppClosestMatch); + HRESULT (STDMETHODCALLTYPE * GetMixFormat) (ma_IAudioClient2* pThis, WAVEFORMATEX** ppDeviceFormat); + HRESULT (STDMETHODCALLTYPE * GetDevicePeriod) (ma_IAudioClient2* pThis, MA_REFERENCE_TIME* pDefaultDevicePeriod, MA_REFERENCE_TIME* pMinimumDevicePeriod); + HRESULT (STDMETHODCALLTYPE * Start) (ma_IAudioClient2* pThis); + HRESULT (STDMETHODCALLTYPE * Stop) (ma_IAudioClient2* pThis); + HRESULT (STDMETHODCALLTYPE * Reset) (ma_IAudioClient2* pThis); + HRESULT (STDMETHODCALLTYPE * SetEventHandle) (ma_IAudioClient2* pThis, HANDLE eventHandle); + HRESULT (STDMETHODCALLTYPE * GetService) (ma_IAudioClient2* pThis, const IID* const riid, void** pp); + + /* IAudioClient2 */ + HRESULT (STDMETHODCALLTYPE * IsOffloadCapable) (ma_IAudioClient2* pThis, MA_AUDIO_STREAM_CATEGORY category, BOOL* pOffloadCapable); + HRESULT (STDMETHODCALLTYPE * SetClientProperties)(ma_IAudioClient2* pThis, const ma_AudioClientProperties* pProperties); + HRESULT (STDMETHODCALLTYPE * GetBufferSizeLimits)(ma_IAudioClient2* pThis, const WAVEFORMATEX* pFormat, BOOL eventDriven, MA_REFERENCE_TIME* pMinBufferDuration, MA_REFERENCE_TIME* pMaxBufferDuration); +} ma_IAudioClient2Vtbl; +struct ma_IAudioClient2 +{ + ma_IAudioClient2Vtbl* lpVtbl; +}; +HRESULT ma_IAudioClient2_QueryInterface(ma_IAudioClient2* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +ULONG ma_IAudioClient2_AddRef(ma_IAudioClient2* pThis) { return pThis->lpVtbl->AddRef(pThis); } +ULONG ma_IAudioClient2_Release(ma_IAudioClient2* pThis) { return pThis->lpVtbl->Release(pThis); } +HRESULT ma_IAudioClient2_Initialize(ma_IAudioClient2* pThis, MA_AUDCLNT_SHAREMODE shareMode, DWORD streamFlags, MA_REFERENCE_TIME bufferDuration, MA_REFERENCE_TIME periodicity, const WAVEFORMATEX* pFormat, const GUID* pAudioSessionGuid) { return pThis->lpVtbl->Initialize(pThis, shareMode, streamFlags, bufferDuration, periodicity, pFormat, pAudioSessionGuid); } +HRESULT ma_IAudioClient2_GetBufferSize(ma_IAudioClient2* pThis, ma_uint32* pNumBufferFrames) { return pThis->lpVtbl->GetBufferSize(pThis, pNumBufferFrames); } +HRESULT ma_IAudioClient2_GetStreamLatency(ma_IAudioClient2* pThis, MA_REFERENCE_TIME* pLatency) { return pThis->lpVtbl->GetStreamLatency(pThis, pLatency); } +HRESULT ma_IAudioClient2_GetCurrentPadding(ma_IAudioClient2* pThis, ma_uint32* pNumPaddingFrames) { return pThis->lpVtbl->GetCurrentPadding(pThis, pNumPaddingFrames); } +HRESULT ma_IAudioClient2_IsFormatSupported(ma_IAudioClient2* pThis, MA_AUDCLNT_SHAREMODE shareMode, const WAVEFORMATEX* pFormat, WAVEFORMATEX** ppClosestMatch) { return pThis->lpVtbl->IsFormatSupported(pThis, shareMode, pFormat, ppClosestMatch); } +HRESULT ma_IAudioClient2_GetMixFormat(ma_IAudioClient2* pThis, WAVEFORMATEX** ppDeviceFormat) { return pThis->lpVtbl->GetMixFormat(pThis, ppDeviceFormat); } +HRESULT ma_IAudioClient2_GetDevicePeriod(ma_IAudioClient2* pThis, MA_REFERENCE_TIME* pDefaultDevicePeriod, MA_REFERENCE_TIME* pMinimumDevicePeriod) { return pThis->lpVtbl->GetDevicePeriod(pThis, pDefaultDevicePeriod, pMinimumDevicePeriod); } +HRESULT ma_IAudioClient2_Start(ma_IAudioClient2* pThis) { return pThis->lpVtbl->Start(pThis); } +HRESULT ma_IAudioClient2_Stop(ma_IAudioClient2* pThis) { return pThis->lpVtbl->Stop(pThis); } +HRESULT ma_IAudioClient2_Reset(ma_IAudioClient2* pThis) { return pThis->lpVtbl->Reset(pThis); } +HRESULT ma_IAudioClient2_SetEventHandle(ma_IAudioClient2* pThis, HANDLE eventHandle) { return pThis->lpVtbl->SetEventHandle(pThis, eventHandle); } +HRESULT ma_IAudioClient2_GetService(ma_IAudioClient2* pThis, const IID* const riid, void** pp) { return pThis->lpVtbl->GetService(pThis, riid, pp); } +HRESULT ma_IAudioClient2_IsOffloadCapable(ma_IAudioClient2* pThis, MA_AUDIO_STREAM_CATEGORY category, BOOL* pOffloadCapable) { return pThis->lpVtbl->IsOffloadCapable(pThis, category, pOffloadCapable); } +HRESULT ma_IAudioClient2_SetClientProperties(ma_IAudioClient2* pThis, const ma_AudioClientProperties* pProperties) { return pThis->lpVtbl->SetClientProperties(pThis, pProperties); } +HRESULT ma_IAudioClient2_GetBufferSizeLimits(ma_IAudioClient2* pThis, const WAVEFORMATEX* pFormat, BOOL eventDriven, MA_REFERENCE_TIME* pMinBufferDuration, MA_REFERENCE_TIME* pMaxBufferDuration) { return pThis->lpVtbl->GetBufferSizeLimits(pThis, pFormat, eventDriven, pMinBufferDuration, pMaxBufferDuration); } + + +/* IAudioClient3 */ +typedef struct +{ + /* IUnknown */ + HRESULT (STDMETHODCALLTYPE * QueryInterface)(ma_IAudioClient3* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (ma_IAudioClient3* pThis); + ULONG (STDMETHODCALLTYPE * Release) (ma_IAudioClient3* pThis); + + /* IAudioClient */ + HRESULT (STDMETHODCALLTYPE * Initialize) (ma_IAudioClient3* pThis, MA_AUDCLNT_SHAREMODE shareMode, DWORD streamFlags, MA_REFERENCE_TIME bufferDuration, MA_REFERENCE_TIME periodicity, const WAVEFORMATEX* pFormat, const GUID* pAudioSessionGuid); + HRESULT (STDMETHODCALLTYPE * GetBufferSize) (ma_IAudioClient3* pThis, ma_uint32* pNumBufferFrames); + HRESULT (STDMETHODCALLTYPE * GetStreamLatency) (ma_IAudioClient3* pThis, MA_REFERENCE_TIME* pLatency); + HRESULT (STDMETHODCALLTYPE * GetCurrentPadding)(ma_IAudioClient3* pThis, ma_uint32* pNumPaddingFrames); + HRESULT (STDMETHODCALLTYPE * IsFormatSupported)(ma_IAudioClient3* pThis, MA_AUDCLNT_SHAREMODE shareMode, const WAVEFORMATEX* pFormat, WAVEFORMATEX** ppClosestMatch); + HRESULT (STDMETHODCALLTYPE * GetMixFormat) (ma_IAudioClient3* pThis, WAVEFORMATEX** ppDeviceFormat); + HRESULT (STDMETHODCALLTYPE * GetDevicePeriod) (ma_IAudioClient3* pThis, MA_REFERENCE_TIME* pDefaultDevicePeriod, MA_REFERENCE_TIME* pMinimumDevicePeriod); + HRESULT (STDMETHODCALLTYPE * Start) (ma_IAudioClient3* pThis); + HRESULT (STDMETHODCALLTYPE * Stop) (ma_IAudioClient3* pThis); + HRESULT (STDMETHODCALLTYPE * Reset) (ma_IAudioClient3* pThis); + HRESULT (STDMETHODCALLTYPE * SetEventHandle) (ma_IAudioClient3* pThis, HANDLE eventHandle); + HRESULT (STDMETHODCALLTYPE * GetService) (ma_IAudioClient3* pThis, const IID* const riid, void** pp); + + /* IAudioClient2 */ + HRESULT (STDMETHODCALLTYPE * IsOffloadCapable) (ma_IAudioClient3* pThis, MA_AUDIO_STREAM_CATEGORY category, BOOL* pOffloadCapable); + HRESULT (STDMETHODCALLTYPE * SetClientProperties)(ma_IAudioClient3* pThis, const ma_AudioClientProperties* pProperties); + 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); +} ma_IAudioClient3Vtbl; +struct ma_IAudioClient3 +{ + ma_IAudioClient3Vtbl* lpVtbl; +}; +HRESULT ma_IAudioClient3_QueryInterface(ma_IAudioClient3* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +ULONG ma_IAudioClient3_AddRef(ma_IAudioClient3* pThis) { return pThis->lpVtbl->AddRef(pThis); } +ULONG ma_IAudioClient3_Release(ma_IAudioClient3* pThis) { return pThis->lpVtbl->Release(pThis); } +HRESULT ma_IAudioClient3_Initialize(ma_IAudioClient3* pThis, MA_AUDCLNT_SHAREMODE shareMode, DWORD streamFlags, MA_REFERENCE_TIME bufferDuration, MA_REFERENCE_TIME periodicity, const WAVEFORMATEX* pFormat, const GUID* pAudioSessionGuid) { return pThis->lpVtbl->Initialize(pThis, shareMode, streamFlags, bufferDuration, periodicity, pFormat, pAudioSessionGuid); } +HRESULT ma_IAudioClient3_GetBufferSize(ma_IAudioClient3* pThis, ma_uint32* pNumBufferFrames) { return pThis->lpVtbl->GetBufferSize(pThis, pNumBufferFrames); } +HRESULT ma_IAudioClient3_GetStreamLatency(ma_IAudioClient3* pThis, MA_REFERENCE_TIME* pLatency) { return pThis->lpVtbl->GetStreamLatency(pThis, pLatency); } +HRESULT ma_IAudioClient3_GetCurrentPadding(ma_IAudioClient3* pThis, ma_uint32* pNumPaddingFrames) { return pThis->lpVtbl->GetCurrentPadding(pThis, pNumPaddingFrames); } +HRESULT ma_IAudioClient3_IsFormatSupported(ma_IAudioClient3* pThis, MA_AUDCLNT_SHAREMODE shareMode, const WAVEFORMATEX* pFormat, WAVEFORMATEX** ppClosestMatch) { return pThis->lpVtbl->IsFormatSupported(pThis, shareMode, pFormat, ppClosestMatch); } +HRESULT ma_IAudioClient3_GetMixFormat(ma_IAudioClient3* pThis, WAVEFORMATEX** ppDeviceFormat) { return pThis->lpVtbl->GetMixFormat(pThis, ppDeviceFormat); } +HRESULT ma_IAudioClient3_GetDevicePeriod(ma_IAudioClient3* pThis, MA_REFERENCE_TIME* pDefaultDevicePeriod, MA_REFERENCE_TIME* pMinimumDevicePeriod) { return pThis->lpVtbl->GetDevicePeriod(pThis, pDefaultDevicePeriod, pMinimumDevicePeriod); } +HRESULT ma_IAudioClient3_Start(ma_IAudioClient3* pThis) { return pThis->lpVtbl->Start(pThis); } +HRESULT ma_IAudioClient3_Stop(ma_IAudioClient3* pThis) { return pThis->lpVtbl->Stop(pThis); } +HRESULT ma_IAudioClient3_Reset(ma_IAudioClient3* pThis) { return pThis->lpVtbl->Reset(pThis); } +HRESULT ma_IAudioClient3_SetEventHandle(ma_IAudioClient3* pThis, HANDLE eventHandle) { return pThis->lpVtbl->SetEventHandle(pThis, eventHandle); } +HRESULT ma_IAudioClient3_GetService(ma_IAudioClient3* pThis, const IID* const riid, void** pp) { return pThis->lpVtbl->GetService(pThis, riid, pp); } +HRESULT ma_IAudioClient3_IsOffloadCapable(ma_IAudioClient3* pThis, MA_AUDIO_STREAM_CATEGORY category, BOOL* pOffloadCapable) { return pThis->lpVtbl->IsOffloadCapable(pThis, category, pOffloadCapable); } +HRESULT ma_IAudioClient3_SetClientProperties(ma_IAudioClient3* pThis, const ma_AudioClientProperties* pProperties) { return pThis->lpVtbl->SetClientProperties(pThis, pProperties); } +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); } +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); } +HRESULT ma_IAudioClient3_GetCurrentSharedModeEnginePeriod(ma_IAudioClient3* pThis, WAVEFORMATEX** ppFormat, UINT32* pCurrentPeriodInFrames) { return pThis->lpVtbl->GetCurrentSharedModeEnginePeriod(pThis, ppFormat, pCurrentPeriodInFrames); } +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); } + + +/* IAudioRenderClient */ +typedef struct +{ + /* IUnknown */ + HRESULT (STDMETHODCALLTYPE * QueryInterface)(ma_IAudioRenderClient* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (ma_IAudioRenderClient* pThis); + ULONG (STDMETHODCALLTYPE * Release) (ma_IAudioRenderClient* pThis); + + /* IAudioRenderClient */ + HRESULT (STDMETHODCALLTYPE * GetBuffer) (ma_IAudioRenderClient* pThis, ma_uint32 numFramesRequested, BYTE** ppData); + HRESULT (STDMETHODCALLTYPE * ReleaseBuffer)(ma_IAudioRenderClient* pThis, ma_uint32 numFramesWritten, DWORD dwFlags); +} ma_IAudioRenderClientVtbl; +struct ma_IAudioRenderClient +{ + ma_IAudioRenderClientVtbl* lpVtbl; +}; +HRESULT ma_IAudioRenderClient_QueryInterface(ma_IAudioRenderClient* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +ULONG ma_IAudioRenderClient_AddRef(ma_IAudioRenderClient* pThis) { return pThis->lpVtbl->AddRef(pThis); } +ULONG ma_IAudioRenderClient_Release(ma_IAudioRenderClient* pThis) { return pThis->lpVtbl->Release(pThis); } +HRESULT ma_IAudioRenderClient_GetBuffer(ma_IAudioRenderClient* pThis, ma_uint32 numFramesRequested, BYTE** ppData) { return pThis->lpVtbl->GetBuffer(pThis, numFramesRequested, ppData); } +HRESULT ma_IAudioRenderClient_ReleaseBuffer(ma_IAudioRenderClient* pThis, ma_uint32 numFramesWritten, DWORD dwFlags) { return pThis->lpVtbl->ReleaseBuffer(pThis, numFramesWritten, dwFlags); } + + +/* IAudioCaptureClient */ +typedef struct +{ + /* IUnknown */ + HRESULT (STDMETHODCALLTYPE * QueryInterface)(ma_IAudioCaptureClient* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (ma_IAudioCaptureClient* pThis); + ULONG (STDMETHODCALLTYPE * Release) (ma_IAudioCaptureClient* pThis); + + /* IAudioRenderClient */ + HRESULT (STDMETHODCALLTYPE * GetBuffer) (ma_IAudioCaptureClient* pThis, BYTE** ppData, ma_uint32* pNumFramesToRead, DWORD* pFlags, ma_uint64* pDevicePosition, ma_uint64* pQPCPosition); + HRESULT (STDMETHODCALLTYPE * ReleaseBuffer) (ma_IAudioCaptureClient* pThis, ma_uint32 numFramesRead); + HRESULT (STDMETHODCALLTYPE * GetNextPacketSize)(ma_IAudioCaptureClient* pThis, ma_uint32* pNumFramesInNextPacket); +} ma_IAudioCaptureClientVtbl; +struct ma_IAudioCaptureClient +{ + ma_IAudioCaptureClientVtbl* lpVtbl; +}; +HRESULT ma_IAudioCaptureClient_QueryInterface(ma_IAudioCaptureClient* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +ULONG ma_IAudioCaptureClient_AddRef(ma_IAudioCaptureClient* pThis) { return pThis->lpVtbl->AddRef(pThis); } +ULONG ma_IAudioCaptureClient_Release(ma_IAudioCaptureClient* pThis) { return pThis->lpVtbl->Release(pThis); } +HRESULT ma_IAudioCaptureClient_GetBuffer(ma_IAudioCaptureClient* pThis, BYTE** ppData, ma_uint32* pNumFramesToRead, DWORD* pFlags, ma_uint64* pDevicePosition, ma_uint64* pQPCPosition) { return pThis->lpVtbl->GetBuffer(pThis, ppData, pNumFramesToRead, pFlags, pDevicePosition, pQPCPosition); } +HRESULT ma_IAudioCaptureClient_ReleaseBuffer(ma_IAudioCaptureClient* pThis, ma_uint32 numFramesRead) { return pThis->lpVtbl->ReleaseBuffer(pThis, numFramesRead); } +HRESULT ma_IAudioCaptureClient_GetNextPacketSize(ma_IAudioCaptureClient* pThis, ma_uint32* pNumFramesInNextPacket) { return pThis->lpVtbl->GetNextPacketSize(pThis, pNumFramesInNextPacket); } + +#ifndef MA_WIN32_DESKTOP +#include +typedef struct ma_completion_handler_uwp ma_completion_handler_uwp; + +typedef struct +{ + /* IUnknown */ + HRESULT (STDMETHODCALLTYPE * QueryInterface)(ma_completion_handler_uwp* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (ma_completion_handler_uwp* pThis); + ULONG (STDMETHODCALLTYPE * Release) (ma_completion_handler_uwp* pThis); + + /* IActivateAudioInterfaceCompletionHandler */ + HRESULT (STDMETHODCALLTYPE * ActivateCompleted)(ma_completion_handler_uwp* pThis, ma_IActivateAudioInterfaceAsyncOperation* pActivateOperation); +} ma_completion_handler_uwp_vtbl; +struct ma_completion_handler_uwp +{ + ma_completion_handler_uwp_vtbl* lpVtbl; + ma_uint32 counter; + HANDLE hEvent; +}; + +HRESULT STDMETHODCALLTYPE ma_completion_handler_uwp_QueryInterface(ma_completion_handler_uwp* pThis, const IID* const riid, void** ppObject) +{ + /* + We need to "implement" IAgileObject which is just an indicator that's used internally by WASAPI for some multithreading management. To + "implement" this, we just make sure we return pThis when the IAgileObject is requested. + */ + if (!ma_is_guid_equal(riid, &MA_IID_IUnknown) && !ma_is_guid_equal(riid, &MA_IID_IActivateAudioInterfaceCompletionHandler) && !ma_is_guid_equal(riid, &MA_IID_IAgileObject)) { + *ppObject = NULL; + return E_NOINTERFACE; + } + + /* Getting here means the IID is IUnknown or IMMNotificationClient. */ + *ppObject = (void*)pThis; + ((ma_completion_handler_uwp_vtbl*)pThis->lpVtbl)->AddRef(pThis); + return S_OK; +} + +ULONG STDMETHODCALLTYPE ma_completion_handler_uwp_AddRef(ma_completion_handler_uwp* pThis) +{ + return (ULONG)ma_atomic_increment_32(&pThis->counter); +} + +ULONG STDMETHODCALLTYPE ma_completion_handler_uwp_Release(ma_completion_handler_uwp* pThis) +{ + ma_uint32 newRefCount = ma_atomic_decrement_32(&pThis->counter); + if (newRefCount == 0) { + return 0; /* We don't free anything here because we never allocate the object on the heap. */ + } + + return (ULONG)newRefCount; +} + +HRESULT STDMETHODCALLTYPE ma_completion_handler_uwp_ActivateCompleted(ma_completion_handler_uwp* pThis, ma_IActivateAudioInterfaceAsyncOperation* pActivateOperation) +{ + (void)pActivateOperation; + SetEvent(pThis->hEvent); + return S_OK; +} + + +static ma_completion_handler_uwp_vtbl g_maCompletionHandlerVtblInstance = { + ma_completion_handler_uwp_QueryInterface, + ma_completion_handler_uwp_AddRef, + ma_completion_handler_uwp_Release, + ma_completion_handler_uwp_ActivateCompleted +}; + +ma_result ma_completion_handler_uwp_init(ma_completion_handler_uwp* pHandler) +{ + ma_assert(pHandler != NULL); + ma_zero_object(pHandler); + + pHandler->lpVtbl = &g_maCompletionHandlerVtblInstance; + pHandler->counter = 1; + pHandler->hEvent = CreateEventA(NULL, FALSE, FALSE, NULL); + if (pHandler->hEvent == NULL) { + return MA_ERROR; + } + + return MA_SUCCESS; +} + +void ma_completion_handler_uwp_uninit(ma_completion_handler_uwp* pHandler) +{ + if (pHandler->hEvent != NULL) { + CloseHandle(pHandler->hEvent); + } +} + +void ma_completion_handler_uwp_wait(ma_completion_handler_uwp* pHandler) +{ + WaitForSingleObject(pHandler->hEvent, INFINITE); +} +#endif /* !MA_WIN32_DESKTOP */ + +/* We need a virtual table for our notification client object that's used for detecting changes to the default device. */ +#ifdef MA_WIN32_DESKTOP +HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_QueryInterface(ma_IMMNotificationClient* pThis, const IID* const riid, void** ppObject) +{ + /* + We care about two interfaces - IUnknown and IMMNotificationClient. If the requested IID is something else + we just return E_NOINTERFACE. Otherwise we need to increment the reference counter and return S_OK. + */ + if (!ma_is_guid_equal(riid, &MA_IID_IUnknown) && !ma_is_guid_equal(riid, &MA_IID_IMMNotificationClient)) { + *ppObject = NULL; + return E_NOINTERFACE; + } + + /* Getting here means the IID is IUnknown or IMMNotificationClient. */ + *ppObject = (void*)pThis; + ((ma_IMMNotificationClientVtbl*)pThis->lpVtbl)->AddRef(pThis); + return S_OK; +} + +ULONG STDMETHODCALLTYPE ma_IMMNotificationClient_AddRef(ma_IMMNotificationClient* pThis) +{ + return (ULONG)ma_atomic_increment_32(&pThis->counter); +} + +ULONG STDMETHODCALLTYPE ma_IMMNotificationClient_Release(ma_IMMNotificationClient* pThis) +{ + ma_uint32 newRefCount = ma_atomic_decrement_32(&pThis->counter); + if (newRefCount == 0) { + return 0; /* We don't free anything here because we never allocate the object on the heap. */ + } + + return (ULONG)newRefCount; +} + + +HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDeviceStateChanged(ma_IMMNotificationClient* pThis, LPCWSTR pDeviceID, DWORD dwNewState) +{ +#ifdef MA_DEBUG_OUTPUT + printf("IMMNotificationClient_OnDeviceStateChanged(pDeviceID=%S, dwNewState=%u)\n", (pDeviceID != NULL) ? pDeviceID : L"(NULL)", (unsigned int)dwNewState); +#endif + + (void)pThis; + (void)pDeviceID; + (void)dwNewState; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDeviceAdded(ma_IMMNotificationClient* pThis, LPCWSTR pDeviceID) +{ +#ifdef MA_DEBUG_OUTPUT + printf("IMMNotificationClient_OnDeviceAdded(pDeviceID=%S)\n", (pDeviceID != NULL) ? pDeviceID : L"(NULL)"); +#endif + + /* We don't need to worry about this event for our purposes. */ + (void)pThis; + (void)pDeviceID; + return S_OK; +} + +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)"); +#endif + + /* We don't need to worry about this event for our purposes. */ + (void)pThis; + (void)pDeviceID; + return S_OK; +} + +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)"); +#endif + + /* We only ever use the eConsole role in miniaudio. */ + if (role != ma_eConsole) { + 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)) { + return S_OK; + } + + /* + Not currently supporting automatic stream routing in exclusive mode. This is not working correctly on my machine due to + AUDCLNT_E_DEVICE_IN_USE errors when reinitializing the device. If this is a bug in miniaudio, we can try re-enabling this once + it's fixed. + */ + if ((dataFlow == ma_eRender && pThis->pDevice->playback.shareMode == ma_share_mode_exclusive) || + (dataFlow == ma_eCapture && pThis->pDevice->capture.shareMode == ma_share_mode_exclusive)) { + 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. + */ + if (dataFlow == ma_eRender) { + ma_atomic_exchange_32(&pThis->pDevice->wasapi.hasDefaultPlaybackDeviceChanged, MA_TRUE); + } + if (dataFlow == ma_eCapture) { + ma_atomic_exchange_32(&pThis->pDevice->wasapi.hasDefaultCaptureDeviceChanged, MA_TRUE); + } + + (void)pDefaultDeviceID; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnPropertyValueChanged(ma_IMMNotificationClient* pThis, LPCWSTR pDeviceID, const PROPERTYKEY key) +{ +#ifdef MA_DEBUG_OUTPUT + printf("IMMNotificationClient_OnPropertyValueChanged(pDeviceID=%S)\n", (pDeviceID != NULL) ? pDeviceID : L"(NULL)"); +#endif + + (void)pThis; + (void)pDeviceID; + (void)key; + return S_OK; +} + +static ma_IMMNotificationClientVtbl g_maNotificationCientVtbl = { + ma_IMMNotificationClient_QueryInterface, + ma_IMMNotificationClient_AddRef, + ma_IMMNotificationClient_Release, + ma_IMMNotificationClient_OnDeviceStateChanged, + ma_IMMNotificationClient_OnDeviceAdded, + ma_IMMNotificationClient_OnDeviceRemoved, + ma_IMMNotificationClient_OnDefaultDeviceChanged, + ma_IMMNotificationClient_OnPropertyValueChanged +}; +#endif /* MA_WIN32_DESKTOP */ + +#ifdef MA_WIN32_DESKTOP +typedef ma_IMMDevice ma_WASAPIDeviceInterface; +#else +typedef ma_IUnknown ma_WASAPIDeviceInterface; +#endif + + + +ma_bool32 ma_context_is_device_id_equal__wasapi(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->wasapi, pID1->wasapi, sizeof(pID0->wasapi)) == 0; +} + +void ma_set_device_info_from_WAVEFORMATEX(const WAVEFORMATEX* pWF, 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; +} + +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) +{ + 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_FAILED_TO_OPEN_BACKEND_DEVICE); + } + } else { + /* Exlcusive Mode. We repeatedly call IsFormatSupported() here. This is not currently support on UWP. */ +#ifdef MA_WIN32_DESKTOP + /* + 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); + 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); + + /* + 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)) { + /* + 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; + ma_uint32 iFormat; + + ma_get_standard_channel_map(ma_standard_channel_map_microsoft, channels, defaultChannelMap); + + ma_zero_object(&wf); + wf.Format.cbSize = sizeof(wf); + wf.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + wf.Format.nChannels = (WORD)channels; + 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]; + ma_uint32 iSampleRate; + + wf.Format.wBitsPerSample = (WORD)ma_get_bytes_per_sample(format)*8; + wf.Format.nBlockAlign = (wf.Format.nChannels * wf.Format.wBitsPerSample) / 8; + wf.Format.nAvgBytesPerSec = wf.Format.nBlockAlign * wf.Format.nSamplesPerSec; + wf.Samples.wValidBitsPerSample = /*(format == ma_format_s24_32) ? 24 :*/ wf.Format.wBitsPerSample; + if (format == ma_format_f32) { + wf.SubFormat = MA_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + } else { + wf.SubFormat = MA_GUID_KSDATAFORMAT_SUBTYPE_PCM; + } + + for (iSampleRate = 0; iSampleRate < ma_countof(g_maStandardSampleRatePriorities); ++iSampleRate) { + wf.Format.nSamplesPerSec = g_maStandardSampleRatePriorities[iSampleRate]; + + 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); + found = MA_TRUE; + break; + } + } + + if (found) { + break; + } + } + + 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_FAILED_TO_OPEN_BACKEND_DEVICE); + } + } + } 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_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + 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_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + return MA_SUCCESS; +#else + /* Exclusive mode not fully supported in UWP right now. */ + return MA_ERROR; +#endif + } +} + +#ifdef MA_WIN32_DESKTOP +ma_result ma_context_get_MMDevice__wasapi(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_IMMDevice** ppMMDevice) +{ + ma_IMMDeviceEnumerator* pDeviceEnumerator; + HRESULT hr; + + ma_assert(pContext != NULL); + ma_assert(ppMMDevice != NULL); + + hr = ma_CoCreateInstance(pContext, MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); + if (FAILED(hr)) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create IMMDeviceEnumerator.", MA_FAILED_TO_INIT_BACKEND); + } + + if (pDeviceID == NULL) { + hr = ma_IMMDeviceEnumerator_GetDefaultAudioEndpoint(pDeviceEnumerator, (deviceType == ma_device_type_playback) ? ma_eRender : ma_eCapture, ma_eConsole, ppMMDevice); + } else { + hr = ma_IMMDeviceEnumerator_GetDevice(pDeviceEnumerator, pDeviceID->wasapi, ppMMDevice); + } + + ma_IMMDeviceEnumerator_Release(pDeviceEnumerator); + if (FAILED(hr)) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve IMMDevice.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + return MA_SUCCESS; +} + +ma_result ma_context_get_device_info_from_MMDevice__wasapi(ma_context* pContext, ma_IMMDevice* pMMDevice, ma_share_mode shareMode, ma_bool32 onlySimpleInfo, ma_device_info* pInfo) +{ + LPWSTR id; + HRESULT hr; + + ma_assert(pContext != NULL); + ma_assert(pMMDevice != NULL); + ma_assert(pInfo != NULL); + + /* ID. */ + hr = ma_IMMDevice_GetId(pMMDevice, &id); + if (SUCCEEDED(hr)) { + size_t idlen = wcslen(id); + if (idlen+1 > ma_countof(pInfo->id.wasapi)) { + ma_CoTaskMemFree(pContext, id); + 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, id, idlen * sizeof(wchar_t)); + pInfo->id.wasapi[idlen] = '\0'; + + ma_CoTaskMemFree(pContext, id); + } + + { + 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)) { + WideCharToMultiByte(CP_UTF8, 0, var.pwszVal, -1, pInfo->name, sizeof(pInfo->name), 0, FALSE); + ma_PropVariantClear(pContext, &var); + } + + ma_IPropertyStore_Release(pProperties); + } + } + + /* Format */ + if (!onlySimpleInfo) { + 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); + + ma_IAudioClient_Release(pAudioClient); + return result; + } else { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to activate audio client for device info retrieval.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + } + + return MA_SUCCESS; +} + +ma_result ma_context_enumerate_device_collection__wasapi(ma_context* pContext, ma_IMMDeviceCollection* pDeviceCollection, ma_device_type deviceType, ma_enum_devices_callback_proc callback, void* pUserData) +{ + UINT deviceCount; + HRESULT hr; + ma_uint32 iDevice; + + ma_assert(pContext != NULL); + ma_assert(callback != NULL); + + hr = ma_IMMDeviceCollection_GetCount(pDeviceCollection, &deviceCount); + if (FAILED(hr)) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to get playback device count.", MA_NO_DEVICE); + } + + 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)) { + ma_result result = ma_context_get_device_info_from_MMDevice__wasapi(pContext, pMMDevice, ma_share_mode_shared, MA_TRUE, &deviceInfo); /* MA_TRUE = onlySimpleInfo. */ + + ma_IMMDevice_Release(pMMDevice); + if (result == MA_SUCCESS) { + ma_bool32 cbResult = callback(pContext, deviceType, &deviceInfo, pUserData); + if (cbResult == MA_FALSE) { + break; + } + } + } + } + + return MA_SUCCESS; +} +#endif + +#ifdef MA_WIN32_DESKTOP +ma_result ma_context_get_IAudioClient_Desktop__wasapi(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_IAudioClient** ppAudioClient, ma_IMMDevice** ppMMDevice) +{ + ma_result result; + HRESULT hr; + + ma_assert(pContext != NULL); + ma_assert(ppAudioClient != NULL); + ma_assert(ppMMDevice != NULL); + + result = ma_context_get_MMDevice__wasapi(pContext, deviceType, pDeviceID, ppMMDevice); + if (result != MA_SUCCESS) { + return result; + } + + hr = ma_IMMDevice_Activate(*ppMMDevice, &MA_IID_IAudioClient, CLSCTX_ALL, NULL, (void**)ppAudioClient); + if (FAILED(hr)) { + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + } + + return MA_SUCCESS; +} +#else +ma_result ma_context_get_IAudioClient_UWP__wasapi(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_IAudioClient** ppAudioClient, ma_IUnknown** ppActivatedInterface) +{ + ma_IActivateAudioInterfaceAsyncOperation *pAsyncOp = NULL; + ma_completion_handler_uwp completionHandler; + IID iid; + LPOLESTR iidStr; + HRESULT hr; + ma_result result; + HRESULT activateResult; + ma_IUnknown* pActivatedInterface; + + ma_assert(pContext != NULL); + ma_assert(ppAudioClient != NULL); + + if (pDeviceID != NULL) { + ma_copy_memory(&iid, pDeviceID->wasapi, sizeof(iid)); + } else { + if (deviceType == ma_device_type_playback) { + iid = MA_IID_DEVINTERFACE_AUDIO_RENDER; + } else { + iid = MA_IID_DEVINTERFACE_AUDIO_CAPTURE; + } + } + +#if defined(__cplusplus) + hr = StringFromIID(iid, &iidStr); +#else + hr = StringFromIID(&iid, &iidStr); +#endif + if (FAILED(hr)) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to convert device IID to string for ActivateAudioInterfaceAsync(). Out of memory.", MA_OUT_OF_MEMORY); + } + + result = ma_completion_handler_uwp_init(&completionHandler); + if (result != MA_SUCCESS) { + ma_CoTaskMemFree(pContext, iidStr); + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for waiting for ActivateAudioInterfaceAsync().", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + +#if defined(__cplusplus) + hr = ActivateAudioInterfaceAsync(iidStr, MA_IID_IAudioClient, NULL, (IActivateAudioInterfaceCompletionHandler*)&completionHandler, (IActivateAudioInterfaceAsyncOperation**)&pAsyncOp); +#else + hr = ActivateAudioInterfaceAsync(iidStr, &MA_IID_IAudioClient, NULL, (IActivateAudioInterfaceCompletionHandler*)&completionHandler, (IActivateAudioInterfaceAsyncOperation**)&pAsyncOp); +#endif + if (FAILED(hr)) { + ma_completion_handler_uwp_uninit(&completionHandler); + ma_CoTaskMemFree(pContext, iidStr); + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] ActivateAudioInterfaceAsync() failed.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + ma_CoTaskMemFree(pContext, iidStr); + + /* Wait for the async operation for finish. */ + ma_completion_handler_uwp_wait(&completionHandler); + ma_completion_handler_uwp_uninit(&completionHandler); + + hr = ma_IActivateAudioInterfaceAsyncOperation_GetActivateResult(pAsyncOp, &activateResult, &pActivatedInterface); + ma_IActivateAudioInterfaceAsyncOperation_Release(pAsyncOp); + + if (FAILED(hr) || FAILED(activateResult)) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to activate device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + /* Here is where we grab the IAudioClient interface. */ + hr = ma_IUnknown_QueryInterface(pActivatedInterface, &MA_IID_IAudioClient, (void**)ppAudioClient); + if (FAILED(hr)) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to query IAudioClient interface.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (ppActivatedInterface) { + *ppActivatedInterface = pActivatedInterface; + } else { + ma_IUnknown_Release(pActivatedInterface); + } + + return MA_SUCCESS; +} +#endif + +ma_result ma_context_get_IAudioClient__wasapi(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_IAudioClient** ppAudioClient, ma_WASAPIDeviceInterface** ppDeviceInterface) +{ +#ifdef MA_WIN32_DESKTOP + return ma_context_get_IAudioClient_Desktop__wasapi(pContext, deviceType, pDeviceID, ppAudioClient, ppDeviceInterface); +#else + return ma_context_get_IAudioClient_UWP__wasapi(pContext, deviceType, pDeviceID, ppAudioClient, ppDeviceInterface); +#endif +} + + +ma_result ma_context_enumerate_devices__wasapi(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + /* Different enumeration for desktop and UWP. */ +#ifdef MA_WIN32_DESKTOP + /* Desktop */ + HRESULT hr; + ma_IMMDeviceEnumerator* pDeviceEnumerator; + ma_IMMDeviceCollection* pDeviceCollection; + + hr = ma_CoCreateInstance(pContext, MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); + if (FAILED(hr)) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create device enumerator.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + /* Playback. */ + hr = ma_IMMDeviceEnumerator_EnumAudioEndpoints(pDeviceEnumerator, ma_eRender, MA_MM_DEVICE_STATE_ACTIVE, &pDeviceCollection); + if (SUCCEEDED(hr)) { + ma_context_enumerate_device_collection__wasapi(pContext, pDeviceCollection, ma_device_type_playback, callback, pUserData); + ma_IMMDeviceCollection_Release(pDeviceCollection); + } + + /* Capture. */ + hr = ma_IMMDeviceEnumerator_EnumAudioEndpoints(pDeviceEnumerator, ma_eCapture, MA_MM_DEVICE_STATE_ACTIVE, &pDeviceCollection); + if (SUCCEEDED(hr)) { + ma_context_enumerate_device_collection__wasapi(pContext, pDeviceCollection, ma_device_type_capture, callback, pUserData); + ma_IMMDeviceCollection_Release(pDeviceCollection); + } + + ma_IMMDeviceEnumerator_Release(pDeviceEnumerator); +#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) { + ma_bool32 cbResult = MA_TRUE; + + /* Playback. */ + if (cbResult) { + ma_device_info deviceInfo; + ma_zero_object(&deviceInfo); + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); + } + + /* Capture. */ + if (cbResult) { + ma_device_info deviceInfo; + ma_zero_object(&deviceInfo); + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); + } + } +#endif + + return MA_SUCCESS; +} + +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) +{ +#ifdef MA_WIN32_DESKTOP + ma_IMMDevice* pMMDevice = NULL; + ma_result result; + + result = ma_context_get_MMDevice__wasapi(pContext, deviceType, pDeviceID, &pMMDevice); + if (result != MA_SUCCESS) { + return result; + } + + result = ma_context_get_device_info_from_MMDevice__wasapi(pContext, pMMDevice, shareMode, MA_FALSE, pDeviceInfo); /* MA_FALSE = !onlySimpleInfo. */ + + ma_IMMDevice_Release(pMMDevice); + return result; +#else + ma_IAudioClient* pAudioClient; + ma_result result; + + /* UWP currently only uses default devices. */ + if (deviceType == ma_device_type_playback) { + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + 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); + + ma_IAudioClient_Release(pAudioClient); + return result; +#endif +} + +void ma_device_uninit__wasapi(ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + +#ifdef MA_WIN32_DESKTOP + if (pDevice->wasapi.pDeviceEnumerator) { + ((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator)->lpVtbl->UnregisterEndpointNotificationCallback((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator, &pDevice->wasapi.notificationClient); + ma_IMMDeviceEnumerator_Release((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator); + } +#endif + + if (pDevice->wasapi.pRenderClient) { + ma_IAudioRenderClient_Release((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient); + } + if (pDevice->wasapi.pCaptureClient) { + ma_IAudioCaptureClient_Release((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient); + } + + if (pDevice->wasapi.pAudioClientPlayback) { + ma_IAudioClient_Release((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); + } + if (pDevice->wasapi.pAudioClientCapture) { + ma_IAudioClient_Release((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); + } + + if (pDevice->wasapi.hEventPlayback) { + CloseHandle(pDevice->wasapi.hEventPlayback); + } + if (pDevice->wasapi.hEventCapture) { + CloseHandle(pDevice->wasapi.hEventCapture); + } +} + + +typedef struct +{ + /* Input. */ + ma_format formatIn; + ma_uint32 channelsIn; + ma_uint32 sampleRateIn; + ma_channel channelMapIn[MA_MAX_CHANNELS]; + ma_uint32 bufferSizeInFramesIn; + ma_uint32 bufferSizeInMillisecondsIn; + ma_uint32 periodsIn; + ma_bool32 usingDefaultFormat; + ma_bool32 usingDefaultChannels; + ma_bool32 usingDefaultSampleRate; + ma_bool32 usingDefaultChannelMap; + ma_share_mode shareMode; + + /* Output. */ + ma_IAudioClient* pAudioClient; + ma_IAudioRenderClient* pRenderClient; + ma_IAudioCaptureClient* pCaptureClient; + ma_format formatOut; + ma_uint32 channelsOut; + ma_uint32 sampleRateOut; + ma_channel channelMapOut[MA_MAX_CHANNELS]; + ma_uint32 bufferSizeInFramesOut; + ma_uint32 periodSizeInFramesOut; + ma_uint32 periodsOut; + char deviceName[256]; +} ma_device_init_internal_data__wasapi; + +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) +{ + HRESULT hr; + ma_result result = MA_SUCCESS; + const char* errorMsg = ""; + MA_AUDCLNT_SHAREMODE shareMode = MA_AUDCLNT_SHAREMODE_SHARED; + MA_REFERENCE_TIME bufferDurationInMicroseconds; + ma_bool32 wasInitializedUsingIAudioClient3 = MA_FALSE; + WAVEFORMATEXTENSIBLE wf; + ma_WASAPIDeviceInterface* pDeviceInterface = NULL; + ma_IAudioClient2* pAudioClient2; + + ma_assert(pContext != NULL); + ma_assert(pData != NULL); + + /* This function is only used to initialize one device type: either playback or capture. Never full-duplex. */ + if (deviceType == ma_device_type_duplex) { + return MA_INVALID_ARGS; + } + + pData->pAudioClient = NULL; + pData->pRenderClient = NULL; + pData->pCaptureClient = NULL; + + result = ma_context_get_IAudioClient__wasapi(pContext, deviceType, pDeviceID, &pData->pAudioClient, &pDeviceInterface); + if (result != MA_SUCCESS) { + goto done; + } + + + /* Try enabling hardware offloading. */ + hr = ma_IAudioClient_QueryInterface(pData->pAudioClient, &MA_IID_IAudioClient2, (void**)&pAudioClient2); + if (SUCCEEDED(hr)) { + BOOL isHardwareOffloadingSupported = 0; + hr = ma_IAudioClient2_IsOffloadCapable(pAudioClient2, MA_AudioCategory_Other, &isHardwareOffloadingSupported); + if (SUCCEEDED(hr) && isHardwareOffloadingSupported) { + ma_AudioClientProperties clientProperties; + ma_zero_object(&clientProperties); + clientProperties.cbSize = sizeof(clientProperties); + clientProperties.bIsOffload = 1; + clientProperties.eCategory = MA_AudioCategory_Other; + ma_IAudioClient2_SetClientProperties(pAudioClient2, &clientProperties); + } + + pAudioClient2->lpVtbl->Release(pAudioClient2); + } + + + /* Here is where we try to determine the best format to use with the device. If the client if wanting exclusive mode, first try finding the best format for that. If this fails, fall back to shared mode. */ + result = MA_FORMAT_NOT_SUPPORTED; + if (pData->shareMode == ma_share_mode_exclusive) { + #ifdef MA_WIN32_DESKTOP + /* In exclusive mode on desktop we always use the backend's native format. */ + ma_IPropertyStore* pStore = NULL; + hr = ma_IMMDevice_OpenPropertyStore(pDeviceInterface, STGM_READ, &pStore); + if (SUCCEEDED(hr)) { + PROPVARIANT prop; + ma_PropVariantInit(&prop); + hr = ma_IPropertyStore_GetValue(pStore, &MA_PKEY_AudioEngine_DeviceFormat, &prop); + if (SUCCEEDED(hr)) { + WAVEFORMATEX* pActualFormat = (WAVEFORMATEX*)prop.blob.pBlobData; + hr = ma_IAudioClient_IsFormatSupported((ma_IAudioClient*)pData->pAudioClient, MA_AUDCLNT_SHAREMODE_EXCLUSIVE, pActualFormat, NULL); + if (SUCCEEDED(hr)) { + ma_copy_memory(&wf, pActualFormat, sizeof(WAVEFORMATEXTENSIBLE)); + } + + ma_PropVariantClear(pContext, &prop); + } + + ma_IPropertyStore_Release(pStore); + } + #else + /* + 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; + #endif + + if (hr == S_OK) { + shareMode = MA_AUDCLNT_SHAREMODE_EXCLUSIVE; + result = MA_SUCCESS; + } else { + result = MA_SHARE_MODE_NOT_SUPPORTED; + } + } else { + /* In shared mode we are always using the format reported by the operating system. */ + WAVEFORMATEXTENSIBLE* pNativeFormat = NULL; + hr = ma_IAudioClient_GetMixFormat((ma_IAudioClient*)pData->pAudioClient, (WAVEFORMATEX**)&pNativeFormat); + if (hr != S_OK) { + result = MA_FORMAT_NOT_SUPPORTED; + } else { + ma_copy_memory(&wf, pNativeFormat, sizeof(wf)); + result = MA_SUCCESS; + } + + ma_CoTaskMemFree(pContext, pNativeFormat); + + shareMode = MA_AUDCLNT_SHAREMODE_SHARED; + } + + /* Return an error if we still haven't found a format. */ + if (result != MA_SUCCESS) { + errorMsg = "[WASAPI] Failed to find best device mix format."; + goto done; + } + + pData->formatOut = ma_format_from_WAVEFORMATEX((WAVEFORMATEX*)&wf); + pData->channelsOut = wf.Format.nChannels; + pData->sampleRateOut = wf.Format.nSamplesPerSec; + + /* Get the internal channel map based on the channel mask. */ + ma_channel_mask_to_channel_map__win32(wf.dwChannelMask, pData->channelsOut, pData->channelMapOut); + + /* If we're using a default buffer size we need to calculate it based on the efficiency of the system. */ + pData->periodsOut = pData->periodsIn; + pData->bufferSizeInFramesOut = pData->bufferSizeInFramesIn; + if (pData->bufferSizeInFramesOut == 0) { + pData->bufferSizeInFramesOut = ma_calculate_buffer_size_in_frames_from_milliseconds(pData->bufferSizeInMillisecondsIn, pData->sampleRateOut); + } + + bufferDurationInMicroseconds = ((ma_uint64)pData->bufferSizeInFramesOut * 1000 * 1000) / pData->sampleRateOut; + + + /* Slightly different initialization for shared and exclusive modes. We try exclusive mode first, and if it fails, fall back to shared mode. */ + if (shareMode == MA_AUDCLNT_SHAREMODE_EXCLUSIVE) { + MA_REFERENCE_TIME bufferDuration = (bufferDurationInMicroseconds / pData->periodsOut) * 10; + + /* + If the periodicy is too small, Initialize() will fail with AUDCLNT_E_INVALID_DEVICE_PERIOD. In this case we should just keep increasing + it and trying it again. + */ + hr = E_FAIL; + for (;;) { + hr = ma_IAudioClient_Initialize((ma_IAudioClient*)pData->pAudioClient, shareMode, MA_AUDCLNT_STREAMFLAGS_EVENTCALLBACK, bufferDuration, bufferDuration, (WAVEFORMATEX*)&wf, NULL); + if (hr == MA_AUDCLNT_E_INVALID_DEVICE_PERIOD) { + if (bufferDuration > 500*10000) { + break; + } else { + if (bufferDuration == 0) { /* <-- Just a sanity check to prevent an infinit loop. Should never happen, but it makes me feel better. */ + break; + } + + bufferDuration = bufferDuration * 2; + continue; + } + } else { + break; + } + } + + if (hr == MA_AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { + ma_uint32 bufferSizeInFrames; + hr = ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pData->pAudioClient, &bufferSizeInFrames); + if (SUCCEEDED(hr)) { + bufferDuration = (MA_REFERENCE_TIME)((10000.0 * 1000 / wf.Format.nSamplesPerSec * bufferSizeInFrames) + 0.5); + + /* Unfortunately we need to release and re-acquire the audio client according to MSDN. Seems silly - why not just call IAudioClient_Initialize() again?! */ + ma_IAudioClient_Release((ma_IAudioClient*)pData->pAudioClient); + + #ifdef MA_WIN32_DESKTOP + hr = ma_IMMDevice_Activate(pDeviceInterface, &MA_IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pData->pAudioClient); + #else + hr = ma_IUnknown_QueryInterface(pDeviceInterface, &MA_IID_IAudioClient, (void**)&pData->pAudioClient); + #endif + + if (SUCCEEDED(hr)) { + hr = ma_IAudioClient_Initialize((ma_IAudioClient*)pData->pAudioClient, shareMode, MA_AUDCLNT_STREAMFLAGS_EVENTCALLBACK, bufferDuration, bufferDuration, (WAVEFORMATEX*)&wf, NULL); + } + } + } + + if (FAILED(hr)) { + /* Failed to initialize in exclusive mode. Don't fall back to shared mode - instead tell the client about it. They can reinitialize in shared mode if they want. */ + if (hr == E_ACCESSDENIED) { + errorMsg = "[WASAPI] Failed to initialize device in exclusive mode. Access denied.", result = MA_ACCESS_DENIED; + } else if (hr == MA_AUDCLNT_E_DEVICE_IN_USE) { + errorMsg = "[WASAPI] Failed to initialize device in exclusive mode. Device in use.", result = MA_DEVICE_BUSY; + } else { + errorMsg = "[WASAPI] Failed to initialize device in exclusive mode."; result = MA_SHARE_MODE_NOT_SUPPORTED; + } + goto done; + } + } + + if (shareMode == MA_AUDCLNT_SHAREMODE_SHARED) { + /* Low latency shared mode via IAudioClient3. */ +#ifndef MA_WASAPI_NO_LOW_LATENCY_SHARED_MODE + 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; + hr = ma_IAudioClient3_GetSharedModeEnginePeriod(pAudioClient3, (WAVEFORMATEX*)&wf, &defaultPeriodInFrames, &fundamentalPeriodInFrames, &minPeriodInFrames, &maxPeriodInFrames); + if (SUCCEEDED(hr)) { + UINT32 desiredPeriodInFrames = pData->bufferSizeInFramesOut / pData->periodsOut; + UINT32 actualPeriodInFrames = desiredPeriodInFrames; + + /* Make sure the period size is a multiple of fundamentalPeriodInFrames. */ + actualPeriodInFrames = actualPeriodInFrames / fundamentalPeriodInFrames; + actualPeriodInFrames = actualPeriodInFrames * fundamentalPeriodInFrames; + + /* The period needs to be clamped between minPeriodInFrames and maxPeriodInFrames. */ + actualPeriodInFrames = ma_clamp(actualPeriodInFrames, minPeriodInFrames, maxPeriodInFrames); + + /* If the client requested a largish buffer than we don't actually want to use low latency shared mode because it forces small buffers. */ + if (actualPeriodInFrames >= desiredPeriodInFrames) { + hr = ma_IAudioClient3_InitializeSharedAudioStream(pAudioClient3, MA_AUDCLNT_STREAMFLAGS_EVENTCALLBACK, actualPeriodInFrames, (WAVEFORMATEX*)&wf, NULL); + if (SUCCEEDED(hr)) { + wasInitializedUsingIAudioClient3 = MA_TRUE; + pData->periodSizeInFramesOut = actualPeriodInFrames; + pData->bufferSizeInFramesOut = actualPeriodInFrames * pData->periodsOut; + } + } + } + + ma_IAudioClient3_Release(pAudioClient3); + pAudioClient3 = NULL; + } +#endif + + /* If we don't have an IAudioClient3 then we need to use the normal initialization routine. */ + if (!wasInitializedUsingIAudioClient3) { + MA_REFERENCE_TIME bufferDuration = bufferDurationInMicroseconds*10; + hr = ma_IAudioClient_Initialize((ma_IAudioClient*)pData->pAudioClient, shareMode, MA_AUDCLNT_STREAMFLAGS_EVENTCALLBACK, bufferDuration, 0, (WAVEFORMATEX*)&wf, NULL); + if (FAILED(hr)) { + if (hr == E_ACCESSDENIED) { + errorMsg = "[WASAPI] Failed to initialize device. Access denied.", result = MA_ACCESS_DENIED; + } else if (hr == MA_AUDCLNT_E_DEVICE_IN_USE) { + errorMsg = "[WASAPI] Failed to initialize device. Device in use.", result = MA_DEVICE_BUSY; + } else { + errorMsg = "[WASAPI] Failed to initialize device.", result = MA_FAILED_TO_OPEN_BACKEND_DEVICE; + } + + goto done; + } + } + } + + if (!wasInitializedUsingIAudioClient3) { + hr = ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pData->pAudioClient, &pData->bufferSizeInFramesOut); + if (FAILED(hr)) { + errorMsg = "[WASAPI] Failed to get audio client's actual buffer size.", result = MA_FAILED_TO_OPEN_BACKEND_DEVICE; + goto done; + } + + pData->periodSizeInFramesOut = pData->bufferSizeInFramesOut / pData->periodsOut; + } + + if (deviceType == ma_device_type_playback) { + hr = ma_IAudioClient_GetService((ma_IAudioClient*)pData->pAudioClient, &MA_IID_IAudioRenderClient, (void**)&pData->pRenderClient); + } else { + hr = ma_IAudioClient_GetService((ma_IAudioClient*)pData->pAudioClient, &MA_IID_IAudioCaptureClient, (void**)&pData->pCaptureClient); + } + + if (FAILED(hr)) { + errorMsg = "[WASAPI] Failed to get audio client service.", result = MA_API_NOT_FOUND; + goto done; + } + + + /* Grab the name of the device. */ +#ifdef MA_WIN32_DESKTOP + { + ma_IPropertyStore *pProperties; + hr = ma_IMMDevice_OpenPropertyStore(pDeviceInterface, STGM_READ, &pProperties); + if (SUCCEEDED(hr)) { + PROPVARIANT varName; + ma_PropVariantInit(&varName); + hr = ma_IPropertyStore_GetValue(pProperties, &MA_PKEY_Device_FriendlyName, &varName); + if (SUCCEEDED(hr)) { + WideCharToMultiByte(CP_UTF8, 0, varName.pwszVal, -1, pData->deviceName, sizeof(pData->deviceName), 0, FALSE); + ma_PropVariantClear(pContext, &varName); + } + + ma_IPropertyStore_Release(pProperties); + } + } +#endif + +done: + /* Clean up. */ +#ifdef MA_WIN32_DESKTOP + if (pDeviceInterface != NULL) { + ma_IMMDevice_Release(pDeviceInterface); + } +#else + if (pDeviceInterface != NULL) { + ma_IUnknown_Release(pDeviceInterface); + } +#endif + + if (result != MA_SUCCESS) { + if (pData->pRenderClient) { + ma_IAudioRenderClient_Release((ma_IAudioRenderClient*)pData->pRenderClient); + pData->pRenderClient = NULL; + } + if (pData->pCaptureClient) { + ma_IAudioCaptureClient_Release((ma_IAudioCaptureClient*)pData->pCaptureClient); + pData->pCaptureClient = NULL; + } + if (pData->pAudioClient) { + ma_IAudioClient_Release((ma_IAudioClient*)pData->pAudioClient); + pData->pAudioClient = NULL; + } + + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, errorMsg, result); + } else { + return MA_SUCCESS; + } +} + +ma_result ma_device_reinit__wasapi(ma_device* pDevice, ma_device_type deviceType) +{ + ma_device_init_internal_data__wasapi data; + ma_result result; + + ma_assert(pDevice != NULL); + + /* We only re-initialize the playback or capture device. Never a full-duplex device. */ + if (deviceType == ma_device_type_duplex) { + return MA_INVALID_ARGS; + } + + if (deviceType == ma_device_type_capture) { + 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; + } else { + 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; + } + + data.sampleRateIn = pDevice->sampleRate; + data.usingDefaultSampleRate = pDevice->usingDefaultSampleRate; + data.bufferSizeInFramesIn = pDevice->wasapi.originalBufferSizeInFrames; + data.bufferSizeInMillisecondsIn = pDevice->wasapi.originalBufferSizeInMilliseconds; + data.periodsIn = pDevice->wasapi.originalPeriods; + result = ma_device_init_internal__wasapi(pDevice->pContext, deviceType, NULL, &data); + if (result != MA_SUCCESS) { + return result; + } + + /* 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) { + 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; + + 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.internalBufferSizeInFrames = data.bufferSizeInFramesOut; + pDevice->capture.internalPeriods = data.periodsOut; + ma_strcpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), data.deviceName); + + ma_IAudioClient_SetEventHandle((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, pDevice->wasapi.hEventCapture); + + pDevice->wasapi.periodSizeInFramesCapture = data.periodSizeInFramesOut; + ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &pDevice->wasapi.actualBufferSizeInFramesCapture); + + /* 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_FAILED_TO_START_BACKEND_DEVICE); + } + } + } + + 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; + + 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.internalBufferSizeInFrames = data.bufferSizeInFramesOut; + pDevice->playback.internalPeriods = data.periodsOut; + ma_strcpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), data.deviceName); + + ma_IAudioClient_SetEventHandle((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, pDevice->wasapi.hEventPlayback); + + pDevice->wasapi.periodSizeInFramesPlayback = data.periodSizeInFramesOut; + ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &pDevice->wasapi.actualBufferSizeInFramesPlayback); + + /* 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_FAILED_TO_START_BACKEND_DEVICE); + } + } + } + + return MA_SUCCESS; +} + +ma_result ma_device_init__wasapi(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +{ + ma_result result = MA_SUCCESS; + + (void)pContext; + + ma_assert(pContext != NULL); + ma_assert(pDevice != NULL); + + ma_zero_object(&pDevice->wasapi); + pDevice->wasapi.originalBufferSizeInFrames = pConfig->bufferSizeInFrames; + pDevice->wasapi.originalBufferSizeInMilliseconds = pConfig->bufferSizeInMilliseconds; + pDevice->wasapi.originalPeriods = pConfig->periods; + + if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { + 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.bufferSizeInFramesIn = pConfig->bufferSizeInFrames; + data.bufferSizeInMillisecondsIn = pConfig->bufferSizeInMilliseconds; + data.periodsIn = pConfig->periods; + + result = ma_device_init_internal__wasapi(pDevice->pContext, ma_device_type_capture, pConfig->capture.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.internalBufferSizeInFrames = data.bufferSizeInFramesOut; + pDevice->capture.internalPeriods = data.periodsOut; + ma_strcpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), data.deviceName); + + /* + The event for capture needs to be manual reset for the same reason as playback. We keep the initial state set to unsignaled, + however, because we want to block until we actually have something for the first call to ma_device_read(). + */ + pDevice->wasapi.hEventCapture = CreateEventA(NULL, FALSE, FALSE, NULL); /* Auto reset, unsignaled by default. */ + if (pDevice->wasapi.hEventCapture == NULL) { + if (pDevice->wasapi.pCaptureClient != NULL) { + ma_IAudioCaptureClient_Release((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient); + pDevice->wasapi.pCaptureClient = NULL; + } + if (pDevice->wasapi.pAudioClientCapture != NULL) { + ma_IAudioClient_Release((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); + pDevice->wasapi.pAudioClientCapture = NULL; + } + + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for capture.", MA_FAILED_TO_CREATE_EVENT); + } + ma_IAudioClient_SetEventHandle((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, pDevice->wasapi.hEventCapture); + + pDevice->wasapi.periodSizeInFramesCapture = data.periodSizeInFramesOut; + ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &pDevice->wasapi.actualBufferSizeInFramesCapture); + } + + 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.bufferSizeInFramesIn = pConfig->bufferSizeInFrames; + data.bufferSizeInMillisecondsIn = pConfig->bufferSizeInMilliseconds; + data.periodsIn = pConfig->periods; + + result = ma_device_init_internal__wasapi(pDevice->pContext, ma_device_type_playback, pConfig->playback.pDeviceID, &data); + if (result != MA_SUCCESS) { + if (pConfig->deviceType == ma_device_type_duplex) { + if (pDevice->wasapi.pCaptureClient != NULL) { + ma_IAudioCaptureClient_Release((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient); + pDevice->wasapi.pCaptureClient = NULL; + } + if (pDevice->wasapi.pAudioClientCapture != NULL) { + ma_IAudioClient_Release((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); + pDevice->wasapi.pAudioClientCapture = NULL; + } + + CloseHandle(pDevice->wasapi.hEventCapture); + pDevice->wasapi.hEventCapture = NULL; + } + 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.internalBufferSizeInFrames = data.bufferSizeInFramesOut; + pDevice->playback.internalPeriods = data.periodsOut; + ma_strcpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), data.deviceName); + + /* + The event for playback is needs to be manual reset because we want to explicitly control the fact that it becomes signalled + only after the whole available space has been filled, never before. + + The playback event also needs to be initially set to a signaled state so that the first call to ma_device_write() is able + to get passed WaitForMultipleObjects(). + */ + pDevice->wasapi.hEventPlayback = CreateEventA(NULL, FALSE, TRUE, NULL); /* Auto reset, signaled by default. */ + if (pDevice->wasapi.hEventPlayback == NULL) { + if (pConfig->deviceType == ma_device_type_duplex) { + if (pDevice->wasapi.pCaptureClient != NULL) { + ma_IAudioCaptureClient_Release((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient); + pDevice->wasapi.pCaptureClient = NULL; + } + if (pDevice->wasapi.pAudioClientCapture != NULL) { + ma_IAudioClient_Release((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); + pDevice->wasapi.pAudioClientCapture = NULL; + } + + CloseHandle(pDevice->wasapi.hEventCapture); + pDevice->wasapi.hEventCapture = NULL; + } + + if (pDevice->wasapi.pRenderClient != NULL) { + ma_IAudioRenderClient_Release((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient); + pDevice->wasapi.pRenderClient = NULL; + } + if (pDevice->wasapi.pAudioClientPlayback != NULL) { + ma_IAudioClient_Release((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); + pDevice->wasapi.pAudioClientPlayback = NULL; + } + + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for playback.", MA_FAILED_TO_CREATE_EVENT); + } + ma_IAudioClient_SetEventHandle((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, pDevice->wasapi.hEventPlayback); + + pDevice->wasapi.periodSizeInFramesPlayback = data.periodSizeInFramesOut; + ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &pDevice->wasapi.actualBufferSizeInFramesPlayback); + } + + /* + We need to get notifications of when the default device changes. We do this through a device enumerator by + registering a IMMNotificationClient with it. We only care about this if it's the default device. + */ +#ifdef MA_WIN32_DESKTOP + { + ma_IMMDeviceEnumerator* pDeviceEnumerator; + HRESULT hr = ma_CoCreateInstance(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_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + pDevice->wasapi.notificationClient.lpVtbl = (void*)&g_maNotificationCientVtbl; + pDevice->wasapi.notificationClient.counter = 1; + pDevice->wasapi.notificationClient.pDevice = pDevice; + + hr = pDeviceEnumerator->lpVtbl->RegisterEndpointNotificationCallback(pDeviceEnumerator, &pDevice->wasapi.notificationClient); + if (SUCCEEDED(hr)) { + pDevice->wasapi.pDeviceEnumerator = (ma_ptr)pDeviceEnumerator; + } else { + /* Not the end of the world if we fail to register the notification callback. We just won't support automatic stream routing. */ + ma_IMMDeviceEnumerator_Release(pDeviceEnumerator); + } + } +#endif + + ma_atomic_exchange_32(&pDevice->wasapi.isStartedCapture, MA_FALSE); + ma_atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_FALSE); + + return MA_SUCCESS; +} + +ma_result ma_device__get_available_frames__wasapi(ma_device* pDevice, ma_IAudioClient* pAudioClient, ma_uint32* pFrameCount) +{ + ma_uint32 paddingFramesCount; + HRESULT hr; + ma_share_mode shareMode; + + 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_DEVICE_UNAVAILABLE; + } + + /* Slightly different rules for exclusive and shared modes. */ + shareMode = ((ma_ptr)pAudioClient == pDevice->wasapi.pAudioClientPlayback) ? pDevice->playback.shareMode : pDevice->capture.shareMode; + if (shareMode == ma_share_mode_exclusive) { + *pFrameCount = paddingFramesCount; + } else { + if ((ma_ptr)pAudioClient == pDevice->wasapi.pAudioClientPlayback) { + *pFrameCount = pDevice->wasapi.actualBufferSizeInFramesPlayback - paddingFramesCount; + } else { + *pFrameCount = paddingFramesCount; + } + } + + return MA_SUCCESS; +} + +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) { + return pDevice->wasapi.hasDefaultCaptureDeviceChanged; + } + + return MA_FALSE; +} + +ma_result ma_device_reroute__wasapi(ma_device* pDevice, ma_device_type deviceType) +{ + ma_result result; + + if (deviceType == ma_device_type_duplex) { + return MA_INVALID_ARGS; + } + + if (deviceType == ma_device_type_playback) { + ma_atomic_exchange_32(&pDevice->wasapi.hasDefaultPlaybackDeviceChanged, MA_FALSE); + } + if (deviceType == ma_device_type_capture) { + ma_atomic_exchange_32(&pDevice->wasapi.hasDefaultCaptureDeviceChanged, MA_FALSE); + } + + + #ifdef MA_DEBUG_OUTPUT + printf("=== CHANGING DEVICE ===\n"); + #endif + + result = ma_device_reinit__wasapi(pDevice, deviceType); + if (result != MA_SUCCESS) { + return result; + } + + ma_device__post_init_setup(pDevice, deviceType); + + return MA_SUCCESS; +} + + +ma_result ma_device_main_loop__wasapi(ma_device* pDevice) +{ + ma_result result; + HRESULT hr; + ma_bool32 exitLoop = MA_FALSE; + ma_uint32 framesWrittenToPlaybackDevice = 0; + ma_uint32 mappedBufferSizeInFramesCapture = 0; + ma_uint32 mappedBufferSizeInFramesPlayback = 0; + ma_uint32 mappedBufferFramesRemainingCapture = 0; + ma_uint32 mappedBufferFramesRemainingPlayback = 0; + BYTE* pMappedBufferCapture = NULL; + BYTE* pMappedBufferPlayback = NULL; + ma_uint32 bpfCapture = ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + ma_uint32 bpfPlayback = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + ma_uint8 inputDataInExternalFormat[4096]; + ma_uint32 inputDataInExternalFormatCap = sizeof(inputDataInExternalFormat) / bpfCapture; + ma_uint8 outputDataInExternalFormat[4096]; + ma_uint32 outputDataInExternalFormatCap = sizeof(outputDataInExternalFormat) / bpfPlayback; + + ma_assert(pDevice != NULL); + + /* The playback device needs to be started immediately. */ + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + 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_FAILED_TO_START_BACKEND_DEVICE); + } + ma_atomic_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, 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 (pMappedBufferPlayback == 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_FAILED) { + 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_FAILED) { + 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, &pMappedBufferPlayback); + 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_FAILED_TO_MAP_DEVICE_BUFFER); + exitLoop = MA_TRUE; + break; + } + + mappedBufferSizeInFramesPlayback = framesAvailablePlayback; + mappedBufferFramesRemainingPlayback = 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 (pMappedBufferCapture == NULL) { + if (pDevice->capture.shareMode == ma_share_mode_shared) { + if (WaitForSingleObject(pDevice->wasapi.hEventCapture, INFINITE) == WAIT_FAILED) { + 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_FAILED) { + return MA_ERROR; /* Wait failed. */ + } + } + + continue; + } + + /* Getting here means there's data available for writing to the output device. */ + mappedBufferSizeInFramesCapture = framesAvailableCapture; + hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pMappedBufferCapture, &mappedBufferSizeInFramesCapture, &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_FAILED_TO_MAP_DEVICE_BUFFER); + exitLoop = MA_TRUE; + break; + } + + /* TODO: How do we handle the capture flags returned by GetBuffer()? In particular, AUDCLNT_BUFFERFLAGS_SILENT (1). */ + #ifdef MA_DEBUG_OUTPUT + if (flagsCapture != 0) { + printf("[WASAPI] Capture Flags: %d\n", flagsCapture); + } + #endif + + mappedBufferFramesRemainingCapture = mappedBufferSizeInFramesCapture; + + pDevice->capture._dspFrameCount = mappedBufferSizeInFramesCapture; + pDevice->capture._dspFrames = (const ma_uint8*)pMappedBufferCapture; + } + + + /* At this point we should have both input and output data available. We now need to post it to the convert the data and post it to the client. */ + for (;;) { + BYTE* pRunningBufferCapture; + BYTE* pRunningBufferPlayback; + ma_uint32 framesToProcess; + ma_uint32 framesProcessed; + + pRunningBufferCapture = pMappedBufferCapture + ((mappedBufferSizeInFramesCapture - mappedBufferFramesRemainingCapture ) * bpfPlayback); + pRunningBufferPlayback = pMappedBufferPlayback + ((mappedBufferSizeInFramesPlayback - mappedBufferFramesRemainingPlayback) * bpfPlayback); + + /* 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) { + framesProcessed = (ma_uint32)ma_pcm_converter_read(&pDevice->playback.converter, pRunningBufferPlayback, mappedBufferFramesRemainingPlayback); + if (framesProcessed > 0) { + mappedBufferFramesRemainingPlayback -= framesProcessed; + if (mappedBufferFramesRemainingPlayback == 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(mappedBufferFramesRemainingCapture, mappedBufferFramesRemainingPlayback); + framesProcessed = framesToProcess; + + pDevice->onData(pDevice, pRunningBufferPlayback, pRunningBufferCapture, framesToProcess); + + mappedBufferFramesRemainingCapture -= framesProcessed; + mappedBufferFramesRemainingPlayback -= framesProcessed; + + if (mappedBufferFramesRemainingCapture == 0) { + break; /* Exhausted input data. */ + } + if (mappedBufferFramesRemainingPlayback == 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(mappedBufferFramesRemainingCapture, outputDataInExternalFormatCap); + framesProcessed = framesToProcess; + + pDevice->onData(pDevice, outputDataInExternalFormat, pRunningBufferCapture, framesToProcess); + mappedBufferFramesRemainingCapture -= framesProcessed; + + pDevice->playback._dspFrameCount = framesProcessed; + pDevice->playback._dspFrames = (const ma_uint8*)outputDataInExternalFormat; + + if (mappedBufferFramesRemainingCapture == 0) { + break; /* Exhausted input data. */ + } + } else if (pDevice->playback.converter.isPassthrough) { + /* The input buffer requires conversion, the playback buffer is passthrough. */ + framesToProcess = ma_min(inputDataInExternalFormatCap, mappedBufferFramesRemainingPlayback); + framesProcessed = (ma_uint32)ma_pcm_converter_read(&pDevice->capture.converter, inputDataInExternalFormat, framesToProcess); + if (framesProcessed == 0) { + /* Getting here means we've run out of input data. */ + mappedBufferFramesRemainingCapture = 0; + break; + } + + pDevice->onData(pDevice, pRunningBufferPlayback, inputDataInExternalFormat, framesProcessed); + mappedBufferFramesRemainingPlayback -= framesProcessed; + + if (framesProcessed < framesToProcess) { + mappedBufferFramesRemainingCapture = 0; + break; /* Exhausted input data. */ + } + + if (mappedBufferFramesRemainingPlayback == 0) { + break; /* Exhausted output data. */ + } + } else { + framesToProcess = ma_min(inputDataInExternalFormatCap, outputDataInExternalFormatCap); + framesProcessed = (ma_uint32)ma_pcm_converter_read(&pDevice->capture.converter, inputDataInExternalFormat, framesToProcess); + if (framesProcessed == 0) { + /* Getting here means we've run out of input data. */ + mappedBufferFramesRemainingCapture = 0; + break; + } + + pDevice->onData(pDevice, outputDataInExternalFormat, inputDataInExternalFormat, framesProcessed); + + pDevice->playback._dspFrameCount = framesProcessed; + pDevice->playback._dspFrames = (const ma_uint8*)outputDataInExternalFormat; + + if (framesProcessed < framesToProcess) { + /* Getting here means we've run out of input data. */ + mappedBufferFramesRemainingCapture = 0; + break; + } + } + } + + + /* If at this point we've run out of capture data we need to release the buffer. */ + if (mappedBufferFramesRemainingCapture == 0 && pMappedBufferCapture != NULL) { + hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedBufferSizeInFramesCapture); + 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_FAILED_TO_UNMAP_DEVICE_BUFFER); + exitLoop = MA_TRUE; + break; + } + + /*printf("TRACE: Released capture buffer\n");*/ + + pMappedBufferCapture = NULL; + mappedBufferFramesRemainingCapture = 0; + mappedBufferSizeInFramesCapture = 0; + } + + /* Get out of this loop if we're run out of room in the playback buffer. */ + if (mappedBufferFramesRemainingPlayback == 0) { + break; + } + } + + + /* If at this point we've run out of data we need to release the buffer. */ + if (mappedBufferFramesRemainingPlayback == 0 && pMappedBufferPlayback != NULL) { + hr = ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, mappedBufferSizeInFramesPlayback, 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_FAILED_TO_UNMAP_DEVICE_BUFFER); + exitLoop = MA_TRUE; + break; + } + + /*printf("TRACE: Released playback buffer\n");*/ + framesWrittenToPlaybackDevice += mappedBufferSizeInFramesPlayback; + + pMappedBufferPlayback = NULL; + mappedBufferFramesRemainingPlayback = 0; + mappedBufferSizeInFramesPlayback = 0; + } + + if (!pDevice->wasapi.isStartedPlayback) { + if (pDevice->playback.shareMode == ma_share_mode_exclusive || framesWrittenToPlaybackDevice >= (pDevice->playback.internalBufferSizeInFrames/pDevice->playback.internalPeriods)*2) { + 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_FAILED_TO_START_BACKEND_DEVICE); + } + ma_atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_TRUE); + } + } + } break; + + + + case ma_device_type_capture: + { + ma_uint32 framesAvailableCapture; + DWORD flagsCapture; /* Passed to IAudioCaptureClient_GetBuffer(). */ + + /* Wait for data to become available first. */ + if (WaitForSingleObject(pDevice->wasapi.hEventCapture, INFINITE) == WAIT_FAILED) { + 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 a the data buffer in preparation for sending to the client. */ + mappedBufferSizeInFramesCapture = framesAvailableCapture; + hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pMappedBufferCapture, &mappedBufferSizeInFramesCapture, &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_FAILED_TO_MAP_DEVICE_BUFFER); + exitLoop = MA_TRUE; + break; + } + + /* We should have a buffer at this point. */ + ma_device__send_frames_to_client(pDevice, mappedBufferSizeInFramesCapture, pMappedBufferCapture); + + /* At this point we're done with the buffer. */ + hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedBufferSizeInFramesCapture); + pMappedBufferCapture = 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. */ + mappedBufferSizeInFramesCapture = 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_FAILED_TO_UNMAP_DEVICE_BUFFER); + 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_FAILED) { + 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, &pMappedBufferPlayback); + 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_FAILED_TO_MAP_DEVICE_BUFFER); + exitLoop = MA_TRUE; + break; + } + + /* We should have a buffer at this point. */ + ma_device__read_frames_from_client(pDevice, framesAvailablePlayback, pMappedBufferPlayback); + + /* 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); + pMappedBufferPlayback = 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. */ + mappedBufferSizeInFramesPlayback = 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_FAILED_TO_UNMAP_DEVICE_BUFFER); + exitLoop = MA_TRUE; + break; + } + + framesWrittenToPlaybackDevice += framesAvailablePlayback; + if (!pDevice->wasapi.isStartedPlayback) { + if (pDevice->playback.shareMode == ma_share_mode_exclusive || framesWrittenToPlaybackDevice >= (pDevice->playback.internalBufferSizeInFrames/pDevice->playback.internalPeriods)*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_FAILED_TO_START_BACKEND_DEVICE); + exitLoop = MA_TRUE; + break; + } + ma_atomic_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) { + /* Any mapped buffers need to be released. */ + if (pMappedBufferCapture != NULL) { + hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedBufferSizeInFramesCapture); + } + + 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_FAILED_TO_STOP_BACKEND_DEVICE); + } + + /* The audio client needs to be reset otherwise restarting will fail. */ + hr = ma_IAudioClient_Reset((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); + if (FAILED(hr)) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal capture device.", MA_FAILED_TO_STOP_BACKEND_DEVICE); + } + + ma_atomic_exchange_32(&pDevice->wasapi.isStartedCapture, MA_FALSE); + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + /* Any mapped buffers need to be released. */ + if (pMappedBufferPlayback != NULL) { + hr = ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, mappedBufferSizeInFramesPlayback, 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 potion of it not getting played. + */ + if (pDevice->wasapi.isStartedPlayback) { + if (pDevice->playback.shareMode == ma_share_mode_exclusive) { + WaitForSingleObject(pDevice->wasapi.hEventPlayback, INFINITE); + } else { + ma_uint32 prevFramesAvaialablePlayback = (size_t)-1; + ma_uint32 framesAvailablePlayback; + for (;;) { + result = ma_device__get_available_frames__wasapi(pDevice, (ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &framesAvailablePlayback); + if (result != MA_SUCCESS) { + break; + } + + if (framesAvailablePlayback >= pDevice->wasapi.actualBufferSizeInFramesPlayback) { + break; + } + + /* + Just a safety check to avoid an infinite loop. If this iteration results in a situation where the number of available frames + has not changed, get out of the loop. I don't think this should ever happen, but I think it's nice to have just in case. + */ + if (framesAvailablePlayback == prevFramesAvaialablePlayback) { + break; + } + prevFramesAvaialablePlayback = framesAvailablePlayback; + + WaitForSingleObject(pDevice->wasapi.hEventPlayback, INFINITE); + ResetEvent(pDevice->wasapi.hEventPlayback); /* Manual reset. */ + } + } + } + + hr = ma_IAudioClient_Stop((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); + if (FAILED(hr)) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal playback device.", MA_FAILED_TO_STOP_BACKEND_DEVICE); + } + + /* The audio client needs to be reset otherwise restarting will fail. */ + hr = ma_IAudioClient_Reset((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); + if (FAILED(hr)) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal playback device.", MA_FAILED_TO_STOP_BACKEND_DEVICE); + } + + ma_atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_FALSE); + } + + return MA_SUCCESS; +} + +ma_result ma_context_uninit__wasapi(ma_context* pContext) +{ + ma_assert(pContext != NULL); + ma_assert(pContext->backend == ma_backend_wasapi); + (void)pContext; + + return MA_SUCCESS; +} + +ma_result ma_context_init__wasapi(const ma_context_config* pConfig, ma_context* pContext) +{ + ma_result result = MA_SUCCESS; + + ma_assert(pContext != NULL); + + (void)pConfig; + +#ifdef MA_WIN32_DESKTOP + /* + WASAPI is only supported in Vista SP1 and newer. The reason for SP1 and not the base version of Vista is that event-driven + exclusive mode does not work until SP1. + + Unfortunately older compilers don't define these functions so we need to dynamically load them in order to avoid a lin error. + */ + { + ma_OSVERSIONINFOEXW osvi; + ma_handle kernel32DLL; + ma_PFNVerifyVersionInfoW _VerifyVersionInfoW; + ma_PFNVerSetConditionMask _VerSetConditionMask; + + kernel32DLL = ma_dlopen(pContext, "kernel32.dll"); + if (kernel32DLL == NULL) { + return MA_NO_BACKEND; + } + + _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); + return MA_NO_BACKEND; + } + + ma_zero_object(&osvi); + osvi.dwOSVersionInfoSize = sizeof(osvi); + osvi.dwMajorVersion = HIBYTE(MA_WIN32_WINNT_VISTA); + osvi.dwMinorVersion = LOBYTE(MA_WIN32_WINNT_VISTA); + osvi.wServicePackMajor = 1; + if (_VerifyVersionInfoW(&osvi, MA_VER_MAJORVERSION | MA_VER_MINORVERSION | MA_VER_SERVICEPACKMAJOR, _VerSetConditionMask(_VerSetConditionMask(_VerSetConditionMask(0, MA_VER_MAJORVERSION, MA_VER_GREATER_EQUAL), MA_VER_MINORVERSION, MA_VER_GREATER_EQUAL), MA_VER_SERVICEPACKMAJOR, MA_VER_GREATER_EQUAL))) { + result = MA_SUCCESS; + } else { + result = MA_NO_BACKEND; + } + + ma_dlclose(pContext, kernel32DLL); + } +#endif + + if (result != MA_SUCCESS) { + 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 = NULL; /* Not used. Stopped in onDeviceMainLoop. */ + pContext->onDeviceWrite = NULL; + pContext->onDeviceRead = NULL; + pContext->onDeviceMainLoop = ma_device_main_loop__wasapi; + + return result; +} +#endif + +/****************************************************************************** + +DirectSound Backend + +******************************************************************************/ +#ifdef MA_HAS_DSOUND +/*#include */ + +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 +#define MA_DSSCL_PRIORITY 2 +#define MA_DSSCL_EXCLUSIVE 3 +#define MA_DSSCL_WRITEPRIMARY 4 + +#define MA_DSCAPS_PRIMARYMONO 0x00000001 +#define MA_DSCAPS_PRIMARYSTEREO 0x00000002 +#define MA_DSCAPS_PRIMARY8BIT 0x00000004 +#define MA_DSCAPS_PRIMARY16BIT 0x00000008 +#define MA_DSCAPS_CONTINUOUSRATE 0x00000010 +#define MA_DSCAPS_EMULDRIVER 0x00000020 +#define MA_DSCAPS_CERTIFIED 0x00000040 +#define MA_DSCAPS_SECONDARYMONO 0x00000100 +#define MA_DSCAPS_SECONDARYSTEREO 0x00000200 +#define MA_DSCAPS_SECONDARY8BIT 0x00000400 +#define MA_DSCAPS_SECONDARY16BIT 0x00000800 + +#define MA_DSBCAPS_PRIMARYBUFFER 0x00000001 +#define MA_DSBCAPS_STATIC 0x00000002 +#define MA_DSBCAPS_LOCHARDWARE 0x00000004 +#define MA_DSBCAPS_LOCSOFTWARE 0x00000008 +#define MA_DSBCAPS_CTRL3D 0x00000010 +#define MA_DSBCAPS_CTRLFREQUENCY 0x00000020 +#define MA_DSBCAPS_CTRLPAN 0x00000040 +#define MA_DSBCAPS_CTRLVOLUME 0x00000080 +#define MA_DSBCAPS_CTRLPOSITIONNOTIFY 0x00000100 +#define MA_DSBCAPS_CTRLFX 0x00000200 +#define MA_DSBCAPS_STICKYFOCUS 0x00004000 +#define MA_DSBCAPS_GLOBALFOCUS 0x00008000 +#define MA_DSBCAPS_GETCURRENTPOSITION2 0x00010000 +#define MA_DSBCAPS_MUTE3DATMAXDISTANCE 0x00020000 +#define MA_DSBCAPS_LOCDEFER 0x00040000 +#define MA_DSBCAPS_TRUEPLAYPOSITION 0x00080000 + +#define MA_DSBPLAY_LOOPING 0x00000001 +#define MA_DSBPLAY_LOCHARDWARE 0x00000002 +#define MA_DSBPLAY_LOCSOFTWARE 0x00000004 +#define MA_DSBPLAY_TERMINATEBY_TIME 0x00000008 +#define MA_DSBPLAY_TERMINATEBY_DISTANCE 0x00000010 +#define MA_DSBPLAY_TERMINATEBY_PRIORITY 0x00000020 + +#define MA_DSCBSTART_LOOPING 0x00000001 + +typedef struct +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwBufferBytes; + DWORD dwReserved; + WAVEFORMATEX* lpwfxFormat; + GUID guid3DAlgorithm; +} MA_DSBUFFERDESC; + +typedef struct +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwBufferBytes; + DWORD dwReserved; + WAVEFORMATEX* lpwfxFormat; + DWORD dwFXCount; + void* lpDSCFXDesc; /* <-- miniaudio doesn't use this, so set to void*. */ +} MA_DSCBUFFERDESC; + +typedef struct +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwMinSecondarySampleRate; + DWORD dwMaxSecondarySampleRate; + DWORD dwPrimaryBuffers; + DWORD dwMaxHwMixingAllBuffers; + DWORD dwMaxHwMixingStaticBuffers; + DWORD dwMaxHwMixingStreamingBuffers; + DWORD dwFreeHwMixingAllBuffers; + DWORD dwFreeHwMixingStaticBuffers; + DWORD dwFreeHwMixingStreamingBuffers; + DWORD dwMaxHw3DAllBuffers; + DWORD dwMaxHw3DStaticBuffers; + DWORD dwMaxHw3DStreamingBuffers; + DWORD dwFreeHw3DAllBuffers; + DWORD dwFreeHw3DStaticBuffers; + DWORD dwFreeHw3DStreamingBuffers; + DWORD dwTotalHwMemBytes; + DWORD dwFreeHwMemBytes; + DWORD dwMaxContigFreeHwMemBytes; + DWORD dwUnlockTransferRateHwBuffers; + DWORD dwPlayCpuOverheadSwBuffers; + DWORD dwReserved1; + DWORD dwReserved2; +} MA_DSCAPS; + +typedef struct +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwBufferBytes; + DWORD dwUnlockTransferRate; + DWORD dwPlayCpuOverhead; +} MA_DSBCAPS; + +typedef struct +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwFormats; + DWORD dwChannels; +} MA_DSCCAPS; + +typedef struct +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwBufferBytes; + DWORD dwReserved; +} MA_DSCBCAPS; + +typedef struct +{ + DWORD dwOffset; + HANDLE hEventNotify; +} MA_DSBPOSITIONNOTIFY; + +typedef struct ma_IDirectSound ma_IDirectSound; +typedef struct ma_IDirectSoundBuffer ma_IDirectSoundBuffer; +typedef struct ma_IDirectSoundCapture ma_IDirectSoundCapture; +typedef struct ma_IDirectSoundCaptureBuffer ma_IDirectSoundCaptureBuffer; +typedef struct ma_IDirectSoundNotify ma_IDirectSoundNotify; + + +/* +COM objects. The way these work is that you have a vtable (a list of function pointers, kind of +like how C++ works internally), and then you have a structure with a single member, which is a +pointer to the vtable. The vtable is where the methods of the object are defined. Methods need +to be in a specific order, and parent classes need to have their methods declared first. +*/ + +/* IDirectSound */ +typedef struct +{ + /* IUnknown */ + HRESULT (STDMETHODCALLTYPE * QueryInterface)(ma_IDirectSound* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (ma_IDirectSound* pThis); + ULONG (STDMETHODCALLTYPE * Release) (ma_IDirectSound* pThis); + + /* IDirectSound */ + HRESULT (STDMETHODCALLTYPE * CreateSoundBuffer) (ma_IDirectSound* pThis, const MA_DSBUFFERDESC* pDSBufferDesc, ma_IDirectSoundBuffer** ppDSBuffer, void* pUnkOuter); + HRESULT (STDMETHODCALLTYPE * GetCaps) (ma_IDirectSound* pThis, MA_DSCAPS* pDSCaps); + HRESULT (STDMETHODCALLTYPE * DuplicateSoundBuffer)(ma_IDirectSound* pThis, ma_IDirectSoundBuffer* pDSBufferOriginal, ma_IDirectSoundBuffer** ppDSBufferDuplicate); + HRESULT (STDMETHODCALLTYPE * SetCooperativeLevel) (ma_IDirectSound* pThis, HWND hwnd, DWORD dwLevel); + HRESULT (STDMETHODCALLTYPE * Compact) (ma_IDirectSound* pThis); + HRESULT (STDMETHODCALLTYPE * GetSpeakerConfig) (ma_IDirectSound* pThis, DWORD* pSpeakerConfig); + HRESULT (STDMETHODCALLTYPE * SetSpeakerConfig) (ma_IDirectSound* pThis, DWORD dwSpeakerConfig); + HRESULT (STDMETHODCALLTYPE * Initialize) (ma_IDirectSound* pThis, const GUID* pGuidDevice); +} ma_IDirectSoundVtbl; +struct ma_IDirectSound +{ + ma_IDirectSoundVtbl* lpVtbl; +}; +HRESULT ma_IDirectSound_QueryInterface(ma_IDirectSound* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +ULONG ma_IDirectSound_AddRef(ma_IDirectSound* pThis) { return pThis->lpVtbl->AddRef(pThis); } +ULONG ma_IDirectSound_Release(ma_IDirectSound* pThis) { return pThis->lpVtbl->Release(pThis); } +HRESULT ma_IDirectSound_CreateSoundBuffer(ma_IDirectSound* pThis, const MA_DSBUFFERDESC* pDSBufferDesc, ma_IDirectSoundBuffer** ppDSBuffer, void* pUnkOuter) { return pThis->lpVtbl->CreateSoundBuffer(pThis, pDSBufferDesc, ppDSBuffer, pUnkOuter); } +HRESULT ma_IDirectSound_GetCaps(ma_IDirectSound* pThis, MA_DSCAPS* pDSCaps) { return pThis->lpVtbl->GetCaps(pThis, pDSCaps); } +HRESULT ma_IDirectSound_DuplicateSoundBuffer(ma_IDirectSound* pThis, ma_IDirectSoundBuffer* pDSBufferOriginal, ma_IDirectSoundBuffer** ppDSBufferDuplicate) { return pThis->lpVtbl->DuplicateSoundBuffer(pThis, pDSBufferOriginal, ppDSBufferDuplicate); } +HRESULT ma_IDirectSound_SetCooperativeLevel(ma_IDirectSound* pThis, HWND hwnd, DWORD dwLevel) { return pThis->lpVtbl->SetCooperativeLevel(pThis, hwnd, dwLevel); } +HRESULT ma_IDirectSound_Compact(ma_IDirectSound* pThis) { return pThis->lpVtbl->Compact(pThis); } +HRESULT ma_IDirectSound_GetSpeakerConfig(ma_IDirectSound* pThis, DWORD* pSpeakerConfig) { return pThis->lpVtbl->GetSpeakerConfig(pThis, pSpeakerConfig); } +HRESULT ma_IDirectSound_SetSpeakerConfig(ma_IDirectSound* pThis, DWORD dwSpeakerConfig) { return pThis->lpVtbl->SetSpeakerConfig(pThis, dwSpeakerConfig); } +HRESULT ma_IDirectSound_Initialize(ma_IDirectSound* pThis, const GUID* pGuidDevice) { return pThis->lpVtbl->Initialize(pThis, pGuidDevice); } + + +/* IDirectSoundBuffer */ +typedef struct +{ + /* IUnknown */ + HRESULT (STDMETHODCALLTYPE * QueryInterface)(ma_IDirectSoundBuffer* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (ma_IDirectSoundBuffer* pThis); + ULONG (STDMETHODCALLTYPE * Release) (ma_IDirectSoundBuffer* pThis); + + /* IDirectSoundBuffer */ + HRESULT (STDMETHODCALLTYPE * GetCaps) (ma_IDirectSoundBuffer* pThis, MA_DSBCAPS* pDSBufferCaps); + HRESULT (STDMETHODCALLTYPE * GetCurrentPosition)(ma_IDirectSoundBuffer* pThis, DWORD* pCurrentPlayCursor, DWORD* pCurrentWriteCursor); + HRESULT (STDMETHODCALLTYPE * GetFormat) (ma_IDirectSoundBuffer* pThis, WAVEFORMATEX* pFormat, DWORD dwSizeAllocated, DWORD* pSizeWritten); + HRESULT (STDMETHODCALLTYPE * GetVolume) (ma_IDirectSoundBuffer* pThis, LONG* pVolume); + HRESULT (STDMETHODCALLTYPE * GetPan) (ma_IDirectSoundBuffer* pThis, LONG* pPan); + HRESULT (STDMETHODCALLTYPE * GetFrequency) (ma_IDirectSoundBuffer* pThis, DWORD* pFrequency); + HRESULT (STDMETHODCALLTYPE * GetStatus) (ma_IDirectSoundBuffer* pThis, DWORD* pStatus); + HRESULT (STDMETHODCALLTYPE * Initialize) (ma_IDirectSoundBuffer* pThis, ma_IDirectSound* pDirectSound, const MA_DSBUFFERDESC* pDSBufferDesc); + HRESULT (STDMETHODCALLTYPE * Lock) (ma_IDirectSoundBuffer* pThis, DWORD dwOffset, DWORD dwBytes, void** ppAudioPtr1, DWORD* pAudioBytes1, void** ppAudioPtr2, DWORD* pAudioBytes2, DWORD dwFlags); + HRESULT (STDMETHODCALLTYPE * Play) (ma_IDirectSoundBuffer* pThis, DWORD dwReserved1, DWORD dwPriority, DWORD dwFlags); + HRESULT (STDMETHODCALLTYPE * SetCurrentPosition)(ma_IDirectSoundBuffer* pThis, DWORD dwNewPosition); + HRESULT (STDMETHODCALLTYPE * SetFormat) (ma_IDirectSoundBuffer* pThis, const WAVEFORMATEX* pFormat); + HRESULT (STDMETHODCALLTYPE * SetVolume) (ma_IDirectSoundBuffer* pThis, LONG volume); + HRESULT (STDMETHODCALLTYPE * SetPan) (ma_IDirectSoundBuffer* pThis, LONG pan); + HRESULT (STDMETHODCALLTYPE * SetFrequency) (ma_IDirectSoundBuffer* pThis, DWORD dwFrequency); + HRESULT (STDMETHODCALLTYPE * Stop) (ma_IDirectSoundBuffer* pThis); + HRESULT (STDMETHODCALLTYPE * Unlock) (ma_IDirectSoundBuffer* pThis, void* pAudioPtr1, DWORD dwAudioBytes1, void* pAudioPtr2, DWORD dwAudioBytes2); + HRESULT (STDMETHODCALLTYPE * Restore) (ma_IDirectSoundBuffer* pThis); +} ma_IDirectSoundBufferVtbl; +struct ma_IDirectSoundBuffer +{ + ma_IDirectSoundBufferVtbl* lpVtbl; +}; +HRESULT ma_IDirectSoundBuffer_QueryInterface(ma_IDirectSoundBuffer* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +ULONG ma_IDirectSoundBuffer_AddRef(ma_IDirectSoundBuffer* pThis) { return pThis->lpVtbl->AddRef(pThis); } +ULONG ma_IDirectSoundBuffer_Release(ma_IDirectSoundBuffer* pThis) { return pThis->lpVtbl->Release(pThis); } +HRESULT ma_IDirectSoundBuffer_GetCaps(ma_IDirectSoundBuffer* pThis, MA_DSBCAPS* pDSBufferCaps) { return pThis->lpVtbl->GetCaps(pThis, pDSBufferCaps); } +HRESULT ma_IDirectSoundBuffer_GetCurrentPosition(ma_IDirectSoundBuffer* pThis, DWORD* pCurrentPlayCursor, DWORD* pCurrentWriteCursor) { return pThis->lpVtbl->GetCurrentPosition(pThis, pCurrentPlayCursor, pCurrentWriteCursor); } +HRESULT ma_IDirectSoundBuffer_GetFormat(ma_IDirectSoundBuffer* pThis, WAVEFORMATEX* pFormat, DWORD dwSizeAllocated, DWORD* pSizeWritten) { return pThis->lpVtbl->GetFormat(pThis, pFormat, dwSizeAllocated, pSizeWritten); } +HRESULT ma_IDirectSoundBuffer_GetVolume(ma_IDirectSoundBuffer* pThis, LONG* pVolume) { return pThis->lpVtbl->GetVolume(pThis, pVolume); } +HRESULT ma_IDirectSoundBuffer_GetPan(ma_IDirectSoundBuffer* pThis, LONG* pPan) { return pThis->lpVtbl->GetPan(pThis, pPan); } +HRESULT ma_IDirectSoundBuffer_GetFrequency(ma_IDirectSoundBuffer* pThis, DWORD* pFrequency) { return pThis->lpVtbl->GetFrequency(pThis, pFrequency); } +HRESULT ma_IDirectSoundBuffer_GetStatus(ma_IDirectSoundBuffer* pThis, DWORD* pStatus) { return pThis->lpVtbl->GetStatus(pThis, pStatus); } +HRESULT ma_IDirectSoundBuffer_Initialize(ma_IDirectSoundBuffer* pThis, ma_IDirectSound* pDirectSound, const MA_DSBUFFERDESC* pDSBufferDesc) { return pThis->lpVtbl->Initialize(pThis, pDirectSound, pDSBufferDesc); } +HRESULT ma_IDirectSoundBuffer_Lock(ma_IDirectSoundBuffer* pThis, DWORD dwOffset, DWORD dwBytes, void** ppAudioPtr1, DWORD* pAudioBytes1, void** ppAudioPtr2, DWORD* pAudioBytes2, DWORD dwFlags) { return pThis->lpVtbl->Lock(pThis, dwOffset, dwBytes, ppAudioPtr1, pAudioBytes1, ppAudioPtr2, pAudioBytes2, dwFlags); } +HRESULT ma_IDirectSoundBuffer_Play(ma_IDirectSoundBuffer* pThis, DWORD dwReserved1, DWORD dwPriority, DWORD dwFlags) { return pThis->lpVtbl->Play(pThis, dwReserved1, dwPriority, dwFlags); } +HRESULT ma_IDirectSoundBuffer_SetCurrentPosition(ma_IDirectSoundBuffer* pThis, DWORD dwNewPosition) { return pThis->lpVtbl->SetCurrentPosition(pThis, dwNewPosition); } +HRESULT ma_IDirectSoundBuffer_SetFormat(ma_IDirectSoundBuffer* pThis, const WAVEFORMATEX* pFormat) { return pThis->lpVtbl->SetFormat(pThis, pFormat); } +HRESULT ma_IDirectSoundBuffer_SetVolume(ma_IDirectSoundBuffer* pThis, LONG volume) { return pThis->lpVtbl->SetVolume(pThis, volume); } +HRESULT ma_IDirectSoundBuffer_SetPan(ma_IDirectSoundBuffer* pThis, LONG pan) { return pThis->lpVtbl->SetPan(pThis, pan); } +HRESULT ma_IDirectSoundBuffer_SetFrequency(ma_IDirectSoundBuffer* pThis, DWORD dwFrequency) { return pThis->lpVtbl->SetFrequency(pThis, dwFrequency); } +HRESULT ma_IDirectSoundBuffer_Stop(ma_IDirectSoundBuffer* pThis) { return pThis->lpVtbl->Stop(pThis); } +HRESULT ma_IDirectSoundBuffer_Unlock(ma_IDirectSoundBuffer* pThis, void* pAudioPtr1, DWORD dwAudioBytes1, void* pAudioPtr2, DWORD dwAudioBytes2) { return pThis->lpVtbl->Unlock(pThis, pAudioPtr1, dwAudioBytes1, pAudioPtr2, dwAudioBytes2); } +HRESULT ma_IDirectSoundBuffer_Restore(ma_IDirectSoundBuffer* pThis) { return pThis->lpVtbl->Restore(pThis); } + + +/* IDirectSoundCapture */ +typedef struct +{ + /* IUnknown */ + HRESULT (STDMETHODCALLTYPE * QueryInterface)(ma_IDirectSoundCapture* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (ma_IDirectSoundCapture* pThis); + ULONG (STDMETHODCALLTYPE * Release) (ma_IDirectSoundCapture* pThis); + + /* IDirectSoundCapture */ + HRESULT (STDMETHODCALLTYPE * CreateCaptureBuffer)(ma_IDirectSoundCapture* pThis, const MA_DSCBUFFERDESC* pDSCBufferDesc, ma_IDirectSoundCaptureBuffer** ppDSCBuffer, void* pUnkOuter); + HRESULT (STDMETHODCALLTYPE * GetCaps) (ma_IDirectSoundCapture* pThis, MA_DSCCAPS* pDSCCaps); + HRESULT (STDMETHODCALLTYPE * Initialize) (ma_IDirectSoundCapture* pThis, const GUID* pGuidDevice); +} ma_IDirectSoundCaptureVtbl; +struct ma_IDirectSoundCapture +{ + ma_IDirectSoundCaptureVtbl* lpVtbl; +}; +HRESULT ma_IDirectSoundCapture_QueryInterface(ma_IDirectSoundCapture* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +ULONG ma_IDirectSoundCapture_AddRef(ma_IDirectSoundCapture* pThis) { return pThis->lpVtbl->AddRef(pThis); } +ULONG ma_IDirectSoundCapture_Release(ma_IDirectSoundCapture* pThis) { return pThis->lpVtbl->Release(pThis); } +HRESULT ma_IDirectSoundCapture_CreateCaptureBuffer(ma_IDirectSoundCapture* pThis, const MA_DSCBUFFERDESC* pDSCBufferDesc, ma_IDirectSoundCaptureBuffer** ppDSCBuffer, void* pUnkOuter) { return pThis->lpVtbl->CreateCaptureBuffer(pThis, pDSCBufferDesc, ppDSCBuffer, pUnkOuter); } +HRESULT ma_IDirectSoundCapture_GetCaps (ma_IDirectSoundCapture* pThis, MA_DSCCAPS* pDSCCaps) { return pThis->lpVtbl->GetCaps(pThis, pDSCCaps); } +HRESULT ma_IDirectSoundCapture_Initialize (ma_IDirectSoundCapture* pThis, const GUID* pGuidDevice) { return pThis->lpVtbl->Initialize(pThis, pGuidDevice); } + + +/* IDirectSoundCaptureBuffer */ +typedef struct +{ + /* IUnknown */ + HRESULT (STDMETHODCALLTYPE * QueryInterface)(ma_IDirectSoundCaptureBuffer* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (ma_IDirectSoundCaptureBuffer* pThis); + ULONG (STDMETHODCALLTYPE * Release) (ma_IDirectSoundCaptureBuffer* pThis); + + /* IDirectSoundCaptureBuffer */ + HRESULT (STDMETHODCALLTYPE * GetCaps) (ma_IDirectSoundCaptureBuffer* pThis, MA_DSCBCAPS* pDSCBCaps); + HRESULT (STDMETHODCALLTYPE * GetCurrentPosition)(ma_IDirectSoundCaptureBuffer* pThis, DWORD* pCapturePosition, DWORD* pReadPosition); + HRESULT (STDMETHODCALLTYPE * GetFormat) (ma_IDirectSoundCaptureBuffer* pThis, WAVEFORMATEX* pFormat, DWORD dwSizeAllocated, DWORD* pSizeWritten); + HRESULT (STDMETHODCALLTYPE * GetStatus) (ma_IDirectSoundCaptureBuffer* pThis, DWORD* pStatus); + HRESULT (STDMETHODCALLTYPE * Initialize) (ma_IDirectSoundCaptureBuffer* pThis, ma_IDirectSoundCapture* pDirectSoundCapture, const MA_DSCBUFFERDESC* pDSCBufferDesc); + HRESULT (STDMETHODCALLTYPE * Lock) (ma_IDirectSoundCaptureBuffer* pThis, DWORD dwOffset, DWORD dwBytes, void** ppAudioPtr1, DWORD* pAudioBytes1, void** ppAudioPtr2, DWORD* pAudioBytes2, DWORD dwFlags); + HRESULT (STDMETHODCALLTYPE * Start) (ma_IDirectSoundCaptureBuffer* pThis, DWORD dwFlags); + HRESULT (STDMETHODCALLTYPE * Stop) (ma_IDirectSoundCaptureBuffer* pThis); + HRESULT (STDMETHODCALLTYPE * Unlock) (ma_IDirectSoundCaptureBuffer* pThis, void* pAudioPtr1, DWORD dwAudioBytes1, void* pAudioPtr2, DWORD dwAudioBytes2); +} ma_IDirectSoundCaptureBufferVtbl; +struct ma_IDirectSoundCaptureBuffer +{ + ma_IDirectSoundCaptureBufferVtbl* lpVtbl; +}; +HRESULT ma_IDirectSoundCaptureBuffer_QueryInterface(ma_IDirectSoundCaptureBuffer* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +ULONG ma_IDirectSoundCaptureBuffer_AddRef(ma_IDirectSoundCaptureBuffer* pThis) { return pThis->lpVtbl->AddRef(pThis); } +ULONG ma_IDirectSoundCaptureBuffer_Release(ma_IDirectSoundCaptureBuffer* pThis) { return pThis->lpVtbl->Release(pThis); } +HRESULT ma_IDirectSoundCaptureBuffer_GetCaps(ma_IDirectSoundCaptureBuffer* pThis, MA_DSCBCAPS* pDSCBCaps) { return pThis->lpVtbl->GetCaps(pThis, pDSCBCaps); } +HRESULT ma_IDirectSoundCaptureBuffer_GetCurrentPosition(ma_IDirectSoundCaptureBuffer* pThis, DWORD* pCapturePosition, DWORD* pReadPosition) { return pThis->lpVtbl->GetCurrentPosition(pThis, pCapturePosition, pReadPosition); } +HRESULT ma_IDirectSoundCaptureBuffer_GetFormat(ma_IDirectSoundCaptureBuffer* pThis, WAVEFORMATEX* pFormat, DWORD dwSizeAllocated, DWORD* pSizeWritten) { return pThis->lpVtbl->GetFormat(pThis, pFormat, dwSizeAllocated, pSizeWritten); } +HRESULT ma_IDirectSoundCaptureBuffer_GetStatus(ma_IDirectSoundCaptureBuffer* pThis, DWORD* pStatus) { return pThis->lpVtbl->GetStatus(pThis, pStatus); } +HRESULT ma_IDirectSoundCaptureBuffer_Initialize(ma_IDirectSoundCaptureBuffer* pThis, ma_IDirectSoundCapture* pDirectSoundCapture, const MA_DSCBUFFERDESC* pDSCBufferDesc) { return pThis->lpVtbl->Initialize(pThis, pDirectSoundCapture, pDSCBufferDesc); } +HRESULT ma_IDirectSoundCaptureBuffer_Lock(ma_IDirectSoundCaptureBuffer* pThis, DWORD dwOffset, DWORD dwBytes, void** ppAudioPtr1, DWORD* pAudioBytes1, void** ppAudioPtr2, DWORD* pAudioBytes2, DWORD dwFlags) { return pThis->lpVtbl->Lock(pThis, dwOffset, dwBytes, ppAudioPtr1, pAudioBytes1, ppAudioPtr2, pAudioBytes2, dwFlags); } +HRESULT ma_IDirectSoundCaptureBuffer_Start(ma_IDirectSoundCaptureBuffer* pThis, DWORD dwFlags) { return pThis->lpVtbl->Start(pThis, dwFlags); } +HRESULT ma_IDirectSoundCaptureBuffer_Stop(ma_IDirectSoundCaptureBuffer* pThis) { return pThis->lpVtbl->Stop(pThis); } +HRESULT ma_IDirectSoundCaptureBuffer_Unlock(ma_IDirectSoundCaptureBuffer* pThis, void* pAudioPtr1, DWORD dwAudioBytes1, void* pAudioPtr2, DWORD dwAudioBytes2) { return pThis->lpVtbl->Unlock(pThis, pAudioPtr1, dwAudioBytes1, pAudioPtr2, dwAudioBytes2); } + + +/* IDirectSoundNotify */ +typedef struct +{ + /* IUnknown */ + HRESULT (STDMETHODCALLTYPE * QueryInterface)(ma_IDirectSoundNotify* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (ma_IDirectSoundNotify* pThis); + ULONG (STDMETHODCALLTYPE * Release) (ma_IDirectSoundNotify* pThis); + + /* IDirectSoundNotify */ + HRESULT (STDMETHODCALLTYPE * SetNotificationPositions)(ma_IDirectSoundNotify* pThis, DWORD dwPositionNotifies, const MA_DSBPOSITIONNOTIFY* pPositionNotifies); +} ma_IDirectSoundNotifyVtbl; +struct ma_IDirectSoundNotify +{ + ma_IDirectSoundNotifyVtbl* lpVtbl; +}; +HRESULT ma_IDirectSoundNotify_QueryInterface(ma_IDirectSoundNotify* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +ULONG ma_IDirectSoundNotify_AddRef(ma_IDirectSoundNotify* pThis) { return pThis->lpVtbl->AddRef(pThis); } +ULONG ma_IDirectSoundNotify_Release(ma_IDirectSoundNotify* pThis) { return pThis->lpVtbl->Release(pThis); } +HRESULT ma_IDirectSoundNotify_SetNotificationPositions(ma_IDirectSoundNotify* pThis, DWORD dwPositionNotifies, const MA_DSBPOSITIONNOTIFY* pPositionNotifies) { return pThis->lpVtbl->SetNotificationPositions(pThis, dwPositionNotifies, pPositionNotifies); } + + +typedef BOOL (CALLBACK * ma_DSEnumCallbackAProc) (LPGUID pDeviceGUID, LPCSTR pDeviceDescription, LPCSTR pModule, LPVOID pContext); +typedef HRESULT (WINAPI * ma_DirectSoundCreateProc) (const GUID* pcGuidDevice, ma_IDirectSound** ppDS8, LPUNKNOWN pUnkOuter); +typedef HRESULT (WINAPI * ma_DirectSoundEnumerateAProc) (ma_DSEnumCallbackAProc pDSEnumCallback, LPVOID pContext); +typedef HRESULT (WINAPI * ma_DirectSoundCaptureCreateProc) (const GUID* pcGuidDevice, ma_IDirectSoundCapture** ppDSC8, LPUNKNOWN pUnkOuter); +typedef HRESULT (WINAPI * ma_DirectSoundCaptureEnumerateAProc)(ma_DSEnumCallbackAProc pDSEnumCallback, LPVOID pContext); + + +/* +Retrieves the channel count and channel map for the given speaker configuration. If the speaker configuration is unknown, +the channel count and channel map will be left unmodified. +*/ +void ma_get_channels_from_speaker_config__dsound(DWORD speakerConfig, WORD* pChannelsOut, DWORD* pChannelMapOut) +{ + WORD channels; + DWORD channelMap; + + channels = 0; + if (pChannelsOut != NULL) { + channels = *pChannelsOut; + } + + channelMap = 0; + if (pChannelMapOut != NULL) { + channelMap = *pChannelMapOut; + } + + /* + The speaker configuration is a combination of speaker config and speaker geometry. The lower 8 bits is what we care about. The upper + 16 bits is for the geometry. + */ + switch ((BYTE)(speakerConfig)) { + case 1 /*DSSPEAKER_HEADPHONE*/: channels = 2; channelMap = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; break; + case 2 /*DSSPEAKER_MONO*/: channels = 1; channelMap = SPEAKER_FRONT_CENTER; break; + case 3 /*DSSPEAKER_QUAD*/: channels = 4; channelMap = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT; break; + case 4 /*DSSPEAKER_STEREO*/: channels = 2; channelMap = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; break; + case 5 /*DSSPEAKER_SURROUND*/: channels = 4; channelMap = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_CENTER; break; + case 6 /*DSSPEAKER_5POINT1_BACK*/ /*DSSPEAKER_5POINT1*/: channels = 6; channelMap = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT; break; + case 7 /*DSSPEAKER_7POINT1_WIDE*/ /*DSSPEAKER_7POINT1*/: channels = 8; channelMap = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER; break; + case 8 /*DSSPEAKER_7POINT1_SURROUND*/: channels = 8; channelMap = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT; break; + case 9 /*DSSPEAKER_5POINT1_SURROUND*/: channels = 6; channelMap = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT; break; + default: break; + } + + if (pChannelsOut != NULL) { + *pChannelsOut = channels; + } + + if (pChannelMapOut != NULL) { + *pChannelMapOut = channelMap; + } +} + + +ma_result ma_context_create_IDirectSound__dsound(ma_context* pContext, ma_share_mode shareMode, const ma_device_id* pDeviceID, ma_IDirectSound** ppDirectSound) +{ + ma_IDirectSound* pDirectSound; + HWND hWnd; + + ma_assert(pContext != NULL); + ma_assert(ppDirectSound != NULL); + + *ppDirectSound = NULL; + pDirectSound = NULL; + + if (FAILED(((ma_DirectSoundCreateProc)pContext->dsound.DirectSoundCreate)((pDeviceID == NULL) ? NULL : (const GUID*)pDeviceID->dsound, &pDirectSound, NULL))) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[DirectSound] DirectSoundCreate() failed for playback device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + /* The cooperative level must be set before doing anything else. */ + hWnd = ((MA_PFN_GetForegroundWindow)pContext->win32.GetForegroundWindow)(); + if (hWnd == NULL) { + hWnd = ((MA_PFN_GetDesktopWindow)pContext->win32.GetDesktopWindow)(); + } + if (FAILED(ma_IDirectSound_SetCooperativeLevel(pDirectSound, hWnd, (shareMode == ma_share_mode_exclusive) ? MA_DSSCL_EXCLUSIVE : MA_DSSCL_PRIORITY))) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_SetCooperateiveLevel() failed for playback device.", MA_SHARE_MODE_NOT_SUPPORTED); + } + + *ppDirectSound = pDirectSound; + return MA_SUCCESS; +} + +ma_result ma_context_create_IDirectSoundCapture__dsound(ma_context* pContext, ma_share_mode shareMode, const ma_device_id* pDeviceID, ma_IDirectSoundCapture** ppDirectSoundCapture) +{ + ma_IDirectSoundCapture* pDirectSoundCapture; + + ma_assert(pContext != NULL); + ma_assert(ppDirectSoundCapture != NULL); + + /* DirectSound does not support exclusive mode for capture. */ + if (shareMode == ma_share_mode_exclusive) { + return MA_SHARE_MODE_NOT_SUPPORTED; + } + + *ppDirectSoundCapture = NULL; + pDirectSoundCapture = NULL; + + if (FAILED(((ma_DirectSoundCaptureCreateProc)pContext->dsound.DirectSoundCaptureCreate)((pDeviceID == NULL) ? NULL : (const GUID*)pDeviceID->dsound, &pDirectSoundCapture, NULL))) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[DirectSound] DirectSoundCaptureCreate() failed for capture device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + *ppDirectSoundCapture = pDirectSoundCapture; + return MA_SUCCESS; +} + +ma_result ma_context_get_format_info_for_IDirectSoundCapture__dsound(ma_context* pContext, ma_IDirectSoundCapture* pDirectSoundCapture, WORD* pChannels, WORD* pBitsPerSample, DWORD* pSampleRate) +{ + MA_DSCCAPS caps; + WORD bitsPerSample; + DWORD sampleRate; + + ma_assert(pContext != NULL); + ma_assert(pDirectSoundCapture != NULL); + + if (pChannels) { + *pChannels = 0; + } + if (pBitsPerSample) { + *pBitsPerSample = 0; + } + if (pSampleRate) { + *pSampleRate = 0; + } + + ma_zero_object(&caps); + caps.dwSize = sizeof(caps); + if (FAILED(ma_IDirectSoundCapture_GetCaps(pDirectSoundCapture, &caps))) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCapture_GetCaps() failed for capture device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (pChannels) { + *pChannels = (WORD)caps.dwChannels; + } + + /* The device can support multiple formats. We just go through the different formats in order of priority and pick the first one. This the same type of system as the WinMM backend. */ + bitsPerSample = 16; + sampleRate = 48000; + + if (caps.dwChannels == 1) { + if ((caps.dwFormats & WAVE_FORMAT_48M16) != 0) { + sampleRate = 48000; + } else if ((caps.dwFormats & WAVE_FORMAT_44M16) != 0) { + sampleRate = 44100; + } else if ((caps.dwFormats & WAVE_FORMAT_2M16) != 0) { + sampleRate = 22050; + } else if ((caps.dwFormats & WAVE_FORMAT_1M16) != 0) { + sampleRate = 11025; + } else if ((caps.dwFormats & WAVE_FORMAT_96M16) != 0) { + sampleRate = 96000; + } else { + bitsPerSample = 8; + if ((caps.dwFormats & WAVE_FORMAT_48M08) != 0) { + sampleRate = 48000; + } else if ((caps.dwFormats & WAVE_FORMAT_44M08) != 0) { + sampleRate = 44100; + } else if ((caps.dwFormats & WAVE_FORMAT_2M08) != 0) { + sampleRate = 22050; + } else if ((caps.dwFormats & WAVE_FORMAT_1M08) != 0) { + sampleRate = 11025; + } else if ((caps.dwFormats & WAVE_FORMAT_96M08) != 0) { + sampleRate = 96000; + } else { + bitsPerSample = 16; /* Didn't find it. Just fall back to 16-bit. */ + } + } + } else if (caps.dwChannels == 2) { + if ((caps.dwFormats & WAVE_FORMAT_48S16) != 0) { + sampleRate = 48000; + } else if ((caps.dwFormats & WAVE_FORMAT_44S16) != 0) { + sampleRate = 44100; + } else if ((caps.dwFormats & WAVE_FORMAT_2S16) != 0) { + sampleRate = 22050; + } else if ((caps.dwFormats & WAVE_FORMAT_1S16) != 0) { + sampleRate = 11025; + } else if ((caps.dwFormats & WAVE_FORMAT_96S16) != 0) { + sampleRate = 96000; + } else { + bitsPerSample = 8; + if ((caps.dwFormats & WAVE_FORMAT_48S08) != 0) { + sampleRate = 48000; + } else if ((caps.dwFormats & WAVE_FORMAT_44S08) != 0) { + sampleRate = 44100; + } else if ((caps.dwFormats & WAVE_FORMAT_2S08) != 0) { + sampleRate = 22050; + } else if ((caps.dwFormats & WAVE_FORMAT_1S08) != 0) { + sampleRate = 11025; + } else if ((caps.dwFormats & WAVE_FORMAT_96S08) != 0) { + sampleRate = 96000; + } else { + bitsPerSample = 16; /* Didn't find it. Just fall back to 16-bit. */ + } + } + } + + if (pBitsPerSample) { + *pBitsPerSample = bitsPerSample; + } + if (pSampleRate) { + *pSampleRate = sampleRate; + } + + return MA_SUCCESS; +} + +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 +{ + ma_context* pContext; + ma_device_type deviceType; + ma_enum_devices_callback_proc callback; + void* pUserData; + ma_bool32 terminated; +} ma_context_enumerate_devices_callback_data__dsound; + +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_zero_object(&deviceInfo); + + /* ID. */ + if (lpGuid != NULL) { + ma_copy_memory(deviceInfo.id.dsound, lpGuid, 16); + } else { + ma_zero_memory(deviceInfo.id.dsound, 16); + } + + /* Name / Description */ + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), lpcstrDescription, (size_t)-1); + + + /* Call the callback function, but make sure we stop enumerating if the callee requested so. */ + ma_assert(pData != NULL); + pData->terminated = !pData->callback(pData->pContext, pData->deviceType, &deviceInfo, pData->pUserData); + if (pData->terminated) { + return FALSE; /* Stop enumeration. */ + } else { + return TRUE; /* Continue enumeration. */ + } + + (void)lpcstrModule; +} + +ma_result ma_context_enumerate_devices__dsound(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_context_enumerate_devices_callback_data__dsound data; + + ma_assert(pContext != NULL); + ma_assert(callback != NULL); + + data.pContext = pContext; + data.callback = callback; + data.pUserData = pUserData; + data.terminated = MA_FALSE; + + /* Playback. */ + if (!data.terminated) { + data.deviceType = ma_device_type_playback; + ((ma_DirectSoundEnumerateAProc)pContext->dsound.DirectSoundEnumerateA)(ma_context_enumerate_devices_callback__dsound, &data); + } + + /* Capture. */ + if (!data.terminated) { + data.deviceType = ma_device_type_capture; + ((ma_DirectSoundCaptureEnumerateAProc)pContext->dsound.DirectSoundCaptureEnumerateA)(ma_context_enumerate_devices_callback__dsound, &data); + } + + return MA_SUCCESS; +} + + +typedef struct +{ + const ma_device_id* pDeviceID; + ma_device_info* pDeviceInfo; + ma_bool32 found; +} ma_context_get_device_info_callback_data__dsound; + +BOOL CALLBACK ma_context_get_device_info_callback__dsound(LPGUID lpGuid, LPCSTR lpcstrDescription, LPCSTR lpcstrModule, LPVOID lpContext) +{ + ma_context_get_device_info_callback_data__dsound* pData = (ma_context_get_device_info_callback_data__dsound*)lpContext; + ma_assert(pData != NULL); + + if ((pData->pDeviceID == NULL || ma_is_guid_equal(pData->pDeviceID->dsound, &MA_GUID_NULL)) && (lpGuid == NULL || ma_is_guid_equal(lpGuid, &MA_GUID_NULL))) { + /* Default device. */ + ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), lpcstrDescription, (size_t)-1); + pData->found = MA_TRUE; + return FALSE; /* Stop enumeration. */ + } else { + /* Not the default device. */ + if (lpGuid != NULL) { + if (memcmp(pData->pDeviceID->dsound, lpGuid, sizeof(pData->pDeviceID->dsound)) == 0) { + ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), lpcstrDescription, (size_t)-1); + pData->found = MA_TRUE; + return FALSE; /* Stop enumeration. */ + } + } + } + + (void)lpcstrModule; + return TRUE; +} + +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) +{ + /* 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; + + /* ID. */ + ma_copy_memory(pDeviceInfo->id.dsound, pDeviceID->dsound, 16); + + /* Name / Description. This is retrieved by enumerating over each device until we find that one that matches the input ID. */ + data.pDeviceID = pDeviceID; + data.pDeviceInfo = pDeviceInfo; + data.found = MA_FALSE; + if (deviceType == ma_device_type_playback) { + ((ma_DirectSoundEnumerateAProc)pContext->dsound.DirectSoundEnumerateA)(ma_context_get_device_info_callback__dsound, &data); + } else { + ((ma_DirectSoundCaptureEnumerateAProc)pContext->dsound.DirectSoundCaptureEnumerateA)(ma_context_get_device_info_callback__dsound, &data); + } + + if (!data.found) { + return MA_NO_DEVICE; + } + } else { + /* I don't think there's a way to get the name of the default device with DirectSound. In this case we just need to use defaults. */ + + /* ID */ + ma_zero_memory(pDeviceInfo->id.dsound, 16); + + /* Name / Description */ + if (deviceType == ma_device_type_playback) { + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + } + } + + /* Retrieving detailed information is slightly different depending on the device type. */ + if (deviceType == ma_device_type_playback) { + /* Playback. */ + ma_IDirectSound* pDirectSound; + ma_result result; + MA_DSCAPS caps; + ma_uint32 iFormat; + + result = ma_context_create_IDirectSound__dsound(pContext, shareMode, pDeviceID, &pDirectSound); + if (result != MA_SUCCESS) { + return result; + } + + ma_zero_object(&caps); + caps.dwSize = sizeof(caps); + if (FAILED(ma_IDirectSound_GetCaps(pDirectSound, &caps))) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_GetCaps() failed for playback device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if ((caps.dwFlags & MA_DSCAPS_PRIMARYSTEREO) != 0) { + /* It supports at least stereo, but could support more. */ + WORD channels = 2; + + /* Look at the speaker configuration to get a better idea on the channel count. */ + DWORD speakerConfig; + if (SUCCEEDED(ma_IDirectSound_GetSpeakerConfig(pDirectSound, &speakerConfig))) { + 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; + } + + /* 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; + } + } 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. */ + } + + ma_IDirectSound_Release(pDirectSound); + } else { + /* + Capture. This is a little different to playback due to the say the supported formats are reported. Technically capture + devices can support a number of different formats, but for simplicity and consistency with ma_device_init() I'm just + reporting the best format. + */ + ma_IDirectSoundCapture* pDirectSoundCapture; + ma_result result; + WORD channels; + WORD bitsPerSample; + DWORD sampleRate; + + result = ma_context_create_IDirectSoundCapture__dsound(pContext, shareMode, pDeviceID, &pDirectSoundCapture); + if (result != MA_SUCCESS) { + return result; + } + + result = ma_context_get_format_info_for_IDirectSoundCapture__dsound(pContext, pDirectSoundCapture, &channels, &bitsPerSample, &sampleRate); + if (result != MA_SUCCESS) { + ma_IDirectSoundCapture_Release(pDirectSoundCapture); + return result; + } + + pDeviceInfo->minChannels = channels; + pDeviceInfo->maxChannels = channels; + pDeviceInfo->minSampleRate = sampleRate; + pDeviceInfo->maxSampleRate = sampleRate; + pDeviceInfo->formatCount = 1; + if (bitsPerSample == 8) { + pDeviceInfo->formats[0] = ma_format_u8; + } else if (bitsPerSample == 16) { + pDeviceInfo->formats[0] = ma_format_s16; + } else if (bitsPerSample == 24) { + pDeviceInfo->formats[0] = ma_format_s24; + } else if (bitsPerSample == 32) { + pDeviceInfo->formats[0] = ma_format_s32; + } else { + ma_IDirectSoundCapture_Release(pDirectSoundCapture); + return MA_FORMAT_NOT_SUPPORTED; + } + + ma_IDirectSoundCapture_Release(pDirectSoundCapture); + } + + return MA_SUCCESS; +} + + +typedef struct +{ + ma_uint32 deviceCount; + ma_uint32 infoCount; + ma_device_info* pInfo; +} ma_device_enum_data__dsound; + +BOOL CALLBACK ma_enum_devices_callback__dsound(LPGUID lpGuid, LPCSTR lpcstrDescription, LPCSTR lpcstrModule, LPVOID lpContext) +{ + ma_device_enum_data__dsound* pData = (ma_device_enum_data__dsound*)lpContext; + ma_assert(pData != NULL); + + if (pData->pInfo != NULL) { + if (pData->infoCount > 0) { + ma_zero_object(pData->pInfo); + ma_strncpy_s(pData->pInfo->name, sizeof(pData->pInfo->name), lpcstrDescription, (size_t)-1); + + if (lpGuid != NULL) { + ma_copy_memory(pData->pInfo->id.dsound, lpGuid, 16); + } else { + ma_zero_memory(pData->pInfo->id.dsound, 16); + } + + pData->pInfo += 1; + pData->infoCount -= 1; + pData->deviceCount += 1; + } + } else { + pData->deviceCount += 1; + } + + (void)lpcstrModule; + return TRUE; +} + +void ma_device_uninit__dsound(ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + if (pDevice->dsound.pCaptureBuffer != NULL) { + ma_IDirectSoundCaptureBuffer_Release((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer); + } + if (pDevice->dsound.pCapture != NULL) { + ma_IDirectSoundCapture_Release((ma_IDirectSoundCapture*)pDevice->dsound.pCapture); + } + + if (pDevice->dsound.pPlaybackBuffer != NULL) { + ma_IDirectSoundBuffer_Release((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer); + } + if (pDevice->dsound.pPlaybackPrimaryBuffer != NULL) { + ma_IDirectSoundBuffer_Release((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackPrimaryBuffer); + } + if (pDevice->dsound.pPlayback != NULL) { + ma_IDirectSound_Release((ma_IDirectSound*)pDevice->dsound.pPlayback); + } +} + +ma_result ma_config_to_WAVEFORMATEXTENSIBLE(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, const ma_channel* pChannelMap, WAVEFORMATEXTENSIBLE* pWF) +{ + GUID subformat; + + switch (format) + { + case ma_format_u8: + case ma_format_s16: + case ma_format_s24: + /*case ma_format_s24_32:*/ + case ma_format_s32: + { + subformat = MA_GUID_KSDATAFORMAT_SUBTYPE_PCM; + } break; + + case ma_format_f32: + { + subformat = MA_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + } break; + + default: + return MA_FORMAT_NOT_SUPPORTED; + } + + ma_zero_object(pWF); + pWF->Format.cbSize = sizeof(*pWF); + pWF->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + pWF->Format.nChannels = (WORD)channels; + pWF->Format.nSamplesPerSec = (DWORD)sampleRate; + pWF->Format.wBitsPerSample = (WORD)ma_get_bytes_per_sample(format)*8; + pWF->Format.nBlockAlign = (pWF->Format.nChannels * pWF->Format.wBitsPerSample) / 8; + pWF->Format.nAvgBytesPerSec = pWF->Format.nBlockAlign * pWF->Format.nSamplesPerSec; + pWF->Samples.wValidBitsPerSample = pWF->Format.wBitsPerSample; + pWF->dwChannelMask = ma_channel_map_to_channel_mask__win32(pChannelMap, channels); + pWF->SubFormat = subformat; + + return MA_SUCCESS; +} + +ma_result ma_device_init__dsound(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +{ + ma_result result; + ma_uint32 bufferSizeInMilliseconds; + + ma_assert(pDevice != NULL); + ma_zero_object(&pDevice->dsound); + + bufferSizeInMilliseconds = pConfig->bufferSizeInMilliseconds; + if (bufferSizeInMilliseconds == 0) { + bufferSizeInMilliseconds = ma_calculate_buffer_size_in_milliseconds_from_frames(pConfig->bufferSizeInFrames, 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) { + bufferSizeInMilliseconds = 20 * pConfig->periods; + } else { + bufferSizeInMilliseconds = 200 * pConfig->periods; + } + } + + /* 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 ((bufferSizeInMilliseconds/pConfig->periods) < 20) { + bufferSizeInMilliseconds = pConfig->periods * 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 + full-duplex mode. + */ + if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { + WAVEFORMATEXTENSIBLE wf; + MA_DSCBUFFERDESC descDS; + ma_uint32 bufferSizeInFrames; + 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); + if (result != MA_SUCCESS) { + return result; + } + + result = ma_context_create_IDirectSoundCapture__dsound(pContext, pConfig->capture.shareMode, pConfig->capture.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); + if (result != MA_SUCCESS) { + ma_device_uninit__dsound(pDevice); + return result; + } + + wf.Format.nBlockAlign = (wf.Format.nChannels * wf.Format.wBitsPerSample) / 8; + wf.Format.nAvgBytesPerSec = wf.Format.nBlockAlign * wf.Format.nSamplesPerSec; + wf.Samples.wValidBitsPerSample = wf.Format.wBitsPerSample; + wf.SubFormat = MA_GUID_KSDATAFORMAT_SUBTYPE_PCM; + + /* The size of the buffer must be a clean multiple of the period count. */ + bufferSizeInFrames = (ma_calculate_buffer_size_in_frames_from_milliseconds(bufferSizeInMilliseconds, wf.Format.nSamplesPerSec) / pConfig->periods) * pConfig->periods; + + ma_zero_object(&descDS); + descDS.dwSize = sizeof(descDS); + descDS.dwFlags = 0; + descDS.dwBufferBytes = bufferSizeInFrames * ma_get_bytes_per_frame(pDevice->capture.internalFormat, wf.Format.nChannels); + descDS.lpwfxFormat = (WAVEFORMATEX*)&wf; + if (FAILED(ma_IDirectSoundCapture_CreateCaptureBuffer((ma_IDirectSoundCapture*)pDevice->dsound.pCapture, &descDS, (ma_IDirectSoundCaptureBuffer**)&pDevice->dsound.pCaptureBuffer, NULL))) { + ma_device_uninit__dsound(pDevice); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCapture_CreateCaptureBuffer() failed for capture device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + /* Get the _actual_ properties of the buffer. */ + pActualFormat = (WAVEFORMATEXTENSIBLE*)rawdata; + if (FAILED(ma_IDirectSoundCaptureBuffer_GetFormat((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, (WAVEFORMATEX*)pActualFormat, sizeof(rawdata), NULL))) { + ma_device_uninit__dsound(pDevice); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to retrieve the actual format of the capture device's buffer.", MA_FORMAT_NOT_SUPPORTED); + } + + pDevice->capture.internalFormat = ma_format_from_WAVEFORMATEX((WAVEFORMATEX*)pActualFormat); + pDevice->capture.internalChannels = pActualFormat->Format.nChannels; + pDevice->capture.internalSampleRate = 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->capture.internalChannels, pDevice->capture.internalChannelMap); + } else { + ma_channel_mask_to_channel_map__win32(wf.dwChannelMask, pDevice->capture.internalChannels, pDevice->capture.internalChannelMap); + } + + /* + 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 (bufferSizeInFrames != (descDS.dwBufferBytes / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels))) { + descDS.dwBufferBytes = bufferSizeInFrames * ma_get_bytes_per_frame(pDevice->capture.internalFormat, wf.Format.nChannels); + ma_IDirectSoundCaptureBuffer_Release((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer); + + if (FAILED(ma_IDirectSoundCapture_CreateCaptureBuffer((ma_IDirectSoundCapture*)pDevice->dsound.pCapture, &descDS, (ma_IDirectSoundCaptureBuffer**)&pDevice->dsound.pCaptureBuffer, NULL))) { + ma_device_uninit__dsound(pDevice); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Second attempt at IDirectSoundCapture_CreateCaptureBuffer() failed for capture device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + } + + /* DirectSound should give us a buffer exactly the size we asked for. */ + pDevice->capture.internalBufferSizeInFrames = bufferSizeInFrames; + pDevice->capture.internalPeriods = pConfig->periods; + } + + if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + WAVEFORMATEXTENSIBLE wf; + MA_DSBUFFERDESC descDSPrimary; + MA_DSCAPS caps; + char rawdata[1024]; /* <-- Ugly hack to avoid a malloc() due to a crappy DirectSound API. */ + WAVEFORMATEXTENSIBLE* pActualFormat; + ma_uint32 bufferSizeInFrames; + MA_DSBUFFERDESC descDS; + + result = ma_config_to_WAVEFORMATEXTENSIBLE(pConfig->playback.format, pConfig->playback.channels, pConfig->sampleRate, pConfig->playback.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); + if (result != MA_SUCCESS) { + ma_device_uninit__dsound(pDevice); + return result; + } + + ma_zero_object(&descDSPrimary); + descDSPrimary.dwSize = sizeof(MA_DSBUFFERDESC); + descDSPrimary.dwFlags = MA_DSBCAPS_PRIMARYBUFFER | MA_DSBCAPS_CTRLVOLUME; + if (FAILED(ma_IDirectSound_CreateSoundBuffer((ma_IDirectSound*)pDevice->dsound.pPlayback, &descDSPrimary, (ma_IDirectSoundBuffer**)&pDevice->dsound.pPlaybackPrimaryBuffer, NULL))) { + ma_device_uninit__dsound(pDevice); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_CreateSoundBuffer() failed for playback device's primary buffer.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + + /* We may want to make some adjustments to the format if we are using defaults. */ + ma_zero_object(&caps); + caps.dwSize = sizeof(caps); + if (FAILED(ma_IDirectSound_GetCaps((ma_IDirectSound*)pDevice->dsound.pPlayback, &caps))) { + ma_device_uninit__dsound(pDevice); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_GetCaps() failed for playback device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (pDevice->playback.usingDefaultChannels) { + if ((caps.dwFlags & MA_DSCAPS_PRIMARYSTEREO) != 0) { + DWORD speakerConfig; + + /* It supports at least stereo, but could support more. */ + wf.Format.nChannels = 2; + + /* Look at the speaker configuration to get a better idea on the channel count. */ + if (SUCCEEDED(ma_IDirectSound_GetSpeakerConfig((ma_IDirectSound*)pDevice->dsound.pPlayback, &speakerConfig))) { + ma_get_channels_from_speaker_config__dsound(speakerConfig, &wf.Format.nChannels, &wf.dwChannelMask); + } + } else { + /* It does not support stereo, which means we are stuck with mono. */ + wf.Format.nChannels = 1; + } + } + + if (pDevice->usingDefaultSampleRate) { + /* 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); + } else { + wf.Format.nSamplesPerSec = caps.dwMaxSecondarySampleRate; + } + } + + wf.Format.nBlockAlign = (wf.Format.nChannels * wf.Format.wBitsPerSample) / 8; + wf.Format.nAvgBytesPerSec = wf.Format.nBlockAlign * wf.Format.nSamplesPerSec; + + /* + From MSDN: + + The method succeeds even if the hardware does not support the requested format; DirectSound sets the buffer to the closest + supported format. To determine whether this has happened, an application can call the GetFormat method for the primary buffer + and compare the result with the format that was requested with the SetFormat method. + */ + if (FAILED(ma_IDirectSoundBuffer_SetFormat((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackPrimaryBuffer, (WAVEFORMATEX*)&wf))) { + ma_device_uninit__dsound(pDevice); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to set format of playback device's primary buffer.", MA_FORMAT_NOT_SUPPORTED); + } + + /* Get the _actual_ properties of the buffer. */ + pActualFormat = (WAVEFORMATEXTENSIBLE*)rawdata; + if (FAILED(ma_IDirectSoundBuffer_GetFormat((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackPrimaryBuffer, (WAVEFORMATEX*)pActualFormat, sizeof(rawdata), NULL))) { + ma_device_uninit__dsound(pDevice); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to retrieve the actual format of the playback device's primary buffer.", MA_FORMAT_NOT_SUPPORTED); + } + + pDevice->playback.internalFormat = ma_format_from_WAVEFORMATEX((WAVEFORMATEX*)pActualFormat); + pDevice->playback.internalChannels = pActualFormat->Format.nChannels; + pDevice->playback.internalSampleRate = 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); + } else { + ma_channel_mask_to_channel_map__win32(wf.dwChannelMask, pDevice->playback.internalChannels, pDevice->playback.internalChannelMap); + } + + /* The size of the buffer must be a clean multiple of the period count. */ + bufferSizeInFrames = (ma_calculate_buffer_size_in_frames_from_milliseconds(bufferSizeInMilliseconds, pDevice->playback.internalSampleRate) / pConfig->periods) * pConfig->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 + application can get a more accurate play cursor. + */ + ma_zero_object(&descDS); + descDS.dwSize = sizeof(descDS); + descDS.dwFlags = MA_DSBCAPS_CTRLPOSITIONNOTIFY | MA_DSBCAPS_GLOBALFOCUS | MA_DSBCAPS_GETCURRENTPOSITION2; + descDS.dwBufferBytes = bufferSizeInFrames * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + descDS.lpwfxFormat = (WAVEFORMATEX*)&wf; + if (FAILED(ma_IDirectSound_CreateSoundBuffer((ma_IDirectSound*)pDevice->dsound.pPlayback, &descDS, (ma_IDirectSoundBuffer**)&pDevice->dsound.pPlaybackBuffer, NULL))) { + ma_device_uninit__dsound(pDevice); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_CreateSoundBuffer() failed for playback device's secondary buffer.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + /* DirectSound should give us a buffer exactly the size we asked for. */ + pDevice->playback.internalBufferSizeInFrames = bufferSizeInFrames; + pDevice->playback.internalPeriods = pConfig->periods; + } + + (void)pContext; + return MA_SUCCESS; +} + + +ma_result ma_device_main_loop__dsound(ma_device* pDevice) +{ + ma_result result = MA_SUCCESS; + ma_uint32 bpfCapture = ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + ma_uint32 bpfPlayback = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + HRESULT hr; + DWORD lockOffsetInBytesCapture; + DWORD lockSizeInBytesCapture; + DWORD mappedSizeInBytesCapture; + void* pMappedBufferCapture; + DWORD lockOffsetInBytesPlayback; + DWORD lockSizeInBytesPlayback; + DWORD mappedSizeInBytesPlayback; + void* pMappedBufferPlayback; + DWORD prevReadCursorInBytesCapture = 0; + DWORD prevPlayCursorInBytesPlayback = 0; + ma_bool32 physicalPlayCursorLoopFlagPlayback = 0; + DWORD virtualWriteCursorInBytesPlayback = 0; + ma_bool32 virtualWriteCursorLoopFlagPlayback = 0; + ma_bool32 isPlaybackDeviceStarted = MA_FALSE; + ma_uint32 framesWrittenToPlaybackDevice = 0; /* For knowing whether or not the playback device needs to be started. */ + ma_uint32 waitTimeInMilliseconds = 1; + + ma_assert(pDevice != NULL); + + /* The first thing to do is start the capture device. The playback device is only started after the first period is written. */ + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + if (FAILED(ma_IDirectSoundCaptureBuffer_Start((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, MA_DSCBSTART_LOOPING))) { + 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) { + switch (pDevice->type) + { + case ma_device_type_duplex: + { + DWORD physicalCaptureCursorInBytes; + DWORD physicalReadCursorInBytes; + if (FAILED(ma_IDirectSoundCaptureBuffer_GetCurrentPosition((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, &physicalCaptureCursorInBytes, &physicalReadCursorInBytes))) { + return MA_ERROR; + } + + /* If nothing is available we just sleep for a bit and return from this iteration. */ + if (physicalReadCursorInBytes == prevReadCursorInBytesCapture) { + ma_sleep(waitTimeInMilliseconds); + continue; /* Nothing is available in the capture buffer. */ + } + + /* + The current position has moved. We need to map all of the captured samples and write them to the playback device, making sure + we don't return until every frame has been copied over. + */ + if (prevReadCursorInBytesCapture < physicalReadCursorInBytes) { + /* The capture position has not looped. This is the simple case. */ + lockOffsetInBytesCapture = prevReadCursorInBytesCapture; + lockSizeInBytesCapture = (physicalReadCursorInBytes - prevReadCursorInBytesCapture); + } else { + /* + The capture position has looped. This is the more complex case. Map to the end of the buffer. If this does not return anything, + do it again from the start. + */ + if (prevReadCursorInBytesCapture < pDevice->capture.internalBufferSizeInFrames*bpfCapture) { + /* Lock up to the end of the buffer. */ + lockOffsetInBytesCapture = prevReadCursorInBytesCapture; + lockSizeInBytesCapture = (pDevice->capture.internalBufferSizeInFrames*bpfCapture) - prevReadCursorInBytesCapture; + } else { + /* Lock starting from the start of the buffer. */ + lockOffsetInBytesCapture = 0; + lockSizeInBytesCapture = physicalReadCursorInBytes; + } + } + + if (lockSizeInBytesCapture == 0) { + ma_sleep(waitTimeInMilliseconds); + continue; /* Nothing is available in the capture buffer. */ + } + + hr = ma_IDirectSoundCaptureBuffer_Lock((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, lockOffsetInBytesCapture, lockSizeInBytesCapture, &pMappedBufferCapture, &mappedSizeInBytesCapture, NULL, NULL, 0); + if (FAILED(hr)) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from capture device in preparation for writing to the device.", MA_FAILED_TO_MAP_DEVICE_BUFFER); + } + + + /* At this point we have some input data that we need to output. We do not return until every mapped frame of the input data is written to the playback device. */ + pDevice->capture._dspFrameCount = mappedSizeInBytesCapture / bpfCapture; + pDevice->capture._dspFrames = (const ma_uint8*)pMappedBufferCapture; + for (;;) { /* Keep writing to the playback device. */ + ma_uint8 inputFramesInExternalFormat[4096]; + ma_uint32 inputFramesInExternalFormatCap = sizeof(inputFramesInExternalFormat) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); + ma_uint32 inputFramesInExternalFormatCount; + ma_uint8 outputFramesInExternalFormat[4096]; + ma_uint32 outputFramesInExternalFormatCap = sizeof(outputFramesInExternalFormat) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); + + inputFramesInExternalFormatCount = (ma_uint32)ma_pcm_converter_read(&pDevice->capture.converter, inputFramesInExternalFormat, ma_min(inputFramesInExternalFormatCap, outputFramesInExternalFormatCap)); + if (inputFramesInExternalFormatCount == 0) { + break; /* No more input data. */ + } + + pDevice->onData(pDevice, outputFramesInExternalFormat, inputFramesInExternalFormat, inputFramesInExternalFormatCount); + + /* At this point we have input and output data in external format. All we need to do now is convert it to the output format. This may take a few passes. */ + pDevice->playback._dspFrameCount = inputFramesInExternalFormatCount; + pDevice->playback._dspFrames = (const ma_uint8*)outputFramesInExternalFormat; + for (;;) { + ma_uint32 framesWrittenThisIteration; + DWORD physicalPlayCursorInBytes; + DWORD physicalWriteCursorInBytes; + DWORD availableBytesPlayback; + DWORD silentPaddingInBytes = 0; /* <-- Must be initialized to 0. */ + + /* We need the physical play and write cursors. */ + if (FAILED(ma_IDirectSoundBuffer_GetCurrentPosition((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, &physicalPlayCursorInBytes, &physicalWriteCursorInBytes))) { + break; + } + + if (physicalPlayCursorInBytes < prevPlayCursorInBytesPlayback) { + physicalPlayCursorLoopFlagPlayback = !physicalPlayCursorLoopFlagPlayback; + } + prevPlayCursorInBytesPlayback = physicalPlayCursorInBytes; + + /* If there's any bytes available for writing we can do that now. The space between the virtual cursor position and play cursor. */ + if (physicalPlayCursorLoopFlagPlayback == virtualWriteCursorLoopFlagPlayback) { + /* Same loop iteration. The available bytes wraps all the way around from the virtual write cursor to the physical play cursor. */ + if (physicalPlayCursorInBytes <= virtualWriteCursorInBytesPlayback) { + availableBytesPlayback = (pDevice->playback.internalBufferSizeInFrames*bpfPlayback) - virtualWriteCursorInBytesPlayback; + availableBytesPlayback += physicalPlayCursorInBytes; /* Wrap around. */ + } else { + /* This is an error. */ + #ifdef MA_DEBUG_OUTPUT + printf("[DirectSound] (Duplex/Playback) WARNING: Play cursor has moved in front of the write cursor (same loop iterations). physicalPlayCursorInBytes=%d, virtualWriteCursorInBytes=%d.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); + #endif + availableBytesPlayback = 0; + } + } else { + /* Different loop iterations. The available bytes only goes from the virtual write cursor to the physical play cursor. */ + if (physicalPlayCursorInBytes >= virtualWriteCursorInBytesPlayback) { + availableBytesPlayback = physicalPlayCursorInBytes - virtualWriteCursorInBytesPlayback; + } else { + /* This is an error. */ + #ifdef MA_DEBUG_OUTPUT + printf("[DirectSound] (Duplex/Playback) WARNING: Write cursor has moved behind the play cursor (different loop iterations). physicalPlayCursorInBytes=%d, virtualWriteCursorInBytes=%d.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); + #endif + availableBytesPlayback = 0; + } + } + + #ifdef MA_DEBUG_OUTPUT + /*printf("[DirectSound] (Duplex/Playback) physicalPlayCursorInBytes=%d, availableBytesPlayback=%d\n", physicalPlayCursorInBytes, availableBytesPlayback);*/ + #endif + + /* If there's no room available for writing we need to wait for more. */ + if (availableBytesPlayback == 0) { + /* If we haven't started the device yet, this will never get beyond 0. In this case we need to get the device started. */ + if (!isPlaybackDeviceStarted) { + if (FAILED(ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING))) { + ma_IDirectSoundCaptureBuffer_Stop((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed.", MA_FAILED_TO_START_BACKEND_DEVICE); + } + isPlaybackDeviceStarted = MA_TRUE; + } else { + ma_sleep(waitTimeInMilliseconds); + continue; + } + } + + + /* Getting here means there room available somewhere. We limit this to either the end of the buffer or the physical play cursor, whichever is closest. */ + lockOffsetInBytesPlayback = virtualWriteCursorInBytesPlayback; + if (physicalPlayCursorLoopFlagPlayback == virtualWriteCursorLoopFlagPlayback) { + /* Same loop iteration. Go up to the end of the buffer. */ + lockSizeInBytesPlayback = (pDevice->playback.internalBufferSizeInFrames*bpfPlayback) - virtualWriteCursorInBytesPlayback; + } else { + /* Different loop iterations. Go up to the physical play cursor. */ + lockSizeInBytesPlayback = physicalPlayCursorInBytes - virtualWriteCursorInBytesPlayback; + } + + hr = ma_IDirectSoundBuffer_Lock((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, lockOffsetInBytesPlayback, lockSizeInBytesPlayback, &pMappedBufferPlayback, &mappedSizeInBytesPlayback, NULL, NULL, 0); + if (FAILED(hr)) { + result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from playback device in preparation for writing to the device.", MA_FAILED_TO_MAP_DEVICE_BUFFER); + break; + } + + /* + Experiment: If the playback buffer is being starved, pad it with some silence to get it back in sync. This will cause a glitch, but it may prevent + endless glitching due to it constantly running out of data. + */ + if (isPlaybackDeviceStarted) { + DWORD bytesQueuedForPlayback = (pDevice->playback.internalBufferSizeInFrames*bpfPlayback) - availableBytesPlayback; + if (bytesQueuedForPlayback < ((pDevice->playback.internalBufferSizeInFrames/pDevice->playback.internalPeriods)*bpfPlayback)) { + silentPaddingInBytes = ((pDevice->playback.internalBufferSizeInFrames/pDevice->playback.internalPeriods)*2*bpfPlayback) - bytesQueuedForPlayback; + if (silentPaddingInBytes > lockSizeInBytesPlayback) { + silentPaddingInBytes = lockSizeInBytesPlayback; + } + + #ifdef MA_DEBUG_OUTPUT + printf("[DirectSound] (Duplex/Playback) Playback buffer starved. availableBytesPlayback=%d, silentPaddingInBytes=%d\n", availableBytesPlayback, silentPaddingInBytes); + #endif + } + } + + /* At this point we have a buffer for output. */ + if (silentPaddingInBytes > 0) { + ma_zero_memory(pMappedBufferPlayback, silentPaddingInBytes); + framesWrittenThisIteration = silentPaddingInBytes/bpfPlayback; + } else { + framesWrittenThisIteration = (ma_uint32)ma_pcm_converter_read(&pDevice->playback.converter, pMappedBufferPlayback, mappedSizeInBytesPlayback/bpfPlayback); + } + + + hr = ma_IDirectSoundBuffer_Unlock((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, pMappedBufferPlayback, framesWrittenThisIteration*bpfPlayback, NULL, 0); + if (FAILED(hr)) { + result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from playback device after writing to the device.", MA_FAILED_TO_UNMAP_DEVICE_BUFFER); + break; + } + + virtualWriteCursorInBytesPlayback += framesWrittenThisIteration*bpfPlayback; + if ((virtualWriteCursorInBytesPlayback/bpfPlayback) == pDevice->playback.internalBufferSizeInFrames) { + 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. + */ + framesWrittenToPlaybackDevice += framesWrittenThisIteration; + if (!isPlaybackDeviceStarted && framesWrittenToPlaybackDevice >= ((pDevice->playback.internalBufferSizeInFrames/pDevice->playback.internalPeriods)*2)) { + if (FAILED(ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING))) { + ma_IDirectSoundCaptureBuffer_Stop((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed.", MA_FAILED_TO_START_BACKEND_DEVICE); + } + isPlaybackDeviceStarted = MA_TRUE; + } + + if (framesWrittenThisIteration < mappedSizeInBytesPlayback/bpfPlayback) { + break; /* We're finished with the output data.*/ + } + } + + if (inputFramesInExternalFormatCount < inputFramesInExternalFormatCap) { + break; /* We just consumed every input sample. */ + } + } + + + /* At this point we're done with the mapped portion of the capture buffer. */ + hr = ma_IDirectSoundCaptureBuffer_Unlock((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, pMappedBufferCapture, mappedSizeInBytesCapture, NULL, 0); + if (FAILED(hr)) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from capture device after reading from the device.", MA_FAILED_TO_UNMAP_DEVICE_BUFFER); + } + prevReadCursorInBytesCapture = (lockOffsetInBytesCapture + mappedSizeInBytesCapture); + } break; + + + + case ma_device_type_capture: + { + DWORD physicalCaptureCursorInBytes; + DWORD physicalReadCursorInBytes; + if (FAILED(ma_IDirectSoundCaptureBuffer_GetCurrentPosition((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, &physicalCaptureCursorInBytes, &physicalReadCursorInBytes))) { + return MA_ERROR; + } + + /* If the previous capture position is the same as the current position we need to wait a bit longer. */ + if (prevReadCursorInBytesCapture == physicalReadCursorInBytes) { + ma_sleep(waitTimeInMilliseconds); + continue; + } + + /* Getting here means we have capture data available. */ + if (prevReadCursorInBytesCapture < physicalReadCursorInBytes) { + /* The capture position has not looped. This is the simple case. */ + lockOffsetInBytesCapture = prevReadCursorInBytesCapture; + lockSizeInBytesCapture = (physicalReadCursorInBytes - prevReadCursorInBytesCapture); + } else { + /* + The capture position has looped. This is the more complex case. Map to the end of the buffer. If this does not return anything, + do it again from the start. + */ + if (prevReadCursorInBytesCapture < pDevice->capture.internalBufferSizeInFrames*bpfCapture) { + /* Lock up to the end of the buffer. */ + lockOffsetInBytesCapture = prevReadCursorInBytesCapture; + lockSizeInBytesCapture = (pDevice->capture.internalBufferSizeInFrames*bpfCapture) - prevReadCursorInBytesCapture; + } else { + /* Lock starting from the start of the buffer. */ + lockOffsetInBytesCapture = 0; + lockSizeInBytesCapture = physicalReadCursorInBytes; + } + } + + #ifdef MA_DEBUG_OUTPUT + /*printf("[DirectSound] (Capture) physicalCaptureCursorInBytes=%d, physicalReadCursorInBytes=%d\n", physicalCaptureCursorInBytes, physicalReadCursorInBytes);*/ + /*printf("[DirectSound] (Capture) lockOffsetInBytesCapture=%d, lockSizeInBytesCapture=%d\n", lockOffsetInBytesCapture, lockSizeInBytesCapture);*/ + #endif + + if (lockSizeInBytesCapture < (pDevice->capture.internalBufferSizeInFrames/pDevice->capture.internalPeriods)) { + ma_sleep(waitTimeInMilliseconds); + continue; /* Nothing is available in the capture buffer. */ + } + + hr = ma_IDirectSoundCaptureBuffer_Lock((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, lockOffsetInBytesCapture, lockSizeInBytesCapture, &pMappedBufferCapture, &mappedSizeInBytesCapture, NULL, NULL, 0); + if (FAILED(hr)) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from capture device in preparation for writing to the device.", MA_FAILED_TO_MAP_DEVICE_BUFFER); + } + + #ifdef MA_DEBUG_OUTPUT + if (lockSizeInBytesCapture != mappedSizeInBytesCapture) { + printf("[DirectSound] (Capture) lockSizeInBytesCapture=%d != mappedSizeInBytesCapture=%d\n", lockSizeInBytesCapture, mappedSizeInBytesCapture); + } + #endif + + ma_device__send_frames_to_client(pDevice, mappedSizeInBytesCapture/bpfCapture, pMappedBufferCapture); + + hr = ma_IDirectSoundCaptureBuffer_Unlock((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, pMappedBufferCapture, mappedSizeInBytesCapture, NULL, 0); + if (FAILED(hr)) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from capture device after reading from the device.", MA_FAILED_TO_UNMAP_DEVICE_BUFFER); + } + prevReadCursorInBytesCapture = lockOffsetInBytesCapture + mappedSizeInBytesCapture; + + if (prevReadCursorInBytesCapture == (pDevice->capture.internalBufferSizeInFrames*bpfCapture)) { + prevReadCursorInBytesCapture = 0; + } + } break; + + + + case ma_device_type_playback: + { + DWORD availableBytesPlayback; + DWORD physicalPlayCursorInBytes; + DWORD physicalWriteCursorInBytes; + if (FAILED(ma_IDirectSoundBuffer_GetCurrentPosition((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, &physicalPlayCursorInBytes, &physicalWriteCursorInBytes))) { + break; + } + + if (physicalPlayCursorInBytes < prevPlayCursorInBytesPlayback) { + physicalPlayCursorLoopFlagPlayback = !physicalPlayCursorLoopFlagPlayback; + } + prevPlayCursorInBytesPlayback = physicalPlayCursorInBytes; + + /* If there's any bytes available for writing we can do that now. The space between the virtual cursor position and play cursor. */ + if (physicalPlayCursorLoopFlagPlayback == virtualWriteCursorLoopFlagPlayback) { + /* Same loop iteration. The available bytes wraps all the way around from the virtual write cursor to the physical play cursor. */ + if (physicalPlayCursorInBytes <= virtualWriteCursorInBytesPlayback) { + availableBytesPlayback = (pDevice->playback.internalBufferSizeInFrames*bpfPlayback) - virtualWriteCursorInBytesPlayback; + availableBytesPlayback += physicalPlayCursorInBytes; /* Wrap around. */ + } else { + /* This is an error. */ + #ifdef MA_DEBUG_OUTPUT + printf("[DirectSound] (Playback) WARNING: Play cursor has moved in front of the write cursor (same loop iterations). physicalPlayCursorInBytes=%d, virtualWriteCursorInBytes=%d.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); + #endif + availableBytesPlayback = 0; + } + } else { + /* Different loop iterations. The available bytes only goes from the virtual write cursor to the physical play cursor. */ + if (physicalPlayCursorInBytes >= virtualWriteCursorInBytesPlayback) { + availableBytesPlayback = physicalPlayCursorInBytes - virtualWriteCursorInBytesPlayback; + } else { + /* This is an error. */ + #ifdef MA_DEBUG_OUTPUT + printf("[DirectSound] (Playback) WARNING: Write cursor has moved behind the play cursor (different loop iterations). physicalPlayCursorInBytes=%d, virtualWriteCursorInBytes=%d.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); + #endif + availableBytesPlayback = 0; + } + } + + #ifdef MA_DEBUG_OUTPUT + /*printf("[DirectSound] (Playback) physicalPlayCursorInBytes=%d, availableBytesPlayback=%d\n", physicalPlayCursorInBytes, availableBytesPlayback);*/ + #endif + + /* If there's no room available for writing we need to wait for more. */ + if (availableBytesPlayback < (pDevice->playback.internalBufferSizeInFrames/pDevice->playback.internalPeriods)) { + /* If we haven't started the device yet, this will never get beyond 0. In this case we need to get the device started. */ + if (availableBytesPlayback == 0 && !isPlaybackDeviceStarted) { + if (FAILED(ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING))) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed.", MA_FAILED_TO_START_BACKEND_DEVICE); + } + isPlaybackDeviceStarted = MA_TRUE; + } else { + ma_sleep(waitTimeInMilliseconds); + continue; + } + } + + /* Getting here means there room available somewhere. We limit this to either the end of the buffer or the physical play cursor, whichever is closest. */ + lockOffsetInBytesPlayback = virtualWriteCursorInBytesPlayback; + if (physicalPlayCursorLoopFlagPlayback == virtualWriteCursorLoopFlagPlayback) { + /* Same loop iteration. Go up to the end of the buffer. */ + lockSizeInBytesPlayback = (pDevice->playback.internalBufferSizeInFrames*bpfPlayback) - virtualWriteCursorInBytesPlayback; + } else { + /* Different loop iterations. Go up to the physical play cursor. */ + lockSizeInBytesPlayback = physicalPlayCursorInBytes - virtualWriteCursorInBytesPlayback; + } + + hr = ma_IDirectSoundBuffer_Lock((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, lockOffsetInBytesPlayback, lockSizeInBytesPlayback, &pMappedBufferPlayback, &mappedSizeInBytesPlayback, NULL, NULL, 0); + if (FAILED(hr)) { + result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from playback device in preparation for writing to the device.", MA_FAILED_TO_MAP_DEVICE_BUFFER); + break; + } + + /* At this point we have a buffer for output. */ + ma_device__read_frames_from_client(pDevice, (mappedSizeInBytesPlayback/bpfPlayback), pMappedBufferPlayback); + + hr = ma_IDirectSoundBuffer_Unlock((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, pMappedBufferPlayback, mappedSizeInBytesPlayback, NULL, 0); + if (FAILED(hr)) { + result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from playback device after writing to the device.", MA_FAILED_TO_UNMAP_DEVICE_BUFFER); + break; + } + + virtualWriteCursorInBytesPlayback += mappedSizeInBytesPlayback; + if (virtualWriteCursorInBytesPlayback == pDevice->playback.internalBufferSizeInFrames*bpfPlayback) { + 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. + */ + framesWrittenToPlaybackDevice += mappedSizeInBytesPlayback/bpfPlayback; + if (!isPlaybackDeviceStarted && framesWrittenToPlaybackDevice >= (pDevice->playback.internalBufferSizeInFrames/pDevice->playback.internalPeriods)) { + if (FAILED(ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING))) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed.", MA_FAILED_TO_START_BACKEND_DEVICE); + } + isPlaybackDeviceStarted = MA_TRUE; + } + } break; + + + default: return MA_INVALID_ARGS; /* Invalid device type. */ + } + + if (result != MA_SUCCESS) { + return result; + } + } + + /* Getting here means the device is being stopped. */ + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + if (FAILED(ma_IDirectSoundCaptureBuffer_Stop((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer))) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCaptureBuffer_Stop() failed.", MA_FAILED_TO_STOP_BACKEND_DEVICE); + } + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + /* The playback device should be drained before stopping. All we do is wait until the available bytes is equal to the size of the buffer. */ + if (isPlaybackDeviceStarted) { + for (;;) { + DWORD availableBytesPlayback = 0; + DWORD physicalPlayCursorInBytes; + DWORD physicalWriteCursorInBytes; + if (FAILED(ma_IDirectSoundBuffer_GetCurrentPosition((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, &physicalPlayCursorInBytes, &physicalWriteCursorInBytes))) { + break; + } + + if (physicalPlayCursorInBytes < prevPlayCursorInBytesPlayback) { + physicalPlayCursorLoopFlagPlayback = !physicalPlayCursorLoopFlagPlayback; + } + prevPlayCursorInBytesPlayback = physicalPlayCursorInBytes; + + if (physicalPlayCursorLoopFlagPlayback == virtualWriteCursorLoopFlagPlayback) { + /* Same loop iteration. The available bytes wraps all the way around from the virtual write cursor to the physical play cursor. */ + if (physicalPlayCursorInBytes <= virtualWriteCursorInBytesPlayback) { + availableBytesPlayback = (pDevice->playback.internalBufferSizeInFrames*bpfPlayback) - virtualWriteCursorInBytesPlayback; + availableBytesPlayback += physicalPlayCursorInBytes; /* Wrap around. */ + } else { + break; + } + } else { + /* Different loop iterations. The available bytes only goes from the virtual write cursor to the physical play cursor. */ + if (physicalPlayCursorInBytes >= virtualWriteCursorInBytesPlayback) { + availableBytesPlayback = physicalPlayCursorInBytes - virtualWriteCursorInBytesPlayback; + } else { + break; + } + } + + if (availableBytesPlayback >= (pDevice->playback.internalBufferSizeInFrames*bpfPlayback)) { + break; + } + + ma_sleep(waitTimeInMilliseconds); + } + } + + if (FAILED(ma_IDirectSoundBuffer_Stop((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer))) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Stop() failed.", MA_FAILED_TO_STOP_BACKEND_DEVICE); + } + + ma_IDirectSoundBuffer_SetCurrentPosition((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0); + } + + return MA_SUCCESS; +} + +ma_result ma_context_uninit__dsound(ma_context* pContext) +{ + ma_assert(pContext != NULL); + ma_assert(pContext->backend == ma_backend_dsound); + + ma_dlclose(pContext, pContext->dsound.hDSoundDLL); + + return MA_SUCCESS; +} + +ma_result ma_context_init__dsound(const ma_context_config* pConfig, ma_context* pContext) +{ + ma_assert(pContext != NULL); + + (void)pConfig; + + pContext->dsound.hDSoundDLL = ma_dlopen(pContext, "dsound.dll"); + if (pContext->dsound.hDSoundDLL == NULL) { + return MA_API_NOT_FOUND; + } + + pContext->dsound.DirectSoundCreate = ma_dlsym(pContext, pContext->dsound.hDSoundDLL, "DirectSoundCreate"); + pContext->dsound.DirectSoundEnumerateA = ma_dlsym(pContext, pContext->dsound.hDSoundDLL, "DirectSoundEnumerateA"); + 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->onDeviceWrite = NULL; + pContext->onDeviceRead = NULL; + pContext->onDeviceMainLoop = ma_device_main_loop__dsound; + + return MA_SUCCESS; +} +#endif + + + +/****************************************************************************** + +WinMM Backend + +******************************************************************************/ +#ifdef MA_HAS_WINMM + +/* +Some older compilers don't have WAVEOUTCAPS2A and WAVEINCAPS2A, so we'll need to write this ourselves. These structures +are exactly the same as the older ones but they have a few GUIDs for manufacturer/product/name identification. I'm keeping +the names the same as the Win32 library for consistency, but namespaced to avoid naming conflicts with the Win32 version. +*/ +typedef struct +{ + WORD wMid; + WORD wPid; + MMVERSION vDriverVersion; + CHAR szPname[MAXPNAMELEN]; + DWORD dwFormats; + WORD wChannels; + WORD wReserved1; + DWORD dwSupport; + GUID ManufacturerGuid; + GUID ProductGuid; + GUID NameGuid; +} MA_WAVEOUTCAPS2A; +typedef struct +{ + WORD wMid; + WORD wPid; + MMVERSION vDriverVersion; + CHAR szPname[MAXPNAMELEN]; + DWORD dwFormats; + WORD wChannels; + WORD wReserved1; + GUID ManufacturerGuid; + GUID ProductGuid; + GUID NameGuid; +} MA_WAVEINCAPS2A; + +typedef UINT (WINAPI * MA_PFN_waveOutGetNumDevs)(void); +typedef MMRESULT (WINAPI * MA_PFN_waveOutGetDevCapsA)(ma_uintptr uDeviceID, LPWAVEOUTCAPSA pwoc, UINT cbwoc); +typedef MMRESULT (WINAPI * MA_PFN_waveOutOpen)(LPHWAVEOUT phwo, UINT uDeviceID, LPCWAVEFORMATEX pwfx, DWORD_PTR dwCallback, DWORD_PTR dwInstance, DWORD fdwOpen); +typedef MMRESULT (WINAPI * MA_PFN_waveOutClose)(HWAVEOUT hwo); +typedef MMRESULT (WINAPI * MA_PFN_waveOutPrepareHeader)(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh); +typedef MMRESULT (WINAPI * MA_PFN_waveOutUnprepareHeader)(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh); +typedef MMRESULT (WINAPI * MA_PFN_waveOutWrite)(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh); +typedef MMRESULT (WINAPI * MA_PFN_waveOutReset)(HWAVEOUT hwo); +typedef UINT (WINAPI * MA_PFN_waveInGetNumDevs)(void); +typedef MMRESULT (WINAPI * MA_PFN_waveInGetDevCapsA)(ma_uintptr uDeviceID, LPWAVEINCAPSA pwic, UINT cbwic); +typedef MMRESULT (WINAPI * MA_PFN_waveInOpen)(LPHWAVEIN phwi, UINT uDeviceID, LPCWAVEFORMATEX pwfx, DWORD_PTR dwCallback, DWORD_PTR dwInstance, DWORD fdwOpen); +typedef MMRESULT (WINAPI * MA_PFN_waveInClose)(HWAVEIN hwi); +typedef MMRESULT (WINAPI * MA_PFN_waveInPrepareHeader)(HWAVEIN hwi, LPWAVEHDR pwh, UINT cbwh); +typedef MMRESULT (WINAPI * MA_PFN_waveInUnprepareHeader)(HWAVEIN hwi, LPWAVEHDR pwh, UINT cbwh); +typedef MMRESULT (WINAPI * MA_PFN_waveInAddBuffer)(HWAVEIN hwi, LPWAVEHDR pwh, UINT cbwh); +typedef MMRESULT (WINAPI * MA_PFN_waveInStart)(HWAVEIN hwi); +typedef MMRESULT (WINAPI * MA_PFN_waveInReset)(HWAVEIN hwi); + +ma_result ma_result_from_MMRESULT(MMRESULT resultMM) +{ + switch (resultMM) { + case MMSYSERR_NOERROR: return MA_SUCCESS; + case MMSYSERR_BADDEVICEID: return MA_INVALID_ARGS; + case MMSYSERR_INVALHANDLE: return MA_INVALID_ARGS; + case MMSYSERR_NOMEM: return MA_OUT_OF_MEMORY; + case MMSYSERR_INVALFLAG: return MA_INVALID_ARGS; + case MMSYSERR_INVALPARAM: return MA_INVALID_ARGS; + case MMSYSERR_HANDLEBUSY: return MA_DEVICE_BUSY; + case MMSYSERR_ERROR: return MA_ERROR; + default: return MA_ERROR; + } +} + +char* ma_find_last_character(char* str, char ch) +{ + char* last; + + if (str == NULL) { + return NULL; + } + + last = NULL; + while (*str != '\0') { + if (*str == ch) { + last = str; + } + + str += 1; + } + + return last; +} + + +/* +Our own "WAVECAPS" structure that contains generic information shared between WAVEOUTCAPS2 and WAVEINCAPS2 so +we can do things generically and typesafely. Names are being kept the same for consistency. +*/ +typedef struct +{ + CHAR szPname[MAXPNAMELEN]; + DWORD dwFormats; + WORD wChannels; + GUID NameGuid; +} MA_WAVECAPSA; + +ma_result ma_get_best_info_from_formats_flags__winmm(DWORD dwFormats, WORD channels, WORD* pBitsPerSample, DWORD* pSampleRate) +{ + WORD bitsPerSample = 0; + DWORD sampleRate = 0; + + if (pBitsPerSample) { + *pBitsPerSample = 0; + } + if (pSampleRate) { + *pSampleRate = 0; + } + + if (channels == 1) { + bitsPerSample = 16; + if ((dwFormats & WAVE_FORMAT_48M16) != 0) { + sampleRate = 48000; + } else if ((dwFormats & WAVE_FORMAT_44M16) != 0) { + sampleRate = 44100; + } else if ((dwFormats & WAVE_FORMAT_2M16) != 0) { + sampleRate = 22050; + } else if ((dwFormats & WAVE_FORMAT_1M16) != 0) { + sampleRate = 11025; + } else if ((dwFormats & WAVE_FORMAT_96M16) != 0) { + sampleRate = 96000; + } else { + bitsPerSample = 8; + if ((dwFormats & WAVE_FORMAT_48M08) != 0) { + sampleRate = 48000; + } else if ((dwFormats & WAVE_FORMAT_44M08) != 0) { + sampleRate = 44100; + } else if ((dwFormats & WAVE_FORMAT_2M08) != 0) { + sampleRate = 22050; + } else if ((dwFormats & WAVE_FORMAT_1M08) != 0) { + sampleRate = 11025; + } else if ((dwFormats & WAVE_FORMAT_96M08) != 0) { + sampleRate = 96000; + } else { + return MA_FORMAT_NOT_SUPPORTED; + } + } + } else { + bitsPerSample = 16; + if ((dwFormats & WAVE_FORMAT_48S16) != 0) { + sampleRate = 48000; + } else if ((dwFormats & WAVE_FORMAT_44S16) != 0) { + sampleRate = 44100; + } else if ((dwFormats & WAVE_FORMAT_2S16) != 0) { + sampleRate = 22050; + } else if ((dwFormats & WAVE_FORMAT_1S16) != 0) { + sampleRate = 11025; + } else if ((dwFormats & WAVE_FORMAT_96S16) != 0) { + sampleRate = 96000; + } else { + bitsPerSample = 8; + if ((dwFormats & WAVE_FORMAT_48S08) != 0) { + sampleRate = 48000; + } else if ((dwFormats & WAVE_FORMAT_44S08) != 0) { + sampleRate = 44100; + } else if ((dwFormats & WAVE_FORMAT_2S08) != 0) { + sampleRate = 22050; + } else if ((dwFormats & WAVE_FORMAT_1S08) != 0) { + sampleRate = 11025; + } else if ((dwFormats & WAVE_FORMAT_96S08) != 0) { + sampleRate = 96000; + } else { + return MA_FORMAT_NOT_SUPPORTED; + } + } + } + + if (pBitsPerSample) { + *pBitsPerSample = bitsPerSample; + } + if (pSampleRate) { + *pSampleRate = sampleRate; + } + + return MA_SUCCESS; +} + +ma_result ma_formats_flags_to_WAVEFORMATEX__winmm(DWORD dwFormats, WORD channels, WAVEFORMATEX* pWF) +{ + ma_assert(pWF != NULL); + + ma_zero_object(pWF); + pWF->cbSize = sizeof(*pWF); + pWF->wFormatTag = WAVE_FORMAT_PCM; + pWF->nChannels = (WORD)channels; + if (pWF->nChannels > 2) { + 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; + } + } + } + + pWF->nBlockAlign = (pWF->nChannels * pWF->wBitsPerSample) / 8; + pWF->nAvgBytesPerSec = pWF->nBlockAlign * pWF->nSamplesPerSec; + + return MA_SUCCESS; +} + +ma_result ma_context_get_device_info_from_WAVECAPS(ma_context* pContext, MA_WAVECAPSA* pCaps, ma_device_info* pDeviceInfo) +{ + WORD bitsPerSample; + DWORD sampleRate; + ma_result result; + + ma_assert(pContext != NULL); + ma_assert(pCaps != NULL); + ma_assert(pDeviceInfo != NULL); + + /* + 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. + */ + + /* Set the default to begin with. */ + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), pCaps->szPname, (size_t)-1); + + /* + Now try the registry. There's a few things to consider here: + - The name GUID can be null, in which we case we just need to stick to the original 31 characters. + - If the name GUID is not present in the registry we'll also need to stick to the original 31 characters. + - I like consistency, so I want the returned device names to be consistent with those returned by WASAPI and DirectSound. The + problem, however is that WASAPI and DirectSound use " ()" format (such as "Speakers (High Definition Audio)"), + but WinMM does not specificy the component name. From my admittedly limited testing, I've notice the component name seems to + usually fit within the 31 characters of the fixed sized buffer, so what I'm going to do is parse that string for the component + name, and then concatenate the name from the registry. + */ + if (!ma_is_guid_equal(&pCaps->NameGuid, &MA_GUID_NULL)) { + wchar_t guidStrW[256]; + if (((MA_PFN_StringFromGUID2)pContext->win32.StringFromGUID2)(&pCaps->NameGuid, guidStrW, ma_countof(guidStrW)) > 0) { + char guidStr[256]; + char keyStr[1024]; + HKEY hKey; + + WideCharToMultiByte(CP_UTF8, 0, guidStrW, -1, guidStr, sizeof(guidStr), 0, FALSE); + + ma_strcpy_s(keyStr, sizeof(keyStr), "SYSTEM\\CurrentControlSet\\Control\\MediaCategories\\"); + ma_strcat_s(keyStr, sizeof(keyStr), guidStr); + + if (((MA_PFN_RegOpenKeyExA)pContext->win32.RegOpenKeyExA)(HKEY_LOCAL_MACHINE, keyStr, 0, KEY_READ, &hKey) == ERROR_SUCCESS) { + BYTE nameFromReg[512]; + DWORD nameFromRegSize = sizeof(nameFromReg); + result = ((MA_PFN_RegQueryValueExA)pContext->win32.RegQueryValueExA)(hKey, "Name", 0, NULL, (LPBYTE)nameFromReg, (LPDWORD)&nameFromRegSize); + ((MA_PFN_RegCloseKey)pContext->win32.RegCloseKey)(hKey); + + if (result == ERROR_SUCCESS) { + /* We have the value from the registry, so now we need to construct the name string. */ + char name[1024]; + if (ma_strcpy_s(name, sizeof(name), pDeviceInfo->name) == 0) { + char* nameBeg = ma_find_last_character(name, '('); + if (nameBeg != NULL) { + size_t leadingLen = (nameBeg - name); + ma_strncpy_s(nameBeg + 1, sizeof(name) - leadingLen, (const char*)nameFromReg, (size_t)-1); + + /* The closing ")", if it can fit. */ + if (leadingLen + nameFromRegSize < sizeof(name)-1) { + ma_strcat_s(name, sizeof(name), ")"); + } + + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), name, (size_t)-1); + } + } + } + } + } + } + + + result = ma_get_best_info_from_formats_flags__winmm(pCaps->dwFormats, pCaps->wChannels, &bitsPerSample, &sampleRate); + if (result != MA_SUCCESS) { + 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; + } else if (bitsPerSample == 16) { + pDeviceInfo->formats[0] = ma_format_s16; + } else if (bitsPerSample == 24) { + pDeviceInfo->formats[0] = ma_format_s24; + } else if (bitsPerSample == 32) { + pDeviceInfo->formats[0] = ma_format_s32; + } else { + return MA_FORMAT_NOT_SUPPORTED; + } + + return MA_SUCCESS; +} + +ma_result ma_context_get_device_info_from_WAVEOUTCAPS2(ma_context* pContext, MA_WAVEOUTCAPS2A* pCaps, ma_device_info* pDeviceInfo) +{ + MA_WAVECAPSA caps; + + ma_assert(pContext != NULL); + ma_assert(pCaps != NULL); + ma_assert(pDeviceInfo != NULL); + + ma_copy_memory(caps.szPname, pCaps->szPname, sizeof(caps.szPname)); + caps.dwFormats = pCaps->dwFormats; + caps.wChannels = pCaps->wChannels; + caps.NameGuid = pCaps->NameGuid; + return ma_context_get_device_info_from_WAVECAPS(pContext, &caps, pDeviceInfo); +} + +ma_result ma_context_get_device_info_from_WAVEINCAPS2(ma_context* pContext, MA_WAVEINCAPS2A* pCaps, ma_device_info* pDeviceInfo) +{ + MA_WAVECAPSA caps; + + ma_assert(pContext != NULL); + ma_assert(pCaps != NULL); + ma_assert(pDeviceInfo != NULL); + + ma_copy_memory(caps.szPname, pCaps->szPname, sizeof(caps.szPname)); + caps.dwFormats = pCaps->dwFormats; + caps.wChannels = pCaps->wChannels; + caps.NameGuid = pCaps->NameGuid; + return ma_context_get_device_info_from_WAVECAPS(pContext, &caps, pDeviceInfo); +} + + +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; +} + +ma_result ma_context_enumerate_devices__winmm(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + UINT playbackDeviceCount; + UINT captureDeviceCount; + UINT iPlaybackDevice; + UINT iCaptureDevice; + + ma_assert(pContext != NULL); + ma_assert(callback != NULL); + + /* Playback. */ + playbackDeviceCount = ((MA_PFN_waveOutGetNumDevs)pContext->winmm.waveOutGetNumDevs)(); + for (iPlaybackDevice = 0; iPlaybackDevice < playbackDeviceCount; ++iPlaybackDevice) { + MMRESULT result; + MA_WAVEOUTCAPS2A caps; + + ma_zero_object(&caps); + + result = ((MA_PFN_waveOutGetDevCapsA)pContext->winmm.waveOutGetDevCapsA)(iPlaybackDevice, (WAVEOUTCAPSA*)&caps, sizeof(caps)); + if (result == MMSYSERR_NOERROR) { + ma_device_info deviceInfo; + + ma_zero_object(&deviceInfo); + deviceInfo.id.winmm = iPlaybackDevice; + + 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) { + return MA_SUCCESS; /* Enumeration was stopped. */ + } + } + } + } + + /* Capture. */ + captureDeviceCount = ((MA_PFN_waveInGetNumDevs)pContext->winmm.waveInGetNumDevs)(); + for (iCaptureDevice = 0; iCaptureDevice < captureDeviceCount; ++iCaptureDevice) { + MMRESULT result; + MA_WAVEINCAPS2A caps; + + ma_zero_object(&caps); + + result = ((MA_PFN_waveInGetDevCapsA)pContext->winmm.waveInGetDevCapsA)(iCaptureDevice, (WAVEINCAPSA*)&caps, sizeof(caps)); + if (result == MMSYSERR_NOERROR) { + ma_device_info deviceInfo; + + ma_zero_object(&deviceInfo); + deviceInfo.id.winmm = iCaptureDevice; + + 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) { + return MA_SUCCESS; /* Enumeration was stopped. */ + } + } + } + } + + return MA_SUCCESS; +} + +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) +{ + 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; + } + + pDeviceInfo->id.winmm = winMMDeviceID; + + 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); + } + } else { + MMRESULT result; + 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); + } + } + + return MA_NO_DEVICE; +} + + +void ma_device_uninit__winmm(ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + ((MA_PFN_waveInClose)pDevice->pContext->winmm.waveInClose)((HWAVEIN)pDevice->winmm.hDeviceCapture); + CloseHandle((HANDLE)pDevice->winmm.hEventCapture); + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ((MA_PFN_waveOutReset)pDevice->pContext->winmm.waveOutReset)((HWAVEOUT)pDevice->winmm.hDevicePlayback); + ((MA_PFN_waveOutClose)pDevice->pContext->winmm.waveOutClose)((HWAVEOUT)pDevice->winmm.hDevicePlayback); + CloseHandle((HANDLE)pDevice->winmm.hEventPlayback); + } + + ma_free(pDevice->winmm._pHeapData); + + ma_zero_object(&pDevice->winmm); /* Safety. */ +} + +ma_result ma_device_init__winmm(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +{ + const char* errorMsg = ""; + ma_result errorCode = MA_ERROR; + ma_result result = MA_SUCCESS; + ma_uint32 heapSize; + UINT winMMDeviceIDPlayback = 0; + UINT winMMDeviceIDCapture = 0; + ma_uint32 bufferSizeInMilliseconds; + + ma_assert(pDevice != NULL); + ma_zero_object(&pDevice->winmm); + + /* 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)) { + return MA_SHARE_MODE_NOT_SUPPORTED; + } + + bufferSizeInMilliseconds = pConfig->bufferSizeInMilliseconds; + if (bufferSizeInMilliseconds == 0) { + bufferSizeInMilliseconds = ma_calculate_buffer_size_in_milliseconds_from_frames(pConfig->bufferSizeInFrames, pConfig->sampleRate); + } + + /* WinMM has horrible latency. */ + if (pDevice->usingDefaultBufferSize) { + if (pConfig->performanceProfile == ma_performance_profile_low_latency) { + bufferSizeInMilliseconds = 40 * pConfig->periods; + } else { + bufferSizeInMilliseconds = 400 * pConfig->periods; + } + } + + + if (pConfig->playback.pDeviceID != NULL) { + winMMDeviceIDPlayback = (UINT)pConfig->playback.pDeviceID->winmm; + } + if (pConfig->capture.pDeviceID != NULL) { + winMMDeviceIDCapture = (UINT)pConfig->capture.pDeviceID->winmm; + } + + /* The capture device needs to be initialized first. */ + if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { + WAVEINCAPSA caps; + WAVEFORMATEX wf; + MMRESULT resultMM; + + /* We use an event to know when a new fragment needs to be enqueued. */ + pDevice->winmm.hEventCapture = (ma_handle)CreateEvent(NULL, TRUE, TRUE, NULL); + if (pDevice->winmm.hEventCapture == NULL) { + errorMsg = "[WinMM] Failed to create event for fragment enqueing for the capture device.", errorCode = MA_FAILED_TO_CREATE_EVENT; + goto on_error; + } + + /* The format should be based on the device's actual format. */ + if (((MA_PFN_waveInGetDevCapsA)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; + } + + result = ma_formats_flags_to_WAVEFORMATEX__winmm(caps.dwFormats, caps.wChannels, &wf); + if (result != MA_SUCCESS) { + errorMsg = "[WinMM] Could not find appropriate format for internal device.", errorCode = result; + goto on_error; + } + + resultMM = ((MA_PFN_waveInOpen)pDevice->pContext->winmm.waveInOpen)((LPHWAVEIN)&pDevice->winmm.hDeviceCapture, winMMDeviceIDCapture, &wf, (DWORD_PTR)pDevice->winmm.hEventCapture, (DWORD_PTR)pDevice, CALLBACK_EVENT | WAVE_ALLOWSYNC); + if (resultMM != MMSYSERR_NOERROR) { + errorMsg = "[WinMM] Failed to open capture device.", errorCode = MA_FAILED_TO_OPEN_BACKEND_DEVICE; + 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.internalBufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(bufferSizeInMilliseconds, pDevice->capture.internalSampleRate); + } + + if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + WAVEOUTCAPSA caps; + WAVEFORMATEX wf; + MMRESULT resultMM; + + /* We use an event to know when a new fragment needs to be enqueued. */ + pDevice->winmm.hEventPlayback = (ma_handle)CreateEvent(NULL, TRUE, TRUE, NULL); + if (pDevice->winmm.hEventPlayback == NULL) { + errorMsg = "[WinMM] Failed to create event for fragment enqueing for the playback device.", errorCode = MA_FAILED_TO_CREATE_EVENT; + goto on_error; + } + + /* The format should be based on the device's actual format. */ + if (((MA_PFN_waveOutGetDevCapsA)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; + } + + result = ma_formats_flags_to_WAVEFORMATEX__winmm(caps.dwFormats, caps.wChannels, &wf); + if (result != MA_SUCCESS) { + errorMsg = "[WinMM] Could not find appropriate format for internal device.", errorCode = result; + 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); + 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.internalBufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(bufferSizeInMilliseconds, pDevice->playback.internalSampleRate); + } + + /* + 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.internalBufferSizeInFrames*ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); + } + if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + heapSize += sizeof(WAVEHDR)*pDevice->playback.internalPeriods + (pDevice->playback.internalBufferSizeInFrames*ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + } + + pDevice->winmm._pHeapData = (ma_uint8*)ma_malloc(heapSize); + if (pDevice->winmm._pHeapData == NULL) { + errorMsg = "[WinMM] Failed to allocate memory for the intermediary buffer.", errorCode = MA_OUT_OF_MEMORY; + goto on_error; + } + + ma_zero_memory(pDevice->winmm._pHeapData, heapSize); + + if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { + ma_uint32 iPeriod; + + if (pConfig->deviceType == ma_device_type_capture) { + pDevice->winmm.pWAVEHDRCapture = pDevice->winmm._pHeapData; + pDevice->winmm.pIntermediaryBufferCapture = pDevice->winmm._pHeapData + (sizeof(WAVEHDR)*(pDevice->capture.internalPeriods)); + } else { + pDevice->winmm.pWAVEHDRCapture = pDevice->winmm._pHeapData; + pDevice->winmm.pIntermediaryBufferCapture = pDevice->winmm._pHeapData + (sizeof(WAVEHDR)*(pDevice->capture.internalPeriods + pDevice->playback.internalPeriods)); + } + + /* Prepare headers. */ + for (iPeriod = 0; iPeriod < pDevice->capture.internalPeriods; ++iPeriod) { + ma_uint32 fragmentSizeInBytes = ma_get_fragment_size_in_bytes(pDevice->capture.internalBufferSizeInFrames, pDevice->capture.internalPeriods, pDevice->capture.internalFormat, pDevice->capture.internalChannels); + + ((WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod].lpData = (LPSTR)(pDevice->winmm.pIntermediaryBufferCapture + (fragmentSizeInBytes*iPeriod)); + ((WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod].dwBufferLength = fragmentSizeInBytes; + ((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)); + + /* + 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 + it's unlocked and available for writing. A value of 1 means it's locked. + */ + ((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); + } 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->playback.internalBufferSizeInFrames*ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + } + + /* Prepare headers. */ + for (iPeriod = 0; iPeriod < pDevice->playback.internalPeriods; ++iPeriod) { + ma_uint32 fragmentSizeInBytes = ma_get_fragment_size_in_bytes(pDevice->playback.internalBufferSizeInFrames, pDevice->playback.internalPeriods, pDevice->playback.internalFormat, pDevice->playback.internalChannels); + + ((WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod].lpData = (LPSTR)(pDevice->winmm.pIntermediaryBufferPlayback + (fragmentSizeInBytes*iPeriod)); + ((WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod].dwBufferLength = fragmentSizeInBytes; + ((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)); + + /* + 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 + it's unlocked and available for writing. A value of 1 means it's locked. + */ + ((WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod].dwUser = 0; + } + } + + return MA_SUCCESS; + +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)); + } + } + + ((MA_PFN_waveInClose)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)); + } + } + + ((MA_PFN_waveOutClose)pContext->winmm.waveOutClose)((HWAVEOUT)pDevice->winmm.hDevicePlayback); + } + + ma_free(pDevice->winmm._pHeapData); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, errorMsg, errorCode); +} + +ma_result ma_device_stop__winmm(ma_device* pDevice) +{ + MMRESULT resultMM; + + ma_assert(pDevice != NULL); + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + if (pDevice->winmm.hDeviceCapture == NULL) { + return MA_INVALID_ARGS; + } + + resultMM = ((MA_PFN_waveInReset)pDevice->pContext->winmm.waveInReset)((HWAVEIN)pDevice->winmm.hDeviceCapture); + if (resultMM != MMSYSERR_NOERROR) { + ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] WARNING: Failed to reset capture device.", ma_result_from_MMRESULT(resultMM)); + } + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + if (pDevice->winmm.hDevicePlayback == NULL) { + return MA_INVALID_ARGS; + } + + resultMM = ((MA_PFN_waveOutReset)pDevice->pContext->winmm.waveOutReset)((HWAVEOUT)pDevice->winmm.hDevicePlayback); + if (resultMM != MMSYSERR_NOERROR) { + ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] WARNING: Failed to reset playback device.", ma_result_from_MMRESULT(resultMM)); + } + } + + ma_atomic_exchange_32(&pDevice->winmm.isStarted, MA_FALSE); + return MA_SUCCESS; +} + +ma_result ma_device_write__winmm(ma_device* pDevice, const void* pPCMFrames, ma_uint32 frameCount) +{ + ma_result result = MA_SUCCESS; + MMRESULT resultMM; + ma_uint32 totalFramesWritten; + WAVEHDR* pWAVEHDR; + + ma_assert(pDevice != NULL); + ma_assert(pPCMFrames != NULL); + + pWAVEHDR = (WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback; + + /* Keep processing as much data as possible. */ + totalFramesWritten = 0; + while (totalFramesWritten < frameCount) { + /* If the current header has some space available we need to write part of it. */ + if (pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].dwUser == 0) { /* 0 = unlocked. */ + /* + This header has room in it. We copy as much of it as we can. If we end up fully consuming the buffer we need to + write it out and move on to the next iteration. + */ + ma_uint32 bpf = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + ma_uint32 framesRemainingInHeader = (pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].dwBufferLength/bpf) - pDevice->winmm.headerFramesConsumedPlayback; + + ma_uint32 framesToCopy = ma_min(framesRemainingInHeader, (frameCount - totalFramesWritten)); + const void* pSrc = ma_offset_ptr(pPCMFrames, totalFramesWritten*bpf); + void* pDst = ma_offset_ptr(pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].lpData, pDevice->winmm.headerFramesConsumedPlayback*bpf); + ma_copy_memory(pDst, pSrc, framesToCopy*bpf); + + pDevice->winmm.headerFramesConsumedPlayback += framesToCopy; + totalFramesWritten += framesToCopy; + + /* If we've consumed the buffer entirely we need to write it out to the device. */ + if (pDevice->winmm.headerFramesConsumedPlayback == (pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].dwBufferLength/bpf)) { + pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].dwUser = 1; /* 1 = locked. */ + pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].dwFlags &= ~WHDR_DONE; /* <-- Need to make sure the WHDR_DONE flag is unset. */ + + /* Make sure the event is reset to a non-signaled state to ensure we don't prematurely return from WaitForSingleObject(). */ + ResetEvent((HANDLE)pDevice->winmm.hEventPlayback); + + /* The device will be started here. */ + resultMM = ((MA_PFN_waveOutWrite)pDevice->pContext->winmm.waveOutWrite)((HWAVEOUT)pDevice->winmm.hDevicePlayback, &pWAVEHDR[pDevice->winmm.iNextHeaderPlayback], sizeof(WAVEHDR)); + if (resultMM != MMSYSERR_NOERROR) { + result = ma_result_from_MMRESULT(resultMM); + ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] waveOutWrite() failed.", result); + break; + } + ma_atomic_exchange_32(&pDevice->winmm.isStarted, MA_TRUE); + + /* Make sure we move to the next header. */ + pDevice->winmm.iNextHeaderPlayback = (pDevice->winmm.iNextHeaderPlayback + 1) % pDevice->playback.internalPeriods; + pDevice->winmm.headerFramesConsumedPlayback = 0; + } + + /* If at this point we have consumed the entire input buffer we can return. */ + ma_assert(totalFramesWritten <= frameCount); + if (totalFramesWritten == frameCount) { + break; + } + + /* Getting here means there's more to process. */ + continue; + } + + /* Getting here means there isn't enough room in the buffer and we need to wait for one to become available. */ + if (WaitForSingleObject((HANDLE)pDevice->winmm.hEventPlayback, INFINITE) != WAIT_OBJECT_0) { + result = MA_ERROR; + break; + } + + /* Something happened. If the next buffer has been marked as done we need to reset a bit of state. */ + if ((pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].dwFlags & WHDR_DONE) != 0) { + pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].dwUser = 0; /* 0 = unlocked (make it available for writing). */ + pDevice->winmm.headerFramesConsumedPlayback = 0; + } + + /* If the device has been stopped we need to break. */ + if (!pDevice->winmm.isStarted) { + break; + } + } + + return result; +} + +ma_result ma_device_read__winmm(ma_device* pDevice, void* pPCMFrames, ma_uint32 frameCount) +{ + ma_result result = MA_SUCCESS; + MMRESULT resultMM; + ma_uint32 totalFramesRead; + WAVEHDR* pWAVEHDR; + + ma_assert(pDevice != NULL); + ma_assert(pPCMFrames != NULL); + + pWAVEHDR = (WAVEHDR*)pDevice->winmm.pWAVEHDRCapture; + + /* We want to start the device immediately. */ + if (!pDevice->winmm.isStarted) { + ma_uint32 iPeriod; + + /* 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)); + } + + ma_atomic_exchange_32(&pDevice->winmm.isStarted, MA_TRUE); + } + + /* Keep processing as much data as possible. */ + totalFramesRead = 0; + while (totalFramesRead < frameCount) { + /* If the current header has some space available we need to write part of it. */ + if (pWAVEHDR[pDevice->winmm.iNextHeaderCapture].dwUser == 0) { /* 0 = unlocked. */ + /* The buffer is available for reading. If we fully consume it we need to add it back to the buffer. */ + ma_uint32 bpf = ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + ma_uint32 framesRemainingInHeader = (pWAVEHDR[pDevice->winmm.iNextHeaderCapture].dwBufferLength/bpf) - pDevice->winmm.headerFramesConsumedCapture; + + ma_uint32 framesToCopy = ma_min(framesRemainingInHeader, (frameCount - totalFramesRead)); + const void* pSrc = ma_offset_ptr(pWAVEHDR[pDevice->winmm.iNextHeaderCapture].lpData, pDevice->winmm.headerFramesConsumedCapture*bpf); + void* pDst = ma_offset_ptr(pPCMFrames, totalFramesRead*bpf); + ma_copy_memory(pDst, pSrc, framesToCopy*bpf); + + pDevice->winmm.headerFramesConsumedCapture += framesToCopy; + totalFramesRead += framesToCopy; + + /* If we've consumed the buffer entirely we need to add it back to the device. */ + if (pDevice->winmm.headerFramesConsumedCapture == (pWAVEHDR[pDevice->winmm.iNextHeaderCapture].dwBufferLength/bpf)) { + pWAVEHDR[pDevice->winmm.iNextHeaderCapture].dwUser = 1; /* 1 = locked. */ + pWAVEHDR[pDevice->winmm.iNextHeaderCapture].dwFlags &= ~WHDR_DONE; /* <-- Need to make sure the WHDR_DONE flag is unset. */ + + /* 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); + + /* The device will be started here. */ + resultMM = ((MA_PFN_waveInAddBuffer)pDevice->pContext->winmm.waveInAddBuffer)((HWAVEIN)pDevice->winmm.hDeviceCapture, &((LPWAVEHDR)pDevice->winmm.pWAVEHDRCapture)[pDevice->winmm.iNextHeaderCapture], sizeof(WAVEHDR)); + if (resultMM != MMSYSERR_NOERROR) { + result = ma_result_from_MMRESULT(resultMM); + ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] waveInAddBuffer() failed.", result); + break; + } + + /* Make sure we move to the next header. */ + pDevice->winmm.iNextHeaderCapture = (pDevice->winmm.iNextHeaderCapture + 1) % pDevice->capture.internalPeriods; + pDevice->winmm.headerFramesConsumedCapture = 0; + } + + /* If at this point we have filled the entire input buffer we can return. */ + ma_assert(totalFramesRead <= frameCount); + if (totalFramesRead == frameCount) { + break; + } + + /* Getting here means there's more to process. */ + continue; + } + + /* Getting here means there isn't enough any data left to send to the client which means we need to wait for more. */ + if (WaitForSingleObject((HANDLE)pDevice->winmm.hEventCapture, INFINITE) != WAIT_OBJECT_0) { + result = MA_ERROR; + break; + } + + /* Something happened. If the next buffer has been marked as done we need to reset a bit of state. */ + if ((pWAVEHDR[pDevice->winmm.iNextHeaderCapture].dwFlags & WHDR_DONE) != 0) { + pWAVEHDR[pDevice->winmm.iNextHeaderCapture].dwUser = 0; /* 0 = unlocked (make it available for reading). */ + pDevice->winmm.headerFramesConsumedCapture = 0; + } + + /* If the device has been stopped we need to break. */ + if (!pDevice->winmm.isStarted) { + break; + } + } + + return result; +} + +ma_result ma_context_uninit__winmm(ma_context* pContext) +{ + ma_assert(pContext != NULL); + ma_assert(pContext->backend == ma_backend_winmm); + + ma_dlclose(pContext, pContext->winmm.hWinMM); + return MA_SUCCESS; +} + +ma_result ma_context_init__winmm(const ma_context_config* pConfig, ma_context* pContext) +{ + ma_assert(pContext != NULL); + + (void)pConfig; + + pContext->winmm.hWinMM = ma_dlopen(pContext, "winmm.dll"); + if (pContext->winmm.hWinMM == NULL) { + return MA_NO_BACKEND; + } + + pContext->winmm.waveOutGetNumDevs = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveOutGetNumDevs"); + pContext->winmm.waveOutGetDevCapsA = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveOutGetDevCapsA"); + pContext->winmm.waveOutOpen = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveOutOpen"); + pContext->winmm.waveOutClose = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveOutClose"); + pContext->winmm.waveOutPrepareHeader = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveOutPrepareHeader"); + pContext->winmm.waveOutUnprepareHeader = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveOutUnprepareHeader"); + pContext->winmm.waveOutWrite = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveOutWrite"); + pContext->winmm.waveOutReset = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveOutReset"); + pContext->winmm.waveInGetNumDevs = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveInGetNumDevs"); + pContext->winmm.waveInGetDevCapsA = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveInGetDevCapsA"); + pContext->winmm.waveInOpen = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveInOpen"); + pContext->winmm.waveInClose = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveInClose"); + pContext->winmm.waveInPrepareHeader = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveInPrepareHeader"); + pContext->winmm.waveInUnprepareHeader = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveInUnprepareHeader"); + pContext->winmm.waveInAddBuffer = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveInAddBuffer"); + 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. Started in onDeviceWrite/onDeviceRead. */ + pContext->onDeviceStop = ma_device_stop__winmm; + pContext->onDeviceWrite = ma_device_write__winmm; + pContext->onDeviceRead = ma_device_read__winmm; + + return MA_SUCCESS; +} +#endif + + + + +/****************************************************************************** + +ALSA Backend + +******************************************************************************/ +#ifdef MA_HAS_ALSA + +#ifdef MA_NO_RUNTIME_LINKING +#include +typedef snd_pcm_uframes_t ma_snd_pcm_uframes_t; +typedef snd_pcm_sframes_t ma_snd_pcm_sframes_t; +typedef snd_pcm_stream_t ma_snd_pcm_stream_t; +typedef snd_pcm_format_t ma_snd_pcm_format_t; +typedef snd_pcm_access_t ma_snd_pcm_access_t; +typedef snd_pcm_t ma_snd_pcm_t; +typedef snd_pcm_hw_params_t ma_snd_pcm_hw_params_t; +typedef snd_pcm_sw_params_t ma_snd_pcm_sw_params_t; +typedef snd_pcm_format_mask_t ma_snd_pcm_format_mask_t; +typedef snd_pcm_info_t ma_snd_pcm_info_t; +typedef snd_pcm_channel_area_t ma_snd_pcm_channel_area_t; +typedef snd_pcm_chmap_t ma_snd_pcm_chmap_t; + +/* snd_pcm_stream_t */ +#define MA_SND_PCM_STREAM_PLAYBACK SND_PCM_STREAM_PLAYBACK +#define MA_SND_PCM_STREAM_CAPTURE SND_PCM_STREAM_CAPTURE + +/* snd_pcm_format_t */ +#define MA_SND_PCM_FORMAT_UNKNOWN SND_PCM_FORMAT_UNKNOWN +#define MA_SND_PCM_FORMAT_U8 SND_PCM_FORMAT_U8 +#define MA_SND_PCM_FORMAT_S16_LE SND_PCM_FORMAT_S16_LE +#define MA_SND_PCM_FORMAT_S16_BE SND_PCM_FORMAT_S16_BE +#define MA_SND_PCM_FORMAT_S24_LE SND_PCM_FORMAT_S24_LE +#define MA_SND_PCM_FORMAT_S24_BE SND_PCM_FORMAT_S24_BE +#define MA_SND_PCM_FORMAT_S32_LE SND_PCM_FORMAT_S32_LE +#define MA_SND_PCM_FORMAT_S32_BE SND_PCM_FORMAT_S32_BE +#define MA_SND_PCM_FORMAT_FLOAT_LE SND_PCM_FORMAT_FLOAT_LE +#define MA_SND_PCM_FORMAT_FLOAT_BE SND_PCM_FORMAT_FLOAT_BE +#define MA_SND_PCM_FORMAT_FLOAT64_LE SND_PCM_FORMAT_FLOAT64_LE +#define MA_SND_PCM_FORMAT_FLOAT64_BE SND_PCM_FORMAT_FLOAT64_BE +#define MA_SND_PCM_FORMAT_MU_LAW SND_PCM_FORMAT_MU_LAW +#define MA_SND_PCM_FORMAT_A_LAW SND_PCM_FORMAT_A_LAW +#define MA_SND_PCM_FORMAT_S24_3LE SND_PCM_FORMAT_S24_3LE +#define MA_SND_PCM_FORMAT_S24_3BE SND_PCM_FORMAT_S24_3BE + +/* ma_snd_pcm_access_t */ +#define MA_SND_PCM_ACCESS_MMAP_INTERLEAVED SND_PCM_ACCESS_MMAP_INTERLEAVED +#define MA_SND_PCM_ACCESS_MMAP_NONINTERLEAVED SND_PCM_ACCESS_MMAP_NONINTERLEAVED +#define MA_SND_PCM_ACCESS_MMAP_COMPLEX SND_PCM_ACCESS_MMAP_COMPLEX +#define MA_SND_PCM_ACCESS_RW_INTERLEAVED SND_PCM_ACCESS_RW_INTERLEAVED +#define MA_SND_PCM_ACCESS_RW_NONINTERLEAVED SND_PCM_ACCESS_RW_NONINTERLEAVED + +/* Channel positions. */ +#define MA_SND_CHMAP_UNKNOWN SND_CHMAP_UNKNOWN +#define MA_SND_CHMAP_NA SND_CHMAP_NA +#define MA_SND_CHMAP_MONO SND_CHMAP_MONO +#define MA_SND_CHMAP_FL SND_CHMAP_FL +#define MA_SND_CHMAP_FR SND_CHMAP_FR +#define MA_SND_CHMAP_RL SND_CHMAP_RL +#define MA_SND_CHMAP_RR SND_CHMAP_RR +#define MA_SND_CHMAP_FC SND_CHMAP_FC +#define MA_SND_CHMAP_LFE SND_CHMAP_LFE +#define MA_SND_CHMAP_SL SND_CHMAP_SL +#define MA_SND_CHMAP_SR SND_CHMAP_SR +#define MA_SND_CHMAP_RC SND_CHMAP_RC +#define MA_SND_CHMAP_FLC SND_CHMAP_FLC +#define MA_SND_CHMAP_FRC SND_CHMAP_FRC +#define MA_SND_CHMAP_RLC SND_CHMAP_RLC +#define MA_SND_CHMAP_RRC SND_CHMAP_RRC +#define MA_SND_CHMAP_FLW SND_CHMAP_FLW +#define MA_SND_CHMAP_FRW SND_CHMAP_FRW +#define MA_SND_CHMAP_FLH SND_CHMAP_FLH +#define MA_SND_CHMAP_FCH SND_CHMAP_FCH +#define MA_SND_CHMAP_FRH SND_CHMAP_FRH +#define MA_SND_CHMAP_TC SND_CHMAP_TC +#define MA_SND_CHMAP_TFL SND_CHMAP_TFL +#define MA_SND_CHMAP_TFR SND_CHMAP_TFR +#define MA_SND_CHMAP_TFC SND_CHMAP_TFC +#define MA_SND_CHMAP_TRL SND_CHMAP_TRL +#define MA_SND_CHMAP_TRR SND_CHMAP_TRR +#define MA_SND_CHMAP_TRC SND_CHMAP_TRC +#define MA_SND_CHMAP_TFLC SND_CHMAP_TFLC +#define MA_SND_CHMAP_TFRC SND_CHMAP_TFRC +#define MA_SND_CHMAP_TSL SND_CHMAP_TSL +#define MA_SND_CHMAP_TSR SND_CHMAP_TSR +#define MA_SND_CHMAP_LLFE SND_CHMAP_LLFE +#define MA_SND_CHMAP_RLFE SND_CHMAP_RLFE +#define MA_SND_CHMAP_BC SND_CHMAP_BC +#define MA_SND_CHMAP_BLC SND_CHMAP_BLC +#define MA_SND_CHMAP_BRC SND_CHMAP_BRC + +/* Open mode flags. */ +#define MA_SND_PCM_NO_AUTO_RESAMPLE SND_PCM_NO_AUTO_RESAMPLE +#define MA_SND_PCM_NO_AUTO_CHANNELS SND_PCM_NO_AUTO_CHANNELS +#define MA_SND_PCM_NO_AUTO_FORMAT SND_PCM_NO_AUTO_FORMAT +#else +#include /* For EPIPE, etc. */ +typedef unsigned long ma_snd_pcm_uframes_t; +typedef long ma_snd_pcm_sframes_t; +typedef int ma_snd_pcm_stream_t; +typedef int ma_snd_pcm_format_t; +typedef int ma_snd_pcm_access_t; +typedef struct ma_snd_pcm_t ma_snd_pcm_t; +typedef struct ma_snd_pcm_hw_params_t ma_snd_pcm_hw_params_t; +typedef struct ma_snd_pcm_sw_params_t ma_snd_pcm_sw_params_t; +typedef struct ma_snd_pcm_format_mask_t ma_snd_pcm_format_mask_t; +typedef struct ma_snd_pcm_info_t ma_snd_pcm_info_t; +typedef struct +{ + void* addr; + unsigned int first; + unsigned int step; +} ma_snd_pcm_channel_area_t; +typedef struct +{ + unsigned int channels; + unsigned int pos[1]; +} ma_snd_pcm_chmap_t; + +/* snd_pcm_state_t */ +#define MA_SND_PCM_STATE_OPEN 0 +#define MA_SND_PCM_STATE_SETUP 1 +#define MA_SND_PCM_STATE_PREPARED 2 +#define MA_SND_PCM_STATE_RUNNING 3 +#define MA_SND_PCM_STATE_XRUN 4 +#define MA_SND_PCM_STATE_DRAINING 5 +#define MA_SND_PCM_STATE_PAUSED 6 +#define MA_SND_PCM_STATE_SUSPENDED 7 +#define MA_SND_PCM_STATE_DISCONNECTED 8 + +/* snd_pcm_stream_t */ +#define MA_SND_PCM_STREAM_PLAYBACK 0 +#define MA_SND_PCM_STREAM_CAPTURE 1 + +/* snd_pcm_format_t */ +#define MA_SND_PCM_FORMAT_UNKNOWN -1 +#define MA_SND_PCM_FORMAT_U8 1 +#define MA_SND_PCM_FORMAT_S16_LE 2 +#define MA_SND_PCM_FORMAT_S16_BE 3 +#define MA_SND_PCM_FORMAT_S24_LE 6 +#define MA_SND_PCM_FORMAT_S24_BE 7 +#define MA_SND_PCM_FORMAT_S32_LE 10 +#define MA_SND_PCM_FORMAT_S32_BE 11 +#define MA_SND_PCM_FORMAT_FLOAT_LE 14 +#define MA_SND_PCM_FORMAT_FLOAT_BE 15 +#define MA_SND_PCM_FORMAT_FLOAT64_LE 16 +#define MA_SND_PCM_FORMAT_FLOAT64_BE 17 +#define MA_SND_PCM_FORMAT_MU_LAW 20 +#define MA_SND_PCM_FORMAT_A_LAW 21 +#define MA_SND_PCM_FORMAT_S24_3LE 32 +#define MA_SND_PCM_FORMAT_S24_3BE 33 + +/* snd_pcm_access_t */ +#define MA_SND_PCM_ACCESS_MMAP_INTERLEAVED 0 +#define MA_SND_PCM_ACCESS_MMAP_NONINTERLEAVED 1 +#define MA_SND_PCM_ACCESS_MMAP_COMPLEX 2 +#define MA_SND_PCM_ACCESS_RW_INTERLEAVED 3 +#define MA_SND_PCM_ACCESS_RW_NONINTERLEAVED 4 + +/* Channel positions. */ +#define MA_SND_CHMAP_UNKNOWN 0 +#define MA_SND_CHMAP_NA 1 +#define MA_SND_CHMAP_MONO 2 +#define MA_SND_CHMAP_FL 3 +#define MA_SND_CHMAP_FR 4 +#define MA_SND_CHMAP_RL 5 +#define MA_SND_CHMAP_RR 6 +#define MA_SND_CHMAP_FC 7 +#define MA_SND_CHMAP_LFE 8 +#define MA_SND_CHMAP_SL 9 +#define MA_SND_CHMAP_SR 10 +#define MA_SND_CHMAP_RC 11 +#define MA_SND_CHMAP_FLC 12 +#define MA_SND_CHMAP_FRC 13 +#define MA_SND_CHMAP_RLC 14 +#define MA_SND_CHMAP_RRC 15 +#define MA_SND_CHMAP_FLW 16 +#define MA_SND_CHMAP_FRW 17 +#define MA_SND_CHMAP_FLH 18 +#define MA_SND_CHMAP_FCH 19 +#define MA_SND_CHMAP_FRH 20 +#define MA_SND_CHMAP_TC 21 +#define MA_SND_CHMAP_TFL 22 +#define MA_SND_CHMAP_TFR 23 +#define MA_SND_CHMAP_TFC 24 +#define MA_SND_CHMAP_TRL 25 +#define MA_SND_CHMAP_TRR 26 +#define MA_SND_CHMAP_TRC 27 +#define MA_SND_CHMAP_TFLC 28 +#define MA_SND_CHMAP_TFRC 29 +#define MA_SND_CHMAP_TSL 30 +#define MA_SND_CHMAP_TSR 31 +#define MA_SND_CHMAP_LLFE 32 +#define MA_SND_CHMAP_RLFE 33 +#define MA_SND_CHMAP_BC 34 +#define MA_SND_CHMAP_BLC 35 +#define MA_SND_CHMAP_BRC 36 + +/* Open mode flags. */ +#define MA_SND_PCM_NO_AUTO_RESAMPLE 0x00010000 +#define MA_SND_PCM_NO_AUTO_CHANNELS 0x00020000 +#define MA_SND_PCM_NO_AUTO_FORMAT 0x00040000 +#endif + +typedef int (* ma_snd_pcm_open_proc) (ma_snd_pcm_t **pcm, const char *name, ma_snd_pcm_stream_t stream, int mode); +typedef int (* ma_snd_pcm_close_proc) (ma_snd_pcm_t *pcm); +typedef size_t (* ma_snd_pcm_hw_params_sizeof_proc) (void); +typedef int (* ma_snd_pcm_hw_params_any_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params); +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_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_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_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); +typedef int (* ma_snd_pcm_hw_params_set_access_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, ma_snd_pcm_access_t _access); +typedef int (* ma_snd_pcm_hw_params_get_format_proc) (const ma_snd_pcm_hw_params_t *params, ma_snd_pcm_format_t *format); +typedef int (* ma_snd_pcm_hw_params_get_channels_proc) (const ma_snd_pcm_hw_params_t *params, unsigned int *val); +typedef int (* ma_snd_pcm_hw_params_get_channels_min_proc) (const ma_snd_pcm_hw_params_t *params, unsigned int *val); +typedef int (* ma_snd_pcm_hw_params_get_channels_max_proc) (const ma_snd_pcm_hw_params_t *params, unsigned int *val); +typedef int (* ma_snd_pcm_hw_params_get_rate_proc) (const ma_snd_pcm_hw_params_t *params, unsigned int *rate, int *dir); +typedef int (* ma_snd_pcm_hw_params_get_rate_min_proc) (const ma_snd_pcm_hw_params_t *params, unsigned int *rate, int *dir); +typedef int (* ma_snd_pcm_hw_params_get_rate_max_proc) (const ma_snd_pcm_hw_params_t *params, unsigned int *rate, int *dir); +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_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); +typedef int (* ma_snd_pcm_sw_params_get_boundary_proc) (ma_snd_pcm_sw_params_t *params, ma_snd_pcm_uframes_t* val); +typedef int (* ma_snd_pcm_sw_params_set_avail_min_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_sw_params_t *params, ma_snd_pcm_uframes_t val); +typedef int (* ma_snd_pcm_sw_params_set_start_threshold_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_sw_params_t *params, ma_snd_pcm_uframes_t val); +typedef int (* ma_snd_pcm_sw_params_set_stop_threshold_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_sw_params_t *params, ma_snd_pcm_uframes_t val); +typedef int (* ma_snd_pcm_sw_params_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_sw_params_t *params); +typedef size_t (* ma_snd_pcm_format_mask_sizeof_proc) (void); +typedef int (* ma_snd_pcm_format_mask_test_proc) (const ma_snd_pcm_format_mask_t *mask, ma_snd_pcm_format_t val); +typedef ma_snd_pcm_chmap_t * (* ma_snd_pcm_get_chmap_proc) (ma_snd_pcm_t *pcm); +typedef int (* ma_snd_pcm_state_proc) (ma_snd_pcm_t *pcm); +typedef int (* ma_snd_pcm_prepare_proc) (ma_snd_pcm_t *pcm); +typedef int (* ma_snd_pcm_start_proc) (ma_snd_pcm_t *pcm); +typedef int (* ma_snd_pcm_drop_proc) (ma_snd_pcm_t *pcm); +typedef int (* ma_snd_pcm_drain_proc) (ma_snd_pcm_t *pcm); +typedef int (* ma_snd_device_name_hint_proc) (int card, const char *iface, void ***hints); +typedef char * (* ma_snd_device_name_get_hint_proc) (const void *hint, const char *id); +typedef int (* ma_snd_card_get_index_proc) (const char *name); +typedef int (* ma_snd_device_name_free_hint_proc) (void **hints); +typedef int (* ma_snd_pcm_mmap_begin_proc) (ma_snd_pcm_t *pcm, const ma_snd_pcm_channel_area_t **areas, ma_snd_pcm_uframes_t *offset, ma_snd_pcm_uframes_t *frames); +typedef ma_snd_pcm_sframes_t (* ma_snd_pcm_mmap_commit_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_uframes_t offset, ma_snd_pcm_uframes_t frames); +typedef int (* ma_snd_pcm_recover_proc) (ma_snd_pcm_t *pcm, int err, int silent); +typedef ma_snd_pcm_sframes_t (* ma_snd_pcm_readi_proc) (ma_snd_pcm_t *pcm, void *buffer, ma_snd_pcm_uframes_t size); +typedef ma_snd_pcm_sframes_t (* ma_snd_pcm_writei_proc) (ma_snd_pcm_t *pcm, const void *buffer, ma_snd_pcm_uframes_t size); +typedef ma_snd_pcm_sframes_t (* ma_snd_pcm_avail_proc) (ma_snd_pcm_t *pcm); +typedef ma_snd_pcm_sframes_t (* ma_snd_pcm_avail_update_proc) (ma_snd_pcm_t *pcm); +typedef int (* ma_snd_pcm_wait_proc) (ma_snd_pcm_t *pcm, int timeout); +typedef int (* ma_snd_pcm_info_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_info_t* info); +typedef size_t (* ma_snd_pcm_info_sizeof_proc) (); +typedef const char* (* ma_snd_pcm_info_get_name_proc) (const ma_snd_pcm_info_t* info); +typedef int (* ma_snd_config_update_free_global_proc) (); + +/* This array specifies each of the common devices that can be used for both playback and capture. */ +const char* g_maCommonDeviceNamesALSA[] = { + "default", + "null", + "pulse", + "jack" +}; + +/* This array allows us to blacklist specific playback devices. */ +const char* g_maBlacklistedPlaybackDeviceNamesALSA[] = { + "" +}; + +/* This array allows us to blacklist specific capture devices. */ +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} +}; + +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; +} + +ma_snd_pcm_format_t ma_convert_ma_format_to_alsa_format(ma_format format) +{ + ma_snd_pcm_format_t ALSAFormats[] = { + MA_SND_PCM_FORMAT_UNKNOWN, /* ma_format_unknown */ + MA_SND_PCM_FORMAT_U8, /* ma_format_u8 */ + MA_SND_PCM_FORMAT_S16_LE, /* ma_format_s16 */ + MA_SND_PCM_FORMAT_S24_3LE, /* ma_format_s24 */ + MA_SND_PCM_FORMAT_S32_LE, /* ma_format_s32 */ + MA_SND_PCM_FORMAT_FLOAT_LE /* ma_format_f32 */ + }; + + if (ma_is_big_endian()) { + ALSAFormats[0] = MA_SND_PCM_FORMAT_UNKNOWN; + ALSAFormats[1] = MA_SND_PCM_FORMAT_U8; + ALSAFormats[2] = MA_SND_PCM_FORMAT_S16_BE; + ALSAFormats[3] = MA_SND_PCM_FORMAT_S24_3BE; + ALSAFormats[4] = MA_SND_PCM_FORMAT_S32_BE; + ALSAFormats[5] = MA_SND_PCM_FORMAT_FLOAT_BE; + } + + return ALSAFormats[format]; +} + +ma_format ma_format_from_alsa(ma_snd_pcm_format_t formatALSA) +{ + if (ma_is_little_endian()) { + switch (formatALSA) { + case MA_SND_PCM_FORMAT_S16_LE: return ma_format_s16; + case MA_SND_PCM_FORMAT_S24_3LE: return ma_format_s24; + case MA_SND_PCM_FORMAT_S32_LE: return ma_format_s32; + case MA_SND_PCM_FORMAT_FLOAT_LE: return ma_format_f32; + default: break; + } + } else { + switch (formatALSA) { + case MA_SND_PCM_FORMAT_S16_BE: return ma_format_s16; + case MA_SND_PCM_FORMAT_S24_3BE: return ma_format_s24; + case MA_SND_PCM_FORMAT_S32_BE: return ma_format_s32; + case MA_SND_PCM_FORMAT_FLOAT_BE: return ma_format_f32; + default: break; + } + } + + /* Endian agnostic. */ + switch (formatALSA) { + case MA_SND_PCM_FORMAT_U8: return ma_format_u8; + default: return ma_format_unknown; + } +} + +ma_channel ma_convert_alsa_channel_position_to_ma_channel(unsigned int alsaChannelPos) +{ + switch (alsaChannelPos) + { + case MA_SND_CHMAP_MONO: return MA_CHANNEL_MONO; + case MA_SND_CHMAP_FL: return MA_CHANNEL_FRONT_LEFT; + case MA_SND_CHMAP_FR: return MA_CHANNEL_FRONT_RIGHT; + case MA_SND_CHMAP_RL: return MA_CHANNEL_BACK_LEFT; + case MA_SND_CHMAP_RR: return MA_CHANNEL_BACK_RIGHT; + case MA_SND_CHMAP_FC: return MA_CHANNEL_FRONT_CENTER; + case MA_SND_CHMAP_LFE: return MA_CHANNEL_LFE; + case MA_SND_CHMAP_SL: return MA_CHANNEL_SIDE_LEFT; + case MA_SND_CHMAP_SR: return MA_CHANNEL_SIDE_RIGHT; + case MA_SND_CHMAP_RC: return MA_CHANNEL_BACK_CENTER; + case MA_SND_CHMAP_FLC: return MA_CHANNEL_FRONT_LEFT_CENTER; + case MA_SND_CHMAP_FRC: return MA_CHANNEL_FRONT_RIGHT_CENTER; + case MA_SND_CHMAP_RLC: return 0; + case MA_SND_CHMAP_RRC: return 0; + case MA_SND_CHMAP_FLW: return 0; + case MA_SND_CHMAP_FRW: return 0; + case MA_SND_CHMAP_FLH: return 0; + case MA_SND_CHMAP_FCH: return 0; + case MA_SND_CHMAP_FRH: return 0; + case MA_SND_CHMAP_TC: return MA_CHANNEL_TOP_CENTER; + case MA_SND_CHMAP_TFL: return MA_CHANNEL_TOP_FRONT_LEFT; + case MA_SND_CHMAP_TFR: return MA_CHANNEL_TOP_FRONT_RIGHT; + case MA_SND_CHMAP_TFC: return MA_CHANNEL_TOP_FRONT_CENTER; + case MA_SND_CHMAP_TRL: return MA_CHANNEL_TOP_BACK_LEFT; + case MA_SND_CHMAP_TRR: return MA_CHANNEL_TOP_BACK_RIGHT; + case MA_SND_CHMAP_TRC: return MA_CHANNEL_TOP_BACK_CENTER; + default: break; + } + + return 0; +} + +ma_bool32 ma_is_common_device_name__alsa(const char* name) +{ + size_t iName; + for (iName = 0; iName < ma_countof(g_maCommonDeviceNamesALSA); ++iName) { + if (ma_strcmp(name, g_maCommonDeviceNamesALSA[iName]) == 0) { + return MA_TRUE; + } + } + + return MA_FALSE; +} + + +ma_bool32 ma_is_playback_device_blacklisted__alsa(const char* name) +{ + size_t iName; + for (iName = 0; iName < ma_countof(g_maBlacklistedPlaybackDeviceNamesALSA); ++iName) { + if (ma_strcmp(name, g_maBlacklistedPlaybackDeviceNamesALSA[iName]) == 0) { + return MA_TRUE; + } + } + + return MA_FALSE; +} + +ma_bool32 ma_is_capture_device_blacklisted__alsa(const char* name) +{ + size_t iName; + for (iName = 0; iName < ma_countof(g_maBlacklistedCaptureDeviceNamesALSA); ++iName) { + if (ma_strcmp(name, g_maBlacklistedCaptureDeviceNamesALSA[iName]) == 0) { + return MA_TRUE; + } + } + + return MA_FALSE; +} + +ma_bool32 ma_is_device_blacklisted__alsa(ma_device_type deviceType, const char* name) +{ + if (deviceType == ma_device_type_playback) { + return ma_is_playback_device_blacklisted__alsa(name); + } else { + return ma_is_capture_device_blacklisted__alsa(name); + } +} + + +const char* ma_find_char(const char* str, char c, int* index) +{ + int i = 0; + for (;;) { + if (str[i] == '\0') { + if (index) *index = -1; + return NULL; + } + + if (str[i] == c) { + if (index) *index = i; + return str + i; + } + + i += 1; + } + + /* Should never get here, but treat it as though the character was not found to make me feel better inside. */ + if (index) *index = -1; + return NULL; +} + +ma_bool32 ma_is_device_name_in_hw_format__alsa(const char* hwid) +{ + /* This function is just checking whether or not hwid is in "hw:%d,%d" format. */ + + int commaPos; + const char* dev; + int i; + + if (hwid == NULL) { + return MA_FALSE; + } + + if (hwid[0] != 'h' || hwid[1] != 'w' || hwid[2] != ':') { + return MA_FALSE; + } + + hwid += 3; + + dev = ma_find_char(hwid, ',', &commaPos); + if (dev == NULL) { + return MA_FALSE; + } else { + dev += 1; /* Skip past the ",". */ + } + + /* Check if the part between the ":" and the "," contains only numbers. If not, return false. */ + for (i = 0; i < commaPos; ++i) { + if (hwid[i] < '0' || hwid[i] > '9') { + return MA_FALSE; + } + } + + /* Check if everything after the "," is numeric. If not, return false. */ + i = 0; + while (dev[i] != '\0') { + if (dev[i] < '0' || dev[i] > '9') { + return MA_FALSE; + } + i += 1; + } + + return MA_TRUE; +} + +int ma_convert_device_name_to_hw_format__alsa(ma_context* pContext, char* dst, size_t dstSize, const char* src) /* Returns 0 on success, non-0 on error. */ +{ + /* src should look something like this: "hw:CARD=I82801AAICH,DEV=0" */ + + int colonPos; + int commaPos; + char card[256]; + const char* dev; + int cardIndex; + + if (dst == NULL) { + return -1; + } + if (dstSize < 7) { + return -1; /* Absolute minimum size of the output buffer is 7 bytes. */ + } + + *dst = '\0'; /* Safety. */ + if (src == NULL) { + return -1; + } + + /* If the input name is already in "hw:%d,%d" format, just return that verbatim. */ + if (ma_is_device_name_in_hw_format__alsa(src)) { + return ma_strcpy_s(dst, dstSize, src); + } + + src = ma_find_char(src, ':', &colonPos); + if (src == NULL) { + return -1; /* Couldn't find a colon */ + } + + dev = ma_find_char(src, ',', &commaPos); + if (dev == NULL) { + dev = "0"; + ma_strncpy_s(card, sizeof(card), src+6, (size_t)-1); /* +6 = ":CARD=" */ + } else { + dev = dev + 5; /* +5 = ",DEV=" */ + ma_strncpy_s(card, sizeof(card), src+6, commaPos-6); /* +6 = ":CARD=" */ + } + + cardIndex = ((ma_snd_card_get_index_proc)pContext->alsa.snd_card_get_index)(card); + if (cardIndex < 0) { + return -2; /* Failed to retrieve the card index. */ + } + + /*printf("TESTING: CARD=%s,DEV=%s\n", card, dev); */ + + + /* Construction. */ + dst[0] = 'h'; dst[1] = 'w'; dst[2] = ':'; + if (ma_itoa_s(cardIndex, dst+3, dstSize-3, 10) != 0) { + return -3; + } + if (ma_strcat_s(dst, dstSize, ",") != 0) { + return -3; + } + if (ma_strcat_s(dst, dstSize, dev) != 0) { + return -3; + } + + return 0; +} + +ma_bool32 ma_does_id_exist_in_list__alsa(ma_device_id* pUniqueIDs, ma_uint32 count, const char* pHWID) +{ + ma_uint32 i; + + ma_assert(pHWID != NULL); + + for (i = 0; i < count; ++i) { + if (ma_strcmp(pUniqueIDs[i].alsa, pHWID) == 0) { + return MA_TRUE; + } + } + + return MA_FALSE; +} + + +ma_result ma_context_open_pcm__alsa(ma_context* pContext, ma_share_mode shareMode, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_snd_pcm_t** ppPCM) +{ + ma_snd_pcm_t* pPCM; + ma_snd_pcm_stream_t stream; + int openMode; + + ma_assert(pContext != NULL); + ma_assert(ppPCM != NULL); + + *ppPCM = NULL; + pPCM = NULL; + + stream = (deviceType == ma_device_type_playback) ? MA_SND_PCM_STREAM_PLAYBACK : MA_SND_PCM_STREAM_CAPTURE; + openMode = MA_SND_PCM_NO_AUTO_RESAMPLE | MA_SND_PCM_NO_AUTO_CHANNELS | MA_SND_PCM_NO_AUTO_FORMAT; + + if (pDeviceID == NULL) { + ma_bool32 isDeviceOpen; + size_t i; + + /* + We're opening the default device. I don't know if trying anything other than "default" is necessary, but it makes + me feel better to try as hard as we can get to get _something_ working. + */ + const char* defaultDeviceNames[] = { + "default", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL + }; + + if (shareMode == ma_share_mode_exclusive) { + defaultDeviceNames[1] = "hw"; + defaultDeviceNames[2] = "hw:0"; + defaultDeviceNames[3] = "hw:0,0"; + } else { + if (deviceType == ma_device_type_playback) { + defaultDeviceNames[1] = "dmix"; + defaultDeviceNames[2] = "dmix:0"; + defaultDeviceNames[3] = "dmix:0,0"; + } else { + defaultDeviceNames[1] = "dsnoop"; + defaultDeviceNames[2] = "dsnoop:0"; + defaultDeviceNames[3] = "dsnoop:0,0"; + } + defaultDeviceNames[4] = "hw"; + defaultDeviceNames[5] = "hw:0"; + defaultDeviceNames[6] = "hw:0,0"; + } + + isDeviceOpen = MA_FALSE; + for (i = 0; i < ma_countof(defaultDeviceNames); ++i) { + if (defaultDeviceNames[i] != NULL && defaultDeviceNames[i][0] != '\0') { + if (((ma_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)(&pPCM, defaultDeviceNames[i], stream, openMode) == 0) { + isDeviceOpen = MA_TRUE; + break; + } + } + } + + if (!isDeviceOpen) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_open() failed when trying to open an appropriate default device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + } 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"). + */ + + /* May end up needing to make small adjustments to the ID, so make a copy. */ + ma_device_id deviceID = *pDeviceID; + ma_bool32 isDeviceOpen = MA_FALSE; + + if (deviceID.alsa[0] != ':') { + /* The ID is not in ":0,0" format. Use the ID exactly as-is. */ + if (((ma_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)(&pPCM, deviceID.alsa, stream, openMode) == 0) { + isDeviceOpen = MA_TRUE; + } + } else { + char hwid[256]; + + /* The ID is in ":0,0" format. Try different plugins depending on the shared mode. */ + if (deviceID.alsa[1] == '\0') { + deviceID.alsa[0] = '\0'; /* An ID of ":" should be converted to "". */ + } + + if (shareMode == ma_share_mode_shared) { + if (deviceType == ma_device_type_playback) { + ma_strcpy_s(hwid, sizeof(hwid), "dmix"); + } else { + ma_strcpy_s(hwid, sizeof(hwid), "dsnoop"); + } + + if (ma_strcat_s(hwid, sizeof(hwid), deviceID.alsa) == 0) { + if (((ma_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)(&pPCM, hwid, stream, openMode) == 0) { + isDeviceOpen = MA_TRUE; + } + } + } + + /* If at this point we still don't have an open device it means we're either preferencing exclusive mode or opening with "dmix"/"dsnoop" failed. */ + if (!isDeviceOpen) { + ma_strcpy_s(hwid, sizeof(hwid), "hw"); + if (ma_strcat_s(hwid, sizeof(hwid), deviceID.alsa) == 0) { + if (((ma_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)(&pPCM, hwid, stream, openMode) == 0) { + isDeviceOpen = MA_TRUE; + } + } + } + } + + if (!isDeviceOpen) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_open() failed.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + } + + *ppPCM = pPCM; + return MA_SUCCESS; +} + + +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; +} + +ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_bool32 cbResult = MA_TRUE; + char** ppDeviceHints; + ma_device_id* pUniqueIDs = NULL; + ma_uint32 uniqueIDCount = 0; + char** ppNextDeviceHint; + + ma_assert(pContext != NULL); + ma_assert(callback != NULL); + + ma_mutex_lock(&pContext->alsa.internalDeviceEnumLock); + + if (((ma_snd_device_name_hint_proc)pContext->alsa.snd_device_name_hint)(-1, "pcm", (void***)&ppDeviceHints) < 0) { + ma_mutex_unlock(&pContext->alsa.internalDeviceEnumLock); + 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_device_type deviceType = ma_device_type_playback; + ma_bool32 stopEnumeration = MA_FALSE; + char hwid[sizeof(pUniqueIDs->alsa)]; + ma_device_info deviceInfo; + + if ((IOID == NULL || ma_strcmp(IOID, "Output") == 0)) { + deviceType = ma_device_type_playback; + } + if ((IOID != NULL && ma_strcmp(IOID, "Input" ) == 0)) { + deviceType = ma_device_type_capture; + } + + if (NAME != NULL) { + if (pContext->alsa.useVerboseDeviceEnumeration) { + /* Verbose mode. Use the name exactly as-is. */ + ma_strncpy_s(hwid, sizeof(hwid), NAME, (size_t)-1); + } else { + /* Simplified mode. Use ":%d,%d" format. */ + if (ma_convert_device_name_to_hw_format__alsa(pContext, hwid, sizeof(hwid), NAME) == 0) { + /* + At this point, hwid looks like "hw:0,0". In simplified enumeration mode, we actually want to strip off the + plugin name so it looks like ":0,0". The reason for this is that this special format is detected at device + initialization time and is used as an indicator to try and use the most appropriate plugin depending on the + device type and sharing mode. + */ + char* dst = hwid; + char* src = hwid+2; + while ((*dst++ = *src++)); + } else { + /* Conversion to "hw:%d,%d" failed. Just use the name as-is. */ + ma_strncpy_s(hwid, sizeof(hwid), NAME, (size_t)-1); + } + + if (ma_does_id_exist_in_list__alsa(pUniqueIDs, uniqueIDCount, hwid)) { + goto next_device; /* The device has already been enumerated. Move on to the next one. */ + } else { + /* The device has not yet been enumerated. Make sure it's added to our list so that it's not enumerated again. */ + ma_device_id* pNewUniqueIDs = (ma_device_id*)ma_realloc(pUniqueIDs, sizeof(*pUniqueIDs) * (uniqueIDCount + 1)); + if (pNewUniqueIDs == NULL) { + goto next_device; /* Failed to allocate memory. */ + } + + pUniqueIDs = pNewUniqueIDs; + ma_copy_memory(pUniqueIDs[uniqueIDCount].alsa, hwid, sizeof(hwid)); + uniqueIDCount += 1; + } + } + } else { + ma_zero_memory(hwid, sizeof(hwid)); + } + + ma_zero_object(&deviceInfo); + ma_strncpy_s(deviceInfo.id.alsa, sizeof(deviceInfo.id.alsa), hwid, (size_t)-1); + + /* + DESC is the friendly name. We treat this slightly differently depending on whether or not we are using verbose + device enumeration. In verbose mode we want to take the entire description so that the end-user can distinguish + between the subdevices of each card/dev pair. In simplified mode, however, we only want the first part of the + description. + + The value in DESC seems to be split into two lines, with the first line being the name of the device and the + second line being a description of the device. I don't like having the description be across two lines because + it makes formatting ugly and annoying. I'm therefore deciding to put it all on a single line with the second line + being put into parentheses. In simplified mode I'm just stripping the second line entirely. + */ + if (DESC != NULL) { + int lfPos; + const char* line2 = ma_find_char(DESC, '\n', &lfPos); + if (line2 != NULL) { + line2 += 1; /* Skip past the new-line character. */ + + if (pContext->alsa.useVerboseDeviceEnumeration) { + /* Verbose mode. Put the second line in brackets. */ + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), DESC, lfPos); + ma_strcat_s (deviceInfo.name, sizeof(deviceInfo.name), " ("); + ma_strcat_s (deviceInfo.name, sizeof(deviceInfo.name), line2); + ma_strcat_s (deviceInfo.name, sizeof(deviceInfo.name), ")"); + } else { + /* Simplified mode. Strip the second line entirely. */ + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), DESC, lfPos); + } + } else { + /* There's no second line. Just copy the whole description. */ + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), DESC, (size_t)-1); + } + } + + if (!ma_is_device_blacklisted__alsa(deviceType, NAME)) { + cbResult = callback(pContext, deviceType, &deviceInfo, pUserData); + } + + /* + Some devices are both playback and capture, but they are only enumerated by ALSA once. We need to fire the callback + again for the other device type in this case. We do this for known devices. + */ + if (cbResult) { + if (ma_is_common_device_name__alsa(NAME)) { + if (deviceType == ma_device_type_playback) { + if (!ma_is_capture_device_blacklisted__alsa(NAME)) { + cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); + } + } else { + if (!ma_is_playback_device_blacklisted__alsa(NAME)) { + cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); + } + } + } + } + + if (cbResult == MA_FALSE) { + stopEnumeration = MA_TRUE; + } + + next_device: + free(NAME); + free(DESC); + free(IOID); + ppNextDeviceHint += 1; + + /* We need to stop enumeration if the callback returned false. */ + if (stopEnumeration) { + break; + } + } + + ma_free(pUniqueIDs); + ((ma_snd_device_name_free_hint_proc)pContext->alsa.snd_device_name_free_hint)((void**)ppDeviceHints); + + ma_mutex_unlock(&pContext->alsa.internalDeviceEnumLock); + + return MA_SUCCESS; +} + + +typedef struct +{ + ma_device_type deviceType; + const ma_device_id* pDeviceID; + ma_share_mode shareMode; + ma_device_info* pDeviceInfo; + ma_bool32 foundDevice; +} ma_context_get_device_info_enum_callback_data__alsa; + +ma_bool32 ma_context_get_device_info_enum_callback__alsa(ma_context* pContext, ma_device_type deviceType, const ma_device_info* pDeviceInfo, void* pUserData) +{ + ma_context_get_device_info_enum_callback_data__alsa* pData = (ma_context_get_device_info_enum_callback_data__alsa*)pUserData; + ma_assert(pData != NULL); + + 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)) { + ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pDeviceInfo->name, (size_t)-1); + pData->foundDevice = MA_TRUE; + } + } + + /* Keep enumerating until we have found the device. */ + return !pData->foundDevice; +} + +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) +{ + ma_context_get_device_info_enum_callback_data__alsa data; + ma_result result; + ma_snd_pcm_t* pPCM; + ma_snd_pcm_hw_params_t* pHWParams; + ma_snd_pcm_format_mask_t* pFormatMask; + int sampleRateDir = 0; + + ma_assert(pContext != NULL); + + /* We just enumerate to find basic information about the device. */ + data.deviceType = deviceType; + data.pDeviceID = pDeviceID; + data.shareMode = shareMode; + data.pDeviceInfo = pDeviceInfo; + data.foundDevice = MA_FALSE; + result = ma_context_enumerate_devices__alsa(pContext, ma_context_get_device_info_enum_callback__alsa, &data); + if (result != MA_SUCCESS) { + return result; + } + + if (!data.foundDevice) { + return MA_NO_DEVICE; + } + + /* For detailed info we need to open the device. */ + result = ma_context_open_pcm__alsa(pContext, shareMode, deviceType, pDeviceID, &pPCM); + if (result != MA_SUCCESS) { + return result; + } + + /* We need to initialize a HW parameters object in order to know what formats are supported. */ + pHWParams = (ma_snd_pcm_hw_params_t*)calloc(1, ((ma_snd_pcm_hw_params_sizeof_proc)pContext->alsa.snd_pcm_hw_params_sizeof)()); + if (pHWParams == NULL) { + return MA_OUT_OF_MEMORY; + } + + if (((ma_snd_pcm_hw_params_any_proc)pContext->alsa.snd_pcm_hw_params_any)(pPCM, pHWParams) < 0) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed.", MA_FAILED_TO_CONFIGURE_BACKEND_DEVICE); + } + + ((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); + + /* Formats. */ + pFormatMask = (ma_snd_pcm_format_mask_t*)calloc(1, ((ma_snd_pcm_format_mask_sizeof_proc)pContext->alsa.snd_pcm_format_mask_sizeof)()); + if (pFormatMask == NULL) { + 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); + + 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(pFormatMask); + ma_free(pHWParams); + + ((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(). +*/ +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; +} + +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_FAILED_TO_SEND_DATA_TO_DEVICE); + return MA_FALSE; + } + + break; /* Success. */ + } else { + ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_writei() failed when writing initial data.", MA_FAILED_TO_SEND_DATA_TO_DEVICE); + return MA_FALSE; + } + } else { + break; /* Success. */ + } + } + } + + return MA_TRUE; +} + +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_FAILED_TO_READ_DATA_FROM_DEVICE); + 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 */ + +void ma_device_uninit__alsa(ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + if ((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture) { + ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture); + } + + 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); + } +} + +ma_result ma_device_init_by_type__alsa(ma_context* pContext, const ma_device_config* pConfig, ma_device_type deviceType, ma_device* pDevice) +{ + ma_result result; + ma_snd_pcm_t* pPCM; + ma_bool32 isUsingMMap; + ma_snd_pcm_format_t formatALSA; + ma_share_mode shareMode; + ma_device_id* pDeviceID; + ma_format internalFormat; + ma_uint32 internalChannels; + ma_uint32 internalSampleRate; + ma_channel internalChannelMap[MA_MAX_CHANNELS]; + ma_uint32 internalBufferSizeInFrames; + ma_uint32 internalPeriods; + 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; + + result = ma_context_open_pcm__alsa(pContext, shareMode, deviceType, pDeviceID, &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*)calloc(1, ((ma_snd_pcm_info_sizeof_proc)pContext->alsa.snd_pcm_info_sizeof)()); + 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(pInfo); + 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(pInfo); + } + + + /* Hardware parameters. */ + pHWParams = (ma_snd_pcm_hw_params_t*)calloc(1, ((ma_snd_pcm_hw_params_sizeof_proc)pContext->alsa.snd_pcm_hw_params_sizeof)()); + if (pHWParams == NULL) { + return MA_OUT_OF_MEMORY; + } + + if (((ma_snd_pcm_hw_params_any_proc)pContext->alsa.snd_pcm_hw_params_any)(pPCM, pHWParams) < 0) { + ma_free(pHWParams); + ((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_FAILED_TO_CONFIGURE_BACKEND_DEVICE); + } + + /* MMAP Mode. Try using interleaved MMAP access. If this fails, fall back to standard readi/writei. */ + isUsingMMap = MA_FALSE; +#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) { + pDevice->alsa.isUsingMMap = MA_TRUE; + } + } + } +#endif + + if (!isUsingMMap) { + if (((ma_snd_pcm_hw_params_set_access_proc)pContext->alsa.snd_pcm_hw_params_set_access)(pPCM, pHWParams, MA_SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { + ma_free(pHWParams); + ((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_FORMAT_NOT_SUPPORTED); + } + } + + /* + Most important properties first. The documentation for OSS (yes, I know this is ALSA!) recommends format, channels, then sample rate. I can't + find any documentation for ALSA specifically, so I'm going to copy the recommendation for OSS. + */ + + /* Format. */ + { + ma_snd_pcm_format_mask_t* pFormatMask; + + /* Try getting every supported format first. */ + pFormatMask = (ma_snd_pcm_format_mask_t*)calloc(1, ((ma_snd_pcm_format_mask_sizeof_proc)pContext->alsa.snd_pcm_format_mask_sizeof)()); + if (pFormatMask == NULL) { + ma_free(pHWParams); + ((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; + } + + 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]; + break; + } + } + + if (formatALSA == MA_SND_PCM_FORMAT_UNKNOWN) { + ma_free(pHWParams); + ((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(pFormatMask); + pFormatMask = NULL; + + if (((ma_snd_pcm_hw_params_set_format_proc)pContext->alsa.snd_pcm_hw_params_set_format)(pPCM, pHWParams, formatALSA) < 0) { + ma_free(pHWParams); + ((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_FORMAT_NOT_SUPPORTED); + } + + internalFormat = ma_format_from_alsa(formatALSA); + if (internalFormat == ma_format_unknown) { + ma_free(pHWParams); + ((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); + } + } + + /* Channels. */ + { + unsigned int channels = (deviceType == ma_device_type_capture) ? pConfig->capture.channels : pConfig->playback.channels; + if (((ma_snd_pcm_hw_params_set_channels_near_proc)pContext->alsa.snd_pcm_hw_params_set_channels_near)(pPCM, pHWParams, &channels) < 0) { + ma_free(pHWParams); + ((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_FORMAT_NOT_SUPPORTED); + } + internalChannels = (ma_uint32)channels; + } + + /* Sample Rate */ + { + unsigned int sampleRate; + + /* + 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); + + sampleRate = pConfig->sampleRate; + if (((ma_snd_pcm_hw_params_set_rate_near_proc)pContext->alsa.snd_pcm_hw_params_set_rate_near)(pPCM, pHWParams, &sampleRate, 0) < 0) { + ma_free(pHWParams); + ((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_FORMAT_NOT_SUPPORTED); + } + internalSampleRate = (ma_uint32)sampleRate; + } + + /* Buffer Size */ + { + ma_snd_pcm_uframes_t actualBufferSizeInFrames = pConfig->bufferSizeInFrames; + if (actualBufferSizeInFrames == 0) { + actualBufferSizeInFrames = ma_scale_buffer_size(ma_calculate_buffer_size_in_frames_from_milliseconds(pConfig->bufferSizeInMilliseconds, internalSampleRate), bufferSizeScaleFactor); + } + + if (((ma_snd_pcm_hw_params_set_buffer_size_near_proc)pContext->alsa.snd_pcm_hw_params_set_buffer_size_near)(pPCM, pHWParams, &actualBufferSizeInFrames) < 0) { + ma_free(pHWParams); + ((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_FORMAT_NOT_SUPPORTED); + } + internalBufferSizeInFrames = actualBufferSizeInFrames; + } + + /* Periods. */ + { + ma_uint32 periods = pConfig->periods; + if (((ma_snd_pcm_hw_params_set_periods_near_proc)pContext->alsa.snd_pcm_hw_params_set_periods_near)(pPCM, pHWParams, &periods, NULL) < 0) { + ma_free(pHWParams); + ((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_FORMAT_NOT_SUPPORTED); + } + internalPeriods = periods; + } + + /* Apply hardware parameters. */ + if (((ma_snd_pcm_hw_params_proc)pContext->alsa.snd_pcm_hw_params)(pPCM, pHWParams) < 0) { + ma_free(pHWParams); + ((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_FAILED_TO_CONFIGURE_BACKEND_DEVICE); + } + + ma_free(pHWParams); + pHWParams = NULL; + + + /* Software parameters. */ + pSWParams = (ma_snd_pcm_sw_params_t*)calloc(1, ((ma_snd_pcm_sw_params_sizeof_proc)pContext->alsa.snd_pcm_sw_params_sizeof)()); + if (pSWParams == NULL) { + ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); + return MA_OUT_OF_MEMORY; + } + + if (((ma_snd_pcm_sw_params_current_proc)pContext->alsa.snd_pcm_sw_params_current)(pPCM, pSWParams) != 0) { + ma_free(pSWParams); + ((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_FAILED_TO_CONFIGURE_BACKEND_DEVICE); + } + + if (deviceType == ma_device_type_capture) { + if (((ma_snd_pcm_sw_params_set_avail_min_proc)pContext->alsa.snd_pcm_sw_params_set_avail_min)(pPCM, pSWParams, 1) != 0) { + ma_free(pSWParams); + ((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_FORMAT_NOT_SUPPORTED); + } + } else { + if (((ma_snd_pcm_sw_params_set_avail_min_proc)pContext->alsa.snd_pcm_sw_params_set_avail_min)(pPCM, pSWParams, 1) != 0) { + ma_free(pSWParams); + ((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_FORMAT_NOT_SUPPORTED); + } + } + + + if (((ma_snd_pcm_sw_params_get_boundary_proc)pContext->alsa.snd_pcm_sw_params_get_boundary)(pSWParams, &bufferBoundary) < 0) { + bufferBoundary = internalBufferSizeInFrames; + } + + /*printf("TRACE: bufferBoundary=%ld\n", bufferBoundary);*/ + + if (deviceType == ma_device_type_playback && !isUsingMMap) { /* Only playback devices in writei/readi mode need a start threshold. */ + /* + 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. + */ + if (((ma_snd_pcm_sw_params_set_start_threshold_proc)pContext->alsa.snd_pcm_sw_params_set_start_threshold)(pPCM, pSWParams, internalBufferSizeInFrames) != 0) { + ma_free(pSWParams); + ((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_FAILED_TO_CONFIGURE_BACKEND_DEVICE); + } + if (((ma_snd_pcm_sw_params_set_stop_threshold_proc)pContext->alsa.snd_pcm_sw_params_set_stop_threshold)(pPCM, pSWParams, bufferBoundary) != 0) { /* Set to boundary to loop instead of stop in the event of an xrun. */ + ma_free(pSWParams); + ((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_FAILED_TO_CONFIGURE_BACKEND_DEVICE); + } + } + + if (((ma_snd_pcm_sw_params_proc)pContext->alsa.snd_pcm_sw_params)(pPCM, pSWParams) != 0) { + ma_free(pSWParams); + ((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_FAILED_TO_CONFIGURE_BACKEND_DEVICE); + } + + ma_free(pSWParams); + 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); + if (pChmap != NULL) { + ma_uint32 iChannel; + + /* There are cases where the returned channel map can have a different channel count than was returned by snd_pcm_hw_params_set_channels_near(). */ + if (pChmap->channels >= internalChannels) { + /* Drop excess channels. */ + for (iChannel = 0; iChannel < internalChannels; ++iChannel) { + internalChannelMap[iChannel] = ma_convert_alsa_channel_position_to_ma_channel(pChmap->pos[iChannel]); + } + } else { + ma_uint32 i; + + /* + Excess channels use defaults. Do an initial fill with defaults, overwrite the first pChmap->channels, validate to ensure there are no duplicate + channels. If validation fails, fall back to defaults. + */ + ma_bool32 isValid = MA_TRUE; + + /* Fill with defaults. */ + ma_get_standard_channel_map(ma_standard_channel_map_alsa, internalChannels, internalChannelMap); + + /* Overwrite first pChmap->channels channels. */ + for (iChannel = 0; iChannel < pChmap->channels; ++iChannel) { + internalChannelMap[iChannel] = ma_convert_alsa_channel_position_to_ma_channel(pChmap->pos[iChannel]); + } + + /* Validate. */ + for (i = 0; i < internalChannels && isValid; ++i) { + ma_uint32 j; + for (j = i+1; j < internalChannels; ++j) { + if (internalChannelMap[i] == internalChannelMap[j]) { + isValid = MA_FALSE; + break; + } + } + } + + /* If our channel map is invalid, fall back to defaults. */ + if (!isValid) { + ma_get_standard_channel_map(ma_standard_channel_map_alsa, internalChannels, internalChannelMap); + } + } + + free(pChmap); + pChmap = NULL; + } else { + /* Could not retrieve the channel map. Fall back to a hard-coded assumption. */ + ma_get_standard_channel_map(ma_standard_channel_map_alsa, internalChannels, internalChannelMap); + } + } + + + /* We're done. Prepare the device. */ + if (((ma_snd_pcm_prepare_proc)pDevice->pContext->alsa.snd_pcm_prepare)(pPCM) < 0) { + ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to prepare device.", MA_FAILED_TO_START_BACKEND_DEVICE); + } + + + 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, internalChannels); + pDevice->capture.internalBufferSizeInFrames = internalBufferSizeInFrames; + pDevice->capture.internalPeriods = internalPeriods; + } 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, internalChannels); + pDevice->playback.internalBufferSizeInFrames = internalBufferSizeInFrames; + pDevice->playback.internalPeriods = internalPeriods; + } + + return MA_SUCCESS; +} + +ma_result ma_device_init__alsa(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + ma_zero_object(&pDevice->alsa); + + 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); + 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); + if (result != MA_SUCCESS) { + return result; + } + } + + return MA_SUCCESS; +} + +#if 0 +ma_result ma_device_start__alsa(ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + /* Prepare the device first... */ + if (((ma_snd_pcm_prepare_proc)pDevice->pContext->alsa.snd_pcm_prepare)((ma_snd_pcm_t*)pDevice->alsa.pPCM) < 0) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to prepare device.", MA_FAILED_TO_START_BACKEND_DEVICE); + } + + /* + ... and then grab an initial chunk from the client. After this is done, the device should + automatically start playing, since that's how we configured the software parameters. + */ + if (pDevice->type == ma_device_type_playback) { + if (!ma_device_read_from_client_and_write__alsa(pDevice)) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to write initial chunk of data to the playback device.", MA_FAILED_TO_SEND_DATA_TO_DEVICE); + } + + /* mmap mode requires an explicit start. */ + if (pDevice->alsa.isUsingMMap) { + if (((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCM) < 0) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start capture device.", MA_FAILED_TO_START_BACKEND_DEVICE); + } + } + } else { + if (((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCM) < 0) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start capture device.", MA_FAILED_TO_START_BACKEND_DEVICE); + } + } + + return MA_SUCCESS; +} +#endif /* 0 */ + +ma_result ma_device_stop__alsa(ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + 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) { + /* Using drain instead of drop because ma_device_stop() is defined such that pending frames are processed before returning. */ + ((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; +} + +ma_result ma_device_write__alsa(ma_device* pDevice, const void* pPCMFrames, ma_uint32 frameCount) +{ + ma_snd_pcm_sframes_t resultALSA; + ma_uint32 totalPCMFramesProcessed; + + ma_assert(pDevice != NULL); + ma_assert(pPCMFrames != NULL); + + /*printf("TRACE: Enter write()\n");*/ + + totalPCMFramesProcessed = 0; + while (totalPCMFramesProcessed < frameCount) { + const void* pSrc = ma_offset_ptr(pPCMFrames, totalPCMFramesProcessed * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + ma_uint32 framesRemaining = (frameCount - totalPCMFramesProcessed); + + /*printf("TRACE: Writing %d frames (frameCount=%d)\n", framesRemaining, frameCount);*/ + + resultALSA = ((ma_snd_pcm_writei_proc)pDevice->pContext->alsa.snd_pcm_writei)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback, pSrc, framesRemaining); + if (resultALSA < 0) { + if (resultALSA == -EAGAIN) { + /*printf("TRACE: EGAIN (write)\n");*/ + continue; /* Try again. */ + } else if (resultALSA == -EPIPE) { + /*printf("TRACE: EPIPE (write)\n");*/ + + /* Underrun. Recover and try again. If this fails we need to return an error. */ + if (((ma_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback, resultALSA, MA_TRUE) < 0) { /* MA_TRUE=silent (don't print anything on error). */ + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after underrun.", MA_FAILED_TO_START_BACKEND_DEVICE); + } + + /* + In my testing I have had a situation where writei() does not automatically restart the device even though I've set it + up as such in the software parameters. What will happen is writei() will block indefinitely even though the number of + frames is well beyond the auto-start threshold. To work around this I've needed to add an explicit start here. Not sure + if this is me just being stupid and not recovering the device properly, but this definitely feels like something isn't + quite right here. + */ + if (((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback) < 0) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start device after underrun.", MA_FAILED_TO_START_BACKEND_DEVICE); + } + + resultALSA = ((ma_snd_pcm_writei_proc)pDevice->pContext->alsa.snd_pcm_writei)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback, pSrc, framesRemaining); + if (resultALSA < 0) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to write data to device after underrun.", MA_FAILED_TO_START_BACKEND_DEVICE); + } + } + } + + totalPCMFramesProcessed += resultALSA; + } + + return MA_SUCCESS; +} + +ma_result ma_device_read__alsa(ma_device* pDevice, void* pPCMFrames, ma_uint32 frameCount) +{ + ma_snd_pcm_sframes_t resultALSA; + ma_uint32 totalPCMFramesProcessed; + + ma_assert(pDevice != NULL); + ma_assert(pPCMFrames != NULL); + + /* We need to explicitly start the device if it isn't already. */ + if (((ma_snd_pcm_state_proc)pDevice->pContext->alsa.snd_pcm_state)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture) != MA_SND_PCM_STATE_RUNNING) { + if (((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture) < 0) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start device in preparation for reading.", MA_FAILED_TO_START_BACKEND_DEVICE); + } + } + + totalPCMFramesProcessed = 0; + while (totalPCMFramesProcessed < frameCount) { + void* pDst = ma_offset_ptr(pPCMFrames, totalPCMFramesProcessed * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); + ma_uint32 framesRemaining = (frameCount - totalPCMFramesProcessed); + + /*printf("TRACE: snd_pcm_readi(framesRemaining=%d)\n", framesRemaining);*/ + + resultALSA = ((ma_snd_pcm_readi_proc)pDevice->pContext->alsa.snd_pcm_readi)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture, pDst, framesRemaining); + if (resultALSA < 0) { + if (resultALSA == -EAGAIN) { + /*printf("TRACE: EGAIN (read)\n");*/ + continue; + } else if (resultALSA == -EPIPE) { + /*printf("TRACE: EPIPE (read)\n");*/ + + /* Overrun. Recover and try again. If this fails we need to return an error. */ + if (((ma_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture, resultALSA, MA_TRUE) < 0) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after overrun.", MA_FAILED_TO_START_BACKEND_DEVICE); + } + + if (((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture) < 0) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start device after underrun.", MA_FAILED_TO_START_BACKEND_DEVICE); + } + + resultALSA = ((ma_snd_pcm_readi_proc)pDevice->pContext->alsa.snd_pcm_readi)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture, pDst, framesRemaining); + if (resultALSA < 0) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to read data from the internal device.", MA_FAILED_TO_READ_DATA_FROM_DEVICE); + } + } + } + + totalPCMFramesProcessed += resultALSA; + } + + return MA_SUCCESS; +} + +#if 0 +ma_result ma_device_break_main_loop__alsa(ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + pDevice->alsa.breakFromMainLoop = MA_TRUE; + return MA_SUCCESS; +} + +ma_result ma_device_main_loop__alsa(ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + pDevice->alsa.breakFromMainLoop = MA_FALSE; + if (pDevice->type == ma_device_type_playback) { + /* Playback. Read from client, write to device. */ + while (!pDevice->alsa.breakFromMainLoop && ma_device_read_from_client_and_write__alsa(pDevice)) { + } + } else { + /* Capture. Read from device, write to client. */ + while (!pDevice->alsa.breakFromMainLoop && ma_device_read_and_send_to_client__alsa(pDevice)) { + } + } + + return MA_SUCCESS; +} +#endif /* 0 */ + +ma_result ma_context_uninit__alsa(ma_context* pContext) +{ + ma_assert(pContext != NULL); + ma_assert(pContext->backend == ma_backend_alsa); + + /* Clean up memory for memory leak checkers. */ + ((ma_snd_config_update_free_global_proc)pContext->alsa.snd_config_update_free_global)(); + +#ifndef MA_NO_RUNTIME_LINKING + ma_dlclose(pContext, pContext->alsa.asoundSO); +#endif + + ma_mutex_uninit(&pContext->alsa.internalDeviceEnumLock); + + return MA_SUCCESS; +} + +ma_result ma_context_init__alsa(const ma_context_config* pConfig, ma_context* pContext) +{ +#ifndef MA_NO_RUNTIME_LINKING + const char* libasoundNames[] = { + "libasound.so.2", + "libasound.so" + }; + size_t i; + + for (i = 0; i < ma_countof(libasoundNames); ++i) { + pContext->alsa.asoundSO = ma_dlopen(pContext, libasoundNames[i]); + if (pContext->alsa.asoundSO != NULL) { + break; + } + } + + if (pContext->alsa.asoundSO == NULL) { +#ifdef MA_DEBUG_OUTPUT + printf("[ALSA] Failed to open shared object.\n"); +#endif + return MA_NO_BACKEND; + } + + pContext->alsa.snd_pcm_open = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_open"); + pContext->alsa.snd_pcm_close = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_close"); + pContext->alsa.snd_pcm_hw_params_sizeof = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_sizeof"); + pContext->alsa.snd_pcm_hw_params_any = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_any"); + 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_near = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_set_channels_near"); + 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_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"); + pContext->alsa.snd_pcm_hw_params_set_access = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_set_access"); + pContext->alsa.snd_pcm_hw_params_get_format = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_get_format"); + pContext->alsa.snd_pcm_hw_params_get_channels = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_get_channels"); + pContext->alsa.snd_pcm_hw_params_get_channels_min = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_get_channels_min"); + pContext->alsa.snd_pcm_hw_params_get_channels_max = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_get_channels_max"); + pContext->alsa.snd_pcm_hw_params_get_rate = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_get_rate"); + pContext->alsa.snd_pcm_hw_params_get_rate_min = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_get_rate_min"); + pContext->alsa.snd_pcm_hw_params_get_rate_max = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_get_rate_max"); + 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 = (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"); + pContext->alsa.snd_pcm_sw_params_get_boundary = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_sw_params_get_boundary"); + pContext->alsa.snd_pcm_sw_params_set_avail_min = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_sw_params_set_avail_min"); + pContext->alsa.snd_pcm_sw_params_set_start_threshold = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_sw_params_set_start_threshold"); + pContext->alsa.snd_pcm_sw_params_set_stop_threshold = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_sw_params_set_stop_threshold"); + pContext->alsa.snd_pcm_sw_params = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_sw_params"); + pContext->alsa.snd_pcm_format_mask_sizeof = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_format_mask_sizeof"); + pContext->alsa.snd_pcm_format_mask_test = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_format_mask_test"); + pContext->alsa.snd_pcm_get_chmap = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_get_chmap"); + pContext->alsa.snd_pcm_state = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_state"); + pContext->alsa.snd_pcm_prepare = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_prepare"); + pContext->alsa.snd_pcm_start = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_start"); + pContext->alsa.snd_pcm_drop = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_drop"); + pContext->alsa.snd_pcm_drain = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_drain"); + pContext->alsa.snd_device_name_hint = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_device_name_hint"); + pContext->alsa.snd_device_name_get_hint = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_device_name_get_hint"); + pContext->alsa.snd_card_get_index = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_card_get_index"); + pContext->alsa.snd_device_name_free_hint = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_device_name_free_hint"); + pContext->alsa.snd_pcm_mmap_begin = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_mmap_begin"); + pContext->alsa.snd_pcm_mmap_commit = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_mmap_commit"); + pContext->alsa.snd_pcm_recover = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_recover"); + pContext->alsa.snd_pcm_readi = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_readi"); + pContext->alsa.snd_pcm_writei = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_writei"); + pContext->alsa.snd_pcm_avail = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_avail"); + pContext->alsa.snd_pcm_avail_update = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_avail_update"); + pContext->alsa.snd_pcm_wait = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_wait"); + pContext->alsa.snd_pcm_info = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_info"); + pContext->alsa.snd_pcm_info_sizeof = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_info_sizeof"); + pContext->alsa.snd_pcm_info_get_name = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_info_get_name"); + pContext->alsa.snd_config_update_free_global = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_config_update_free_global"); +#else + /* The system below is just for type safety. */ + ma_snd_pcm_open_proc _snd_pcm_open = snd_pcm_open; + ma_snd_pcm_close_proc _snd_pcm_close = snd_pcm_close; + ma_snd_pcm_hw_params_sizeof_proc _snd_pcm_hw_params_sizeof = snd_pcm_hw_params_sizeof; + ma_snd_pcm_hw_params_any_proc _snd_pcm_hw_params_any = snd_pcm_hw_params_any; + 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_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_proc _snd_pcm_hw_params_set_rate_near = snd_pcm_hw_params_set_rate_near; + 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; + ma_snd_pcm_hw_params_get_format_proc _snd_pcm_hw_params_get_format = snd_pcm_hw_params_get_format; + ma_snd_pcm_hw_params_get_channels_proc _snd_pcm_hw_params_get_channels = snd_pcm_hw_params_get_channels; + ma_snd_pcm_hw_params_get_channels_min_proc _snd_pcm_hw_params_get_channels_min = snd_pcm_hw_params_get_channels_min; + ma_snd_pcm_hw_params_get_channels_max_proc _snd_pcm_hw_params_get_channels_max = snd_pcm_hw_params_get_channels_max; + ma_snd_pcm_hw_params_get_rate_proc _snd_pcm_hw_params_get_rate = snd_pcm_hw_params_get_rate; + ma_snd_pcm_hw_params_get_rate_min_proc _snd_pcm_hw_params_get_rate_min = snd_pcm_hw_params_get_rate_min; + ma_snd_pcm_hw_params_get_rate_max_proc _snd_pcm_hw_params_get_rate_max = snd_pcm_hw_params_get_rate_max; + 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_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; + ma_snd_pcm_sw_params_get_boundary_proc _snd_pcm_sw_params_get_boundary = snd_pcm_sw_params_get_boundary; + ma_snd_pcm_sw_params_set_avail_min_proc _snd_pcm_sw_params_set_avail_min = snd_pcm_sw_params_set_avail_min; + ma_snd_pcm_sw_params_set_start_threshold_proc _snd_pcm_sw_params_set_start_threshold = snd_pcm_sw_params_set_start_threshold; + ma_snd_pcm_sw_params_set_stop_threshold_proc _snd_pcm_sw_params_set_stop_threshold = snd_pcm_sw_params_set_stop_threshold; + ma_snd_pcm_sw_params_proc _snd_pcm_sw_params = snd_pcm_sw_params; + ma_snd_pcm_format_mask_sizeof_proc _snd_pcm_format_mask_sizeof = snd_pcm_format_mask_sizeof; + ma_snd_pcm_format_mask_test_proc _snd_pcm_format_mask_test = snd_pcm_format_mask_test; + ma_snd_pcm_get_chmap_proc _snd_pcm_get_chmap = snd_pcm_get_chmap; + ma_snd_pcm_state_proc _snd_pcm_state = snd_pcm_state; + ma_snd_pcm_prepare_proc _snd_pcm_prepare = snd_pcm_prepare; + ma_snd_pcm_start_proc _snd_pcm_start = snd_pcm_start; + ma_snd_pcm_drop_proc _snd_pcm_drop = snd_pcm_drop; + ma_snd_pcm_drain_proc _snd_pcm_drain = snd_pcm_drain; + ma_snd_device_name_hint_proc _snd_device_name_hint = snd_device_name_hint; + ma_snd_device_name_get_hint_proc _snd_device_name_get_hint = snd_device_name_get_hint; + ma_snd_card_get_index_proc _snd_card_get_index = snd_card_get_index; + ma_snd_device_name_free_hint_proc _snd_device_name_free_hint = snd_device_name_free_hint; + ma_snd_pcm_mmap_begin_proc _snd_pcm_mmap_begin = snd_pcm_mmap_begin; + ma_snd_pcm_mmap_commit_proc _snd_pcm_mmap_commit = snd_pcm_mmap_commit; + ma_snd_pcm_recover_proc _snd_pcm_recover = snd_pcm_recover; + ma_snd_pcm_readi_proc _snd_pcm_readi = snd_pcm_readi; + ma_snd_pcm_writei_proc _snd_pcm_writei = snd_pcm_writei; + ma_snd_pcm_avail_proc _snd_pcm_avail = snd_pcm_avail; + ma_snd_pcm_avail_update_proc _snd_pcm_avail_update = snd_pcm_avail_update; + ma_snd_pcm_wait_proc _snd_pcm_wait = snd_pcm_wait; + ma_snd_pcm_info_proc _snd_pcm_info = snd_pcm_info; + ma_snd_pcm_info_sizeof_proc _snd_pcm_info_sizeof = snd_pcm_info_sizeof; + ma_snd_pcm_info_get_name_proc _snd_pcm_info_get_name = snd_pcm_info_get_name; + ma_snd_config_update_free_global_proc _snd_config_update_free_global = snd_config_update_free_global; + + pContext->alsa.snd_pcm_open = (ma_proc)_snd_pcm_open; + pContext->alsa.snd_pcm_close = (ma_proc)_snd_pcm_close; + pContext->alsa.snd_pcm_hw_params_sizeof = (ma_proc)_snd_pcm_hw_params_sizeof; + pContext->alsa.snd_pcm_hw_params_any = (ma_proc)_snd_pcm_hw_params_any; + 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_near = (ma_proc)_snd_pcm_hw_params_set_channels_near; + 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_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; + pContext->alsa.snd_pcm_hw_params_set_access = (ma_proc)_snd_pcm_hw_params_set_access; + pContext->alsa.snd_pcm_hw_params_get_format = (ma_proc)_snd_pcm_hw_params_get_format; + pContext->alsa.snd_pcm_hw_params_get_channels = (ma_proc)_snd_pcm_hw_params_get_channels; + pContext->alsa.snd_pcm_hw_params_get_channels_min = (ma_proc)_snd_pcm_hw_params_get_channels_min; + pContext->alsa.snd_pcm_hw_params_get_channels_max = (ma_proc)_snd_pcm_hw_params_get_channels_max; + pContext->alsa.snd_pcm_hw_params_get_rate = (ma_proc)_snd_pcm_hw_params_get_rate; + 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 = (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; + pContext->alsa.snd_pcm_sw_params_get_boundary = (ma_proc)_snd_pcm_sw_params_get_boundary; + pContext->alsa.snd_pcm_sw_params_set_avail_min = (ma_proc)_snd_pcm_sw_params_set_avail_min; + pContext->alsa.snd_pcm_sw_params_set_start_threshold = (ma_proc)_snd_pcm_sw_params_set_start_threshold; + pContext->alsa.snd_pcm_sw_params_set_stop_threshold = (ma_proc)_snd_pcm_sw_params_set_stop_threshold; + pContext->alsa.snd_pcm_sw_params = (ma_proc)_snd_pcm_sw_params; + pContext->alsa.snd_pcm_format_mask_sizeof = (ma_proc)_snd_pcm_format_mask_sizeof; + pContext->alsa.snd_pcm_format_mask_test = (ma_proc)_snd_pcm_format_mask_test; + pContext->alsa.snd_pcm_get_chmap = (ma_proc)_snd_pcm_get_chmap; + pContext->alsa.snd_pcm_state = (ma_proc)_snd_pcm_state; + pContext->alsa.snd_pcm_prepare = (ma_proc)_snd_pcm_prepare; + pContext->alsa.snd_pcm_start = (ma_proc)_snd_pcm_start; + pContext->alsa.snd_pcm_drop = (ma_proc)_snd_pcm_drop; + pContext->alsa.snd_pcm_drain = (ma_proc)_snd_pcm_drain; + pContext->alsa.snd_device_name_hint = (ma_proc)_snd_device_name_hint; + pContext->alsa.snd_device_name_get_hint = (ma_proc)_snd_device_name_get_hint; + pContext->alsa.snd_card_get_index = (ma_proc)_snd_card_get_index; + pContext->alsa.snd_device_name_free_hint = (ma_proc)_snd_device_name_free_hint; + pContext->alsa.snd_pcm_mmap_begin = (ma_proc)_snd_pcm_mmap_begin; + pContext->alsa.snd_pcm_mmap_commit = (ma_proc)_snd_pcm_mmap_commit; + pContext->alsa.snd_pcm_recover = (ma_proc)_snd_pcm_recover; + pContext->alsa.snd_pcm_readi = (ma_proc)_snd_pcm_readi; + pContext->alsa.snd_pcm_writei = (ma_proc)_snd_pcm_writei; + pContext->alsa.snd_pcm_avail = (ma_proc)_snd_pcm_avail; + pContext->alsa.snd_pcm_avail_update = (ma_proc)_snd_pcm_avail_update; + pContext->alsa.snd_pcm_wait = (ma_proc)_snd_pcm_wait; + pContext->alsa.snd_pcm_info = (ma_proc)_snd_pcm_info; + pContext->alsa.snd_pcm_info_sizeof = (ma_proc)_snd_pcm_info_sizeof; + pContext->alsa.snd_pcm_info_get_name = (ma_proc)_snd_pcm_info_get_name; + pContext->alsa.snd_config_update_free_global = (ma_proc)_snd_config_update_free_global; +#endif + + pContext->alsa.useVerboseDeviceEnumeration = pConfig->alsa.useVerboseDeviceEnumeration; + + if (ma_mutex_init(pContext, &pContext->alsa.internalDeviceEnumLock) != MA_SUCCESS) { + 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; /*ma_device_start__alsa;*/ + pContext->onDeviceStop = ma_device_stop__alsa; + pContext->onDeviceWrite = ma_device_write__alsa; + pContext->onDeviceRead = ma_device_read__alsa; + + return MA_SUCCESS; +} +#endif /* ALSA */ + + + +/****************************************************************************** + +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). + +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. +*/ +#ifdef MA_NO_RUNTIME_LINKING +#include + +#define MA_PA_OK PA_OK +#define MA_PA_ERR_ACCESS PA_ERR_ACCESS +#define MA_PA_ERR_INVALID PA_ERR_INVALID +#define MA_PA_ERR_NOENTITY PA_ERR_NOENTITY + +#define MA_PA_CHANNELS_MAX PA_CHANNELS_MAX +#define MA_PA_RATE_MAX PA_RATE_MAX + +typedef pa_context_flags_t ma_pa_context_flags_t; +#define MA_PA_CONTEXT_NOFLAGS PA_CONTEXT_NOFLAGS +#define MA_PA_CONTEXT_NOAUTOSPAWN PA_CONTEXT_NOAUTOSPAWN +#define MA_PA_CONTEXT_NOFAIL PA_CONTEXT_NOFAIL + +typedef pa_stream_flags_t ma_pa_stream_flags_t; +#define MA_PA_STREAM_NOFLAGS PA_STREAM_NOFLAGS +#define MA_PA_STREAM_START_CORKED PA_STREAM_START_CORKED +#define MA_PA_STREAM_INTERPOLATE_TIMING PA_STREAM_INTERPOLATE_TIMING +#define MA_PA_STREAM_NOT_MONOTONIC PA_STREAM_NOT_MONOTONIC +#define MA_PA_STREAM_AUTO_TIMING_UPDATE PA_STREAM_AUTO_TIMING_UPDATE +#define MA_PA_STREAM_NO_REMAP_CHANNELS PA_STREAM_NO_REMAP_CHANNELS +#define MA_PA_STREAM_NO_REMIX_CHANNELS PA_STREAM_NO_REMIX_CHANNELS +#define MA_PA_STREAM_FIX_FORMAT PA_STREAM_FIX_FORMAT +#define MA_PA_STREAM_FIX_RATE PA_STREAM_FIX_RATE +#define MA_PA_STREAM_FIX_CHANNELS PA_STREAM_FIX_CHANNELS +#define MA_PA_STREAM_DONT_MOVE PA_STREAM_DONT_MOVE +#define MA_PA_STREAM_VARIABLE_RATE PA_STREAM_VARIABLE_RATE +#define MA_PA_STREAM_PEAK_DETECT PA_STREAM_PEAK_DETECT +#define MA_PA_STREAM_START_MUTED PA_STREAM_START_MUTED +#define MA_PA_STREAM_ADJUST_LATENCY PA_STREAM_ADJUST_LATENCY +#define MA_PA_STREAM_EARLY_REQUESTS PA_STREAM_EARLY_REQUESTS +#define MA_PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND +#define MA_PA_STREAM_START_UNMUTED PA_STREAM_START_UNMUTED +#define MA_PA_STREAM_FAIL_ON_SUSPEND PA_STREAM_FAIL_ON_SUSPEND +#define MA_PA_STREAM_RELATIVE_VOLUME PA_STREAM_RELATIVE_VOLUME +#define MA_PA_STREAM_PASSTHROUGH PA_STREAM_PASSTHROUGH + +typedef pa_sink_flags_t ma_pa_sink_flags_t; +#define MA_PA_SINK_NOFLAGS PA_SINK_NOFLAGS +#define MA_PA_SINK_HW_VOLUME_CTRL PA_SINK_HW_VOLUME_CTRL +#define MA_PA_SINK_LATENCY PA_SINK_LATENCY +#define MA_PA_SINK_HARDWARE PA_SINK_HARDWARE +#define MA_PA_SINK_NETWORK PA_SINK_NETWORK +#define MA_PA_SINK_HW_MUTE_CTRL PA_SINK_HW_MUTE_CTRL +#define MA_PA_SINK_DECIBEL_VOLUME PA_SINK_DECIBEL_VOLUME +#define MA_PA_SINK_FLAT_VOLUME PA_SINK_FLAT_VOLUME +#define MA_PA_SINK_DYNAMIC_LATENCY PA_SINK_DYNAMIC_LATENCY +#define MA_PA_SINK_SET_FORMATS PA_SINK_SET_FORMATS + +typedef pa_source_flags_t ma_pa_source_flags_t; +#define MA_PA_SOURCE_NOFLAGS PA_SOURCE_NOFLAGS +#define MA_PA_SOURCE_HW_VOLUME_CTRL PA_SOURCE_HW_VOLUME_CTRL +#define MA_PA_SOURCE_LATENCY PA_SOURCE_LATENCY +#define MA_PA_SOURCE_HARDWARE PA_SOURCE_HARDWARE +#define MA_PA_SOURCE_NETWORK PA_SOURCE_NETWORK +#define MA_PA_SOURCE_HW_MUTE_CTRL PA_SOURCE_HW_MUTE_CTRL +#define MA_PA_SOURCE_DECIBEL_VOLUME PA_SOURCE_DECIBEL_VOLUME +#define MA_PA_SOURCE_DYNAMIC_LATENCY PA_SOURCE_DYNAMIC_LATENCY +#define MA_PA_SOURCE_FLAT_VOLUME PA_SOURCE_FLAT_VOLUME + +typedef pa_context_state_t ma_pa_context_state_t; +#define MA_PA_CONTEXT_UNCONNECTED PA_CONTEXT_UNCONNECTED +#define MA_PA_CONTEXT_CONNECTING PA_CONTEXT_CONNECTING +#define MA_PA_CONTEXT_AUTHORIZING PA_CONTEXT_AUTHORIZING +#define MA_PA_CONTEXT_SETTING_NAME PA_CONTEXT_SETTING_NAME +#define MA_PA_CONTEXT_READY PA_CONTEXT_READY +#define MA_PA_CONTEXT_FAILED PA_CONTEXT_FAILED +#define MA_PA_CONTEXT_TERMINATED PA_CONTEXT_TERMINATED + +typedef pa_stream_state_t ma_pa_stream_state_t; +#define MA_PA_STREAM_UNCONNECTED PA_STREAM_UNCONNECTED +#define MA_PA_STREAM_CREATING PA_STREAM_CREATING +#define MA_PA_STREAM_READY PA_STREAM_READY +#define MA_PA_STREAM_FAILED PA_STREAM_FAILED +#define MA_PA_STREAM_TERMINATED PA_STREAM_TERMINATED + +typedef pa_operation_state_t ma_pa_operation_state_t; +#define MA_PA_OPERATION_RUNNING PA_OPERATION_RUNNING +#define MA_PA_OPERATION_DONE PA_OPERATION_DONE +#define MA_PA_OPERATION_CANCELLED PA_OPERATION_CANCELLED + +typedef pa_sink_state_t ma_pa_sink_state_t; +#define MA_PA_SINK_INVALID_STATE PA_SINK_INVALID_STATE +#define MA_PA_SINK_RUNNING PA_SINK_RUNNING +#define MA_PA_SINK_IDLE PA_SINK_IDLE +#define MA_PA_SINK_SUSPENDED PA_SINK_SUSPENDED + +typedef pa_source_state_t ma_pa_source_state_t; +#define MA_PA_SOURCE_INVALID_STATE PA_SOURCE_INVALID_STATE +#define MA_PA_SOURCE_RUNNING PA_SOURCE_RUNNING +#define MA_PA_SOURCE_IDLE PA_SOURCE_IDLE +#define MA_PA_SOURCE_SUSPENDED PA_SOURCE_SUSPENDED + +typedef pa_seek_mode_t ma_pa_seek_mode_t; +#define MA_PA_SEEK_RELATIVE PA_SEEK_RELATIVE +#define MA_PA_SEEK_ABSOLUTE PA_SEEK_ABSOLUTE +#define MA_PA_SEEK_RELATIVE_ON_READ PA_SEEK_RELATIVE_ON_READ +#define MA_PA_SEEK_RELATIVE_END PA_SEEK_RELATIVE_END + +typedef pa_channel_position_t ma_pa_channel_position_t; +#define MA_PA_CHANNEL_POSITION_INVALID PA_CHANNEL_POSITION_INVALID +#define MA_PA_CHANNEL_POSITION_MONO PA_CHANNEL_POSITION_MONO +#define MA_PA_CHANNEL_POSITION_FRONT_LEFT PA_CHANNEL_POSITION_FRONT_LEFT +#define MA_PA_CHANNEL_POSITION_FRONT_RIGHT PA_CHANNEL_POSITION_FRONT_RIGHT +#define MA_PA_CHANNEL_POSITION_FRONT_CENTER PA_CHANNEL_POSITION_FRONT_CENTER +#define MA_PA_CHANNEL_POSITION_REAR_CENTER PA_CHANNEL_POSITION_REAR_CENTER +#define MA_PA_CHANNEL_POSITION_REAR_LEFT PA_CHANNEL_POSITION_REAR_LEFT +#define MA_PA_CHANNEL_POSITION_REAR_RIGHT PA_CHANNEL_POSITION_REAR_RIGHT +#define MA_PA_CHANNEL_POSITION_LFE PA_CHANNEL_POSITION_LFE +#define MA_PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER +#define MA_PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER +#define MA_PA_CHANNEL_POSITION_SIDE_LEFT PA_CHANNEL_POSITION_SIDE_LEFT +#define MA_PA_CHANNEL_POSITION_SIDE_RIGHT PA_CHANNEL_POSITION_SIDE_RIGHT +#define MA_PA_CHANNEL_POSITION_AUX0 PA_CHANNEL_POSITION_AUX0 +#define MA_PA_CHANNEL_POSITION_AUX1 PA_CHANNEL_POSITION_AUX1 +#define MA_PA_CHANNEL_POSITION_AUX2 PA_CHANNEL_POSITION_AUX2 +#define MA_PA_CHANNEL_POSITION_AUX3 PA_CHANNEL_POSITION_AUX3 +#define MA_PA_CHANNEL_POSITION_AUX4 PA_CHANNEL_POSITION_AUX4 +#define MA_PA_CHANNEL_POSITION_AUX5 PA_CHANNEL_POSITION_AUX5 +#define MA_PA_CHANNEL_POSITION_AUX6 PA_CHANNEL_POSITION_AUX6 +#define MA_PA_CHANNEL_POSITION_AUX7 PA_CHANNEL_POSITION_AUX7 +#define MA_PA_CHANNEL_POSITION_AUX8 PA_CHANNEL_POSITION_AUX8 +#define MA_PA_CHANNEL_POSITION_AUX9 PA_CHANNEL_POSITION_AUX9 +#define MA_PA_CHANNEL_POSITION_AUX10 PA_CHANNEL_POSITION_AUX10 +#define MA_PA_CHANNEL_POSITION_AUX11 PA_CHANNEL_POSITION_AUX11 +#define MA_PA_CHANNEL_POSITION_AUX12 PA_CHANNEL_POSITION_AUX12 +#define MA_PA_CHANNEL_POSITION_AUX13 PA_CHANNEL_POSITION_AUX13 +#define MA_PA_CHANNEL_POSITION_AUX14 PA_CHANNEL_POSITION_AUX14 +#define MA_PA_CHANNEL_POSITION_AUX15 PA_CHANNEL_POSITION_AUX15 +#define MA_PA_CHANNEL_POSITION_AUX16 PA_CHANNEL_POSITION_AUX16 +#define MA_PA_CHANNEL_POSITION_AUX17 PA_CHANNEL_POSITION_AUX17 +#define MA_PA_CHANNEL_POSITION_AUX18 PA_CHANNEL_POSITION_AUX18 +#define MA_PA_CHANNEL_POSITION_AUX19 PA_CHANNEL_POSITION_AUX19 +#define MA_PA_CHANNEL_POSITION_AUX20 PA_CHANNEL_POSITION_AUX20 +#define MA_PA_CHANNEL_POSITION_AUX21 PA_CHANNEL_POSITION_AUX21 +#define MA_PA_CHANNEL_POSITION_AUX22 PA_CHANNEL_POSITION_AUX22 +#define MA_PA_CHANNEL_POSITION_AUX23 PA_CHANNEL_POSITION_AUX23 +#define MA_PA_CHANNEL_POSITION_AUX24 PA_CHANNEL_POSITION_AUX24 +#define MA_PA_CHANNEL_POSITION_AUX25 PA_CHANNEL_POSITION_AUX25 +#define MA_PA_CHANNEL_POSITION_AUX26 PA_CHANNEL_POSITION_AUX26 +#define MA_PA_CHANNEL_POSITION_AUX27 PA_CHANNEL_POSITION_AUX27 +#define MA_PA_CHANNEL_POSITION_AUX28 PA_CHANNEL_POSITION_AUX28 +#define MA_PA_CHANNEL_POSITION_AUX29 PA_CHANNEL_POSITION_AUX29 +#define MA_PA_CHANNEL_POSITION_AUX30 PA_CHANNEL_POSITION_AUX30 +#define MA_PA_CHANNEL_POSITION_AUX31 PA_CHANNEL_POSITION_AUX31 +#define MA_PA_CHANNEL_POSITION_TOP_CENTER PA_CHANNEL_POSITION_TOP_CENTER +#define MA_PA_CHANNEL_POSITION_TOP_FRONT_LEFT PA_CHANNEL_POSITION_TOP_FRONT_LEFT +#define MA_PA_CHANNEL_POSITION_TOP_FRONT_RIGHT PA_CHANNEL_POSITION_TOP_FRONT_RIGHT +#define MA_PA_CHANNEL_POSITION_TOP_FRONT_CENTER PA_CHANNEL_POSITION_TOP_FRONT_CENTER +#define MA_PA_CHANNEL_POSITION_TOP_REAR_LEFT PA_CHANNEL_POSITION_TOP_REAR_LEFT +#define MA_PA_CHANNEL_POSITION_TOP_REAR_RIGHT PA_CHANNEL_POSITION_TOP_REAR_RIGHT +#define MA_PA_CHANNEL_POSITION_TOP_REAR_CENTER PA_CHANNEL_POSITION_TOP_REAR_CENTER +#define MA_PA_CHANNEL_POSITION_LEFT PA_CHANNEL_POSITION_LEFT +#define MA_PA_CHANNEL_POSITION_RIGHT PA_CHANNEL_POSITION_RIGHT +#define MA_PA_CHANNEL_POSITION_CENTER PA_CHANNEL_POSITION_CENTER +#define MA_PA_CHANNEL_POSITION_SUBWOOFER PA_CHANNEL_POSITION_SUBWOOFER + +typedef pa_channel_map_def_t ma_pa_channel_map_def_t; +#define MA_PA_CHANNEL_MAP_AIFF PA_CHANNEL_MAP_AIFF +#define MA_PA_CHANNEL_MAP_ALSA PA_CHANNEL_MAP_ALSA +#define MA_PA_CHANNEL_MAP_AUX PA_CHANNEL_MAP_AUX +#define MA_PA_CHANNEL_MAP_WAVEEX PA_CHANNEL_MAP_WAVEEX +#define MA_PA_CHANNEL_MAP_OSS PA_CHANNEL_MAP_OSS +#define MA_PA_CHANNEL_MAP_DEFAULT PA_CHANNEL_MAP_DEFAULT + +typedef pa_sample_format_t ma_pa_sample_format_t; +#define MA_PA_SAMPLE_INVALID PA_SAMPLE_INVALID +#define MA_PA_SAMPLE_U8 PA_SAMPLE_U8 +#define MA_PA_SAMPLE_ALAW PA_SAMPLE_ALAW +#define MA_PA_SAMPLE_ULAW PA_SAMPLE_ULAW +#define MA_PA_SAMPLE_S16LE PA_SAMPLE_S16LE +#define MA_PA_SAMPLE_S16BE PA_SAMPLE_S16BE +#define MA_PA_SAMPLE_FLOAT32LE PA_SAMPLE_FLOAT32LE +#define MA_PA_SAMPLE_FLOAT32BE PA_SAMPLE_FLOAT32BE +#define MA_PA_SAMPLE_S32LE PA_SAMPLE_S32LE +#define MA_PA_SAMPLE_S32BE PA_SAMPLE_S32BE +#define MA_PA_SAMPLE_S24LE PA_SAMPLE_S24LE +#define MA_PA_SAMPLE_S24BE PA_SAMPLE_S24BE +#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_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 +#define MA_PA_ERR_INVALID 2 +#define MA_PA_ERR_NOENTITY 5 + +#define MA_PA_CHANNELS_MAX 32 +#define MA_PA_RATE_MAX 384000 + +typedef int ma_pa_context_flags_t; +#define MA_PA_CONTEXT_NOFLAGS 0x00000000 +#define MA_PA_CONTEXT_NOAUTOSPAWN 0x00000001 +#define MA_PA_CONTEXT_NOFAIL 0x00000002 + +typedef int ma_pa_stream_flags_t; +#define MA_PA_STREAM_NOFLAGS 0x00000000 +#define MA_PA_STREAM_START_CORKED 0x00000001 +#define MA_PA_STREAM_INTERPOLATE_TIMING 0x00000002 +#define MA_PA_STREAM_NOT_MONOTONIC 0x00000004 +#define MA_PA_STREAM_AUTO_TIMING_UPDATE 0x00000008 +#define MA_PA_STREAM_NO_REMAP_CHANNELS 0x00000010 +#define MA_PA_STREAM_NO_REMIX_CHANNELS 0x00000020 +#define MA_PA_STREAM_FIX_FORMAT 0x00000040 +#define MA_PA_STREAM_FIX_RATE 0x00000080 +#define MA_PA_STREAM_FIX_CHANNELS 0x00000100 +#define MA_PA_STREAM_DONT_MOVE 0x00000200 +#define MA_PA_STREAM_VARIABLE_RATE 0x00000400 +#define MA_PA_STREAM_PEAK_DETECT 0x00000800 +#define MA_PA_STREAM_START_MUTED 0x00001000 +#define MA_PA_STREAM_ADJUST_LATENCY 0x00002000 +#define MA_PA_STREAM_EARLY_REQUESTS 0x00004000 +#define MA_PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND 0x00008000 +#define MA_PA_STREAM_START_UNMUTED 0x00010000 +#define MA_PA_STREAM_FAIL_ON_SUSPEND 0x00020000 +#define MA_PA_STREAM_RELATIVE_VOLUME 0x00040000 +#define MA_PA_STREAM_PASSTHROUGH 0x00080000 + +typedef int ma_pa_sink_flags_t; +#define MA_PA_SINK_NOFLAGS 0x00000000 +#define MA_PA_SINK_HW_VOLUME_CTRL 0x00000001 +#define MA_PA_SINK_LATENCY 0x00000002 +#define MA_PA_SINK_HARDWARE 0x00000004 +#define MA_PA_SINK_NETWORK 0x00000008 +#define MA_PA_SINK_HW_MUTE_CTRL 0x00000010 +#define MA_PA_SINK_DECIBEL_VOLUME 0x00000020 +#define MA_PA_SINK_FLAT_VOLUME 0x00000040 +#define MA_PA_SINK_DYNAMIC_LATENCY 0x00000080 +#define MA_PA_SINK_SET_FORMATS 0x00000100 + +typedef int ma_pa_source_flags_t; +#define MA_PA_SOURCE_NOFLAGS 0x00000000 +#define MA_PA_SOURCE_HW_VOLUME_CTRL 0x00000001 +#define MA_PA_SOURCE_LATENCY 0x00000002 +#define MA_PA_SOURCE_HARDWARE 0x00000004 +#define MA_PA_SOURCE_NETWORK 0x00000008 +#define MA_PA_SOURCE_HW_MUTE_CTRL 0x00000010 +#define MA_PA_SOURCE_DECIBEL_VOLUME 0x00000020 +#define MA_PA_SOURCE_DYNAMIC_LATENCY 0x00000040 +#define MA_PA_SOURCE_FLAT_VOLUME 0x00000080 + +typedef int ma_pa_context_state_t; +#define MA_PA_CONTEXT_UNCONNECTED 0 +#define MA_PA_CONTEXT_CONNECTING 1 +#define MA_PA_CONTEXT_AUTHORIZING 2 +#define MA_PA_CONTEXT_SETTING_NAME 3 +#define MA_PA_CONTEXT_READY 4 +#define MA_PA_CONTEXT_FAILED 5 +#define MA_PA_CONTEXT_TERMINATED 6 + +typedef int ma_pa_stream_state_t; +#define MA_PA_STREAM_UNCONNECTED 0 +#define MA_PA_STREAM_CREATING 1 +#define MA_PA_STREAM_READY 2 +#define MA_PA_STREAM_FAILED 3 +#define MA_PA_STREAM_TERMINATED 4 + +typedef int ma_pa_operation_state_t; +#define MA_PA_OPERATION_RUNNING 0 +#define MA_PA_OPERATION_DONE 1 +#define MA_PA_OPERATION_CANCELLED 2 + +typedef int ma_pa_sink_state_t; +#define MA_PA_SINK_INVALID_STATE -1 +#define MA_PA_SINK_RUNNING 0 +#define MA_PA_SINK_IDLE 1 +#define MA_PA_SINK_SUSPENDED 2 + +typedef int ma_pa_source_state_t; +#define MA_PA_SOURCE_INVALID_STATE -1 +#define MA_PA_SOURCE_RUNNING 0 +#define MA_PA_SOURCE_IDLE 1 +#define MA_PA_SOURCE_SUSPENDED 2 + +typedef int ma_pa_seek_mode_t; +#define MA_PA_SEEK_RELATIVE 0 +#define MA_PA_SEEK_ABSOLUTE 1 +#define MA_PA_SEEK_RELATIVE_ON_READ 2 +#define MA_PA_SEEK_RELATIVE_END 3 + +typedef int ma_pa_channel_position_t; +#define MA_PA_CHANNEL_POSITION_INVALID -1 +#define MA_PA_CHANNEL_POSITION_MONO 0 +#define MA_PA_CHANNEL_POSITION_FRONT_LEFT 1 +#define MA_PA_CHANNEL_POSITION_FRONT_RIGHT 2 +#define MA_PA_CHANNEL_POSITION_FRONT_CENTER 3 +#define MA_PA_CHANNEL_POSITION_REAR_CENTER 4 +#define MA_PA_CHANNEL_POSITION_REAR_LEFT 5 +#define MA_PA_CHANNEL_POSITION_REAR_RIGHT 6 +#define MA_PA_CHANNEL_POSITION_LFE 7 +#define MA_PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER 8 +#define MA_PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER 9 +#define MA_PA_CHANNEL_POSITION_SIDE_LEFT 10 +#define MA_PA_CHANNEL_POSITION_SIDE_RIGHT 11 +#define MA_PA_CHANNEL_POSITION_AUX0 12 +#define MA_PA_CHANNEL_POSITION_AUX1 13 +#define MA_PA_CHANNEL_POSITION_AUX2 14 +#define MA_PA_CHANNEL_POSITION_AUX3 15 +#define MA_PA_CHANNEL_POSITION_AUX4 16 +#define MA_PA_CHANNEL_POSITION_AUX5 17 +#define MA_PA_CHANNEL_POSITION_AUX6 18 +#define MA_PA_CHANNEL_POSITION_AUX7 19 +#define MA_PA_CHANNEL_POSITION_AUX8 20 +#define MA_PA_CHANNEL_POSITION_AUX9 21 +#define MA_PA_CHANNEL_POSITION_AUX10 22 +#define MA_PA_CHANNEL_POSITION_AUX11 23 +#define MA_PA_CHANNEL_POSITION_AUX12 24 +#define MA_PA_CHANNEL_POSITION_AUX13 25 +#define MA_PA_CHANNEL_POSITION_AUX14 26 +#define MA_PA_CHANNEL_POSITION_AUX15 27 +#define MA_PA_CHANNEL_POSITION_AUX16 28 +#define MA_PA_CHANNEL_POSITION_AUX17 29 +#define MA_PA_CHANNEL_POSITION_AUX18 30 +#define MA_PA_CHANNEL_POSITION_AUX19 31 +#define MA_PA_CHANNEL_POSITION_AUX20 32 +#define MA_PA_CHANNEL_POSITION_AUX21 33 +#define MA_PA_CHANNEL_POSITION_AUX22 34 +#define MA_PA_CHANNEL_POSITION_AUX23 35 +#define MA_PA_CHANNEL_POSITION_AUX24 36 +#define MA_PA_CHANNEL_POSITION_AUX25 37 +#define MA_PA_CHANNEL_POSITION_AUX26 38 +#define MA_PA_CHANNEL_POSITION_AUX27 39 +#define MA_PA_CHANNEL_POSITION_AUX28 40 +#define MA_PA_CHANNEL_POSITION_AUX29 41 +#define MA_PA_CHANNEL_POSITION_AUX30 42 +#define MA_PA_CHANNEL_POSITION_AUX31 43 +#define MA_PA_CHANNEL_POSITION_TOP_CENTER 44 +#define MA_PA_CHANNEL_POSITION_TOP_FRONT_LEFT 45 +#define MA_PA_CHANNEL_POSITION_TOP_FRONT_RIGHT 46 +#define MA_PA_CHANNEL_POSITION_TOP_FRONT_CENTER 47 +#define MA_PA_CHANNEL_POSITION_TOP_REAR_LEFT 48 +#define MA_PA_CHANNEL_POSITION_TOP_REAR_RIGHT 49 +#define MA_PA_CHANNEL_POSITION_TOP_REAR_CENTER 50 +#define MA_PA_CHANNEL_POSITION_LEFT MA_PA_CHANNEL_POSITION_FRONT_LEFT +#define MA_PA_CHANNEL_POSITION_RIGHT MA_PA_CHANNEL_POSITION_FRONT_RIGHT +#define MA_PA_CHANNEL_POSITION_CENTER MA_PA_CHANNEL_POSITION_FRONT_CENTER +#define MA_PA_CHANNEL_POSITION_SUBWOOFER MA_PA_CHANNEL_POSITION_LFE + +typedef int ma_pa_channel_map_def_t; +#define MA_PA_CHANNEL_MAP_AIFF 0 +#define MA_PA_CHANNEL_MAP_ALSA 1 +#define MA_PA_CHANNEL_MAP_AUX 2 +#define MA_PA_CHANNEL_MAP_WAVEEX 3 +#define MA_PA_CHANNEL_MAP_OSS 4 +#define MA_PA_CHANNEL_MAP_DEFAULT MA_PA_CHANNEL_MAP_AIFF + +typedef int ma_pa_sample_format_t; +#define MA_PA_SAMPLE_INVALID -1 +#define MA_PA_SAMPLE_U8 0 +#define MA_PA_SAMPLE_ALAW 1 +#define MA_PA_SAMPLE_ULAW 2 +#define MA_PA_SAMPLE_S16LE 3 +#define MA_PA_SAMPLE_S16BE 4 +#define MA_PA_SAMPLE_FLOAT32LE 5 +#define MA_PA_SAMPLE_FLOAT32BE 6 +#define MA_PA_SAMPLE_S32LE 7 +#define MA_PA_SAMPLE_S32BE 8 +#define MA_PA_SAMPLE_S24LE 9 +#define MA_PA_SAMPLE_S24BE 10 +#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_uint32 maxlength; + ma_uint32 tlength; + ma_uint32 prebuf; + ma_uint32 minreq; + ma_uint32 fragsize; +} ma_pa_buffer_attr; + +typedef struct +{ + ma_uint8 channels; + ma_pa_channel_position_t map[MA_PA_CHANNELS_MAX]; +} ma_pa_channel_map; + +typedef struct +{ + ma_uint8 channels; + ma_uint32 values[MA_PA_CHANNELS_MAX]; +} ma_pa_cvolume; + +typedef struct +{ + ma_pa_sample_format_t format; + ma_uint32 rate; + ma_uint8 channels; +} ma_pa_sample_spec; + +typedef struct +{ + const char* name; + ma_uint32 index; + const char* description; + ma_pa_sample_spec sample_spec; + ma_pa_channel_map channel_map; + ma_uint32 owner_module; + ma_pa_cvolume volume; + int mute; + ma_uint32 monitor_source; + const char* monitor_source_name; + ma_uint64 latency; + const char* driver; + ma_pa_sink_flags_t flags; + void* proplist; + ma_uint64 configured_latency; + ma_uint32 base_volume; + ma_pa_sink_state_t state; + ma_uint32 n_volume_steps; + ma_uint32 card; + ma_uint32 n_ports; + void** ports; + void* active_port; + ma_uint8 n_formats; + void** formats; +} ma_pa_sink_info; + +typedef struct +{ + const char *name; + ma_uint32 index; + const char *description; + ma_pa_sample_spec sample_spec; + ma_pa_channel_map channel_map; + ma_uint32 owner_module; + ma_pa_cvolume volume; + int mute; + ma_uint32 monitor_of_sink; + const char *monitor_of_sink_name; + ma_uint64 latency; + const char *driver; + ma_pa_source_flags_t flags; + void* proplist; + ma_uint64 configured_latency; + ma_uint32 base_volume; + ma_pa_source_state_t state; + ma_uint32 n_volume_steps; + ma_uint32 card; + ma_uint32 n_ports; + void** ports; + void* active_port; + ma_uint8 n_formats; + void** formats; +} ma_pa_source_info; + +typedef void (* ma_pa_context_notify_cb_t)(ma_pa_context* c, void* userdata); +typedef void (* ma_pa_sink_info_cb_t) (ma_pa_context* c, const ma_pa_sink_info* i, int eol, void* userdata); +typedef void (* ma_pa_source_info_cb_t) (ma_pa_context* c, const ma_pa_source_info* i, int eol, void* userdata); +typedef void (* ma_pa_stream_success_cb_t)(ma_pa_stream* s, int success, void* userdata); +typedef void (* ma_pa_stream_request_cb_t)(ma_pa_stream* s, size_t nbytes, void* userdata); +typedef void (* ma_pa_free_cb_t) (void* p); +#endif + + +typedef ma_pa_mainloop* (* ma_pa_mainloop_new_proc) (); +typedef void (* ma_pa_mainloop_free_proc) (ma_pa_mainloop* m); +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_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); +typedef void (* ma_pa_context_disconnect_proc) (ma_pa_context* c); +typedef void (* ma_pa_context_set_state_callback_proc) (ma_pa_context* c, ma_pa_context_notify_cb_t cb, void* userdata); +typedef ma_pa_context_state_t (* ma_pa_context_get_state_proc) (ma_pa_context* c); +typedef ma_pa_operation* (* ma_pa_context_get_sink_info_list_proc) (ma_pa_context* c, ma_pa_sink_info_cb_t cb, void* userdata); +typedef ma_pa_operation* (* ma_pa_context_get_source_info_list_proc) (ma_pa_context* c, ma_pa_source_info_cb_t cb, void* userdata); +typedef ma_pa_operation* (* ma_pa_context_get_sink_info_by_name_proc) (ma_pa_context* c, const char* name, ma_pa_sink_info_cb_t cb, void* userdata); +typedef ma_pa_operation* (* ma_pa_context_get_source_info_by_name_proc)(ma_pa_context* c, const char* name, ma_pa_source_info_cb_t cb, void* userdata); +typedef void (* ma_pa_operation_unref_proc) (ma_pa_operation* o); +typedef ma_pa_operation_state_t (* ma_pa_operation_get_state_proc) (ma_pa_operation* o); +typedef ma_pa_channel_map* (* ma_pa_channel_map_init_extend_proc) (ma_pa_channel_map* m, unsigned channels, ma_pa_channel_map_def_t def); +typedef int (* ma_pa_channel_map_valid_proc) (const ma_pa_channel_map* m); +typedef int (* ma_pa_channel_map_compatible_proc) (const ma_pa_channel_map* m, const ma_pa_sample_spec* ss); +typedef ma_pa_stream* (* ma_pa_stream_new_proc) (ma_pa_context* c, const char* name, const ma_pa_sample_spec* ss, const ma_pa_channel_map* map); +typedef void (* ma_pa_stream_unref_proc) (ma_pa_stream* s); +typedef int (* ma_pa_stream_connect_playback_proc) (ma_pa_stream* s, const char* dev, const ma_pa_buffer_attr* attr, ma_pa_stream_flags_t flags, const ma_pa_cvolume* volume, ma_pa_stream* sync_stream); +typedef int (* ma_pa_stream_connect_record_proc) (ma_pa_stream* s, const char* dev, const ma_pa_buffer_attr* attr, ma_pa_stream_flags_t flags); +typedef int (* ma_pa_stream_disconnect_proc) (ma_pa_stream* s); +typedef ma_pa_stream_state_t (* ma_pa_stream_get_state_proc) (ma_pa_stream* s); +typedef const ma_pa_sample_spec* (* ma_pa_stream_get_sample_spec_proc) (ma_pa_stream* s); +typedef const ma_pa_channel_map* (* ma_pa_stream_get_channel_map_proc) (ma_pa_stream* s); +typedef const ma_pa_buffer_attr* (* ma_pa_stream_get_buffer_attr_proc) (ma_pa_stream* s); +typedef ma_pa_operation* (* ma_pa_stream_set_buffer_attr_proc) (ma_pa_stream* s, const ma_pa_buffer_attr* attr, ma_pa_stream_success_cb_t cb, void* userdata); +typedef const char* (* ma_pa_stream_get_device_name_proc) (ma_pa_stream* s); +typedef void (* ma_pa_stream_set_write_callback_proc) (ma_pa_stream* s, ma_pa_stream_request_cb_t cb, void* userdata); +typedef void (* ma_pa_stream_set_read_callback_proc) (ma_pa_stream* s, ma_pa_stream_request_cb_t cb, void* userdata); +typedef ma_pa_operation* (* ma_pa_stream_flush_proc) (ma_pa_stream* s, ma_pa_stream_success_cb_t cb, void* userdata); +typedef ma_pa_operation* (* ma_pa_stream_drain_proc) (ma_pa_stream* s, ma_pa_stream_success_cb_t cb, void* userdata); +typedef int (* ma_pa_stream_is_corked_proc) (ma_pa_stream* s); +typedef ma_pa_operation* (* ma_pa_stream_cork_proc) (ma_pa_stream* s, int b, ma_pa_stream_success_cb_t cb, void* userdata); +typedef ma_pa_operation* (* ma_pa_stream_trigger_proc) (ma_pa_stream* s, ma_pa_stream_success_cb_t cb, void* userdata); +typedef int (* ma_pa_stream_begin_write_proc) (ma_pa_stream* s, void** data, size_t* nbytes); +typedef int (* ma_pa_stream_write_proc) (ma_pa_stream* s, const void* data, size_t nbytes, ma_pa_free_cb_t free_cb, int64_t offset, ma_pa_seek_mode_t seek); +typedef int (* ma_pa_stream_peek_proc) (ma_pa_stream* s, const void** data, size_t* nbytes); +typedef int (* ma_pa_stream_drop_proc) (ma_pa_stream* s); +typedef size_t (* ma_pa_stream_writable_size_proc) (ma_pa_stream* s); +typedef size_t (* ma_pa_stream_readable_size_proc) (ma_pa_stream* s); + +typedef struct +{ + ma_uint32 count; + ma_uint32 capacity; + ma_device_info* pInfo; +} ma_pulse_device_enum_data; + +ma_result ma_result_from_pulse(int result) +{ + 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; + } +} + +#if 0 +ma_pa_sample_format_t ma_format_to_pulse(ma_format format) +{ + if (ma_is_little_endian()) { + switch (format) { + case ma_format_s16: return MA_PA_SAMPLE_S16LE; + case ma_format_s24: return MA_PA_SAMPLE_S24LE; + case ma_format_s32: return MA_PA_SAMPLE_S32LE; + case ma_format_f32: return MA_PA_SAMPLE_FLOAT32LE; + default: break; + } + } else { + switch (format) { + case ma_format_s16: return MA_PA_SAMPLE_S16BE; + case ma_format_s24: return MA_PA_SAMPLE_S24BE; + case ma_format_s32: return MA_PA_SAMPLE_S32BE; + case ma_format_f32: return MA_PA_SAMPLE_FLOAT32BE; + default: break; + } + } + + /* Endian agnostic. */ + switch (format) { + case ma_format_u8: return MA_PA_SAMPLE_U8; + default: return MA_PA_SAMPLE_INVALID; + } +} +#endif + +ma_format ma_format_from_pulse(ma_pa_sample_format_t format) +{ + if (ma_is_little_endian()) { + switch (format) { + case MA_PA_SAMPLE_S16LE: return ma_format_s16; + case MA_PA_SAMPLE_S24LE: return ma_format_s24; + case MA_PA_SAMPLE_S32LE: return ma_format_s32; + case MA_PA_SAMPLE_FLOAT32LE: return ma_format_f32; + default: break; + } + } else { + switch (format) { + case MA_PA_SAMPLE_S16BE: return ma_format_s16; + case MA_PA_SAMPLE_S24BE: return ma_format_s24; + case MA_PA_SAMPLE_S32BE: return ma_format_s32; + case MA_PA_SAMPLE_FLOAT32BE: return ma_format_f32; + default: break; + } + } + + /* Endian agnostic. */ + switch (format) { + case MA_PA_SAMPLE_U8: return ma_format_u8; + default: return ma_format_unknown; + } +} + +ma_channel ma_channel_position_from_pulse(ma_pa_channel_position_t position) +{ + switch (position) + { + case MA_PA_CHANNEL_POSITION_INVALID: return MA_CHANNEL_NONE; + case MA_PA_CHANNEL_POSITION_MONO: return MA_CHANNEL_MONO; + case MA_PA_CHANNEL_POSITION_FRONT_LEFT: return MA_CHANNEL_FRONT_LEFT; + case MA_PA_CHANNEL_POSITION_FRONT_RIGHT: return MA_CHANNEL_FRONT_RIGHT; + case MA_PA_CHANNEL_POSITION_FRONT_CENTER: return MA_CHANNEL_FRONT_CENTER; + case MA_PA_CHANNEL_POSITION_REAR_CENTER: return MA_CHANNEL_BACK_CENTER; + case MA_PA_CHANNEL_POSITION_REAR_LEFT: return MA_CHANNEL_BACK_LEFT; + case MA_PA_CHANNEL_POSITION_REAR_RIGHT: return MA_CHANNEL_BACK_RIGHT; + case MA_PA_CHANNEL_POSITION_LFE: return MA_CHANNEL_LFE; + case MA_PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: return MA_CHANNEL_FRONT_LEFT_CENTER; + case MA_PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: return MA_CHANNEL_FRONT_RIGHT_CENTER; + case MA_PA_CHANNEL_POSITION_SIDE_LEFT: return MA_CHANNEL_SIDE_LEFT; + case MA_PA_CHANNEL_POSITION_SIDE_RIGHT: return MA_CHANNEL_SIDE_RIGHT; + case MA_PA_CHANNEL_POSITION_AUX0: return MA_CHANNEL_AUX_0; + case MA_PA_CHANNEL_POSITION_AUX1: return MA_CHANNEL_AUX_1; + case MA_PA_CHANNEL_POSITION_AUX2: return MA_CHANNEL_AUX_2; + case MA_PA_CHANNEL_POSITION_AUX3: return MA_CHANNEL_AUX_3; + case MA_PA_CHANNEL_POSITION_AUX4: return MA_CHANNEL_AUX_4; + case MA_PA_CHANNEL_POSITION_AUX5: return MA_CHANNEL_AUX_5; + case MA_PA_CHANNEL_POSITION_AUX6: return MA_CHANNEL_AUX_6; + case MA_PA_CHANNEL_POSITION_AUX7: return MA_CHANNEL_AUX_7; + case MA_PA_CHANNEL_POSITION_AUX8: return MA_CHANNEL_AUX_8; + case MA_PA_CHANNEL_POSITION_AUX9: return MA_CHANNEL_AUX_9; + case MA_PA_CHANNEL_POSITION_AUX10: return MA_CHANNEL_AUX_10; + case MA_PA_CHANNEL_POSITION_AUX11: return MA_CHANNEL_AUX_11; + case MA_PA_CHANNEL_POSITION_AUX12: return MA_CHANNEL_AUX_12; + case MA_PA_CHANNEL_POSITION_AUX13: return MA_CHANNEL_AUX_13; + case MA_PA_CHANNEL_POSITION_AUX14: return MA_CHANNEL_AUX_14; + case MA_PA_CHANNEL_POSITION_AUX15: return MA_CHANNEL_AUX_15; + case MA_PA_CHANNEL_POSITION_AUX16: return MA_CHANNEL_AUX_16; + case MA_PA_CHANNEL_POSITION_AUX17: return MA_CHANNEL_AUX_17; + case MA_PA_CHANNEL_POSITION_AUX18: return MA_CHANNEL_AUX_18; + case MA_PA_CHANNEL_POSITION_AUX19: return MA_CHANNEL_AUX_19; + case MA_PA_CHANNEL_POSITION_AUX20: return MA_CHANNEL_AUX_20; + case MA_PA_CHANNEL_POSITION_AUX21: return MA_CHANNEL_AUX_21; + case MA_PA_CHANNEL_POSITION_AUX22: return MA_CHANNEL_AUX_22; + case MA_PA_CHANNEL_POSITION_AUX23: return MA_CHANNEL_AUX_23; + case MA_PA_CHANNEL_POSITION_AUX24: return MA_CHANNEL_AUX_24; + case MA_PA_CHANNEL_POSITION_AUX25: return MA_CHANNEL_AUX_25; + case MA_PA_CHANNEL_POSITION_AUX26: return MA_CHANNEL_AUX_26; + case MA_PA_CHANNEL_POSITION_AUX27: return MA_CHANNEL_AUX_27; + case MA_PA_CHANNEL_POSITION_AUX28: return MA_CHANNEL_AUX_28; + case MA_PA_CHANNEL_POSITION_AUX29: return MA_CHANNEL_AUX_29; + case MA_PA_CHANNEL_POSITION_AUX30: return MA_CHANNEL_AUX_30; + case MA_PA_CHANNEL_POSITION_AUX31: return MA_CHANNEL_AUX_31; + case MA_PA_CHANNEL_POSITION_TOP_CENTER: return MA_CHANNEL_TOP_CENTER; + case MA_PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return MA_CHANNEL_TOP_FRONT_LEFT; + case MA_PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return MA_CHANNEL_TOP_FRONT_RIGHT; + case MA_PA_CHANNEL_POSITION_TOP_FRONT_CENTER: return MA_CHANNEL_TOP_FRONT_CENTER; + case MA_PA_CHANNEL_POSITION_TOP_REAR_LEFT: return MA_CHANNEL_TOP_BACK_LEFT; + case MA_PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return MA_CHANNEL_TOP_BACK_RIGHT; + case MA_PA_CHANNEL_POSITION_TOP_REAR_CENTER: return MA_CHANNEL_TOP_BACK_CENTER; + default: return MA_CHANNEL_NONE; + } +} + +#if 0 +ma_pa_channel_position_t ma_channel_position_to_pulse(ma_channel position) +{ + switch (position) + { + case MA_CHANNEL_NONE: return MA_PA_CHANNEL_POSITION_INVALID; + case MA_CHANNEL_FRONT_LEFT: return MA_PA_CHANNEL_POSITION_FRONT_LEFT; + case MA_CHANNEL_FRONT_RIGHT: return MA_PA_CHANNEL_POSITION_FRONT_RIGHT; + case MA_CHANNEL_FRONT_CENTER: return MA_PA_CHANNEL_POSITION_FRONT_CENTER; + case MA_CHANNEL_LFE: return MA_PA_CHANNEL_POSITION_LFE; + case MA_CHANNEL_BACK_LEFT: return MA_PA_CHANNEL_POSITION_REAR_LEFT; + case MA_CHANNEL_BACK_RIGHT: return MA_PA_CHANNEL_POSITION_REAR_RIGHT; + case MA_CHANNEL_FRONT_LEFT_CENTER: return MA_PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; + case MA_CHANNEL_FRONT_RIGHT_CENTER: return MA_PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; + case MA_CHANNEL_BACK_CENTER: return MA_PA_CHANNEL_POSITION_REAR_CENTER; + case MA_CHANNEL_SIDE_LEFT: return MA_PA_CHANNEL_POSITION_SIDE_LEFT; + case MA_CHANNEL_SIDE_RIGHT: return MA_PA_CHANNEL_POSITION_SIDE_RIGHT; + case MA_CHANNEL_TOP_CENTER: return MA_PA_CHANNEL_POSITION_TOP_CENTER; + case MA_CHANNEL_TOP_FRONT_LEFT: return MA_PA_CHANNEL_POSITION_TOP_FRONT_LEFT; + case MA_CHANNEL_TOP_FRONT_CENTER: return MA_PA_CHANNEL_POSITION_TOP_FRONT_CENTER; + case MA_CHANNEL_TOP_FRONT_RIGHT: return MA_PA_CHANNEL_POSITION_TOP_FRONT_RIGHT; + case MA_CHANNEL_TOP_BACK_LEFT: return MA_PA_CHANNEL_POSITION_TOP_REAR_LEFT; + case MA_CHANNEL_TOP_BACK_CENTER: return MA_PA_CHANNEL_POSITION_TOP_REAR_CENTER; + case MA_CHANNEL_TOP_BACK_RIGHT: return MA_PA_CHANNEL_POSITION_TOP_REAR_RIGHT; + case MA_CHANNEL_19: return MA_PA_CHANNEL_POSITION_AUX18; + case MA_CHANNEL_20: return MA_PA_CHANNEL_POSITION_AUX19; + case MA_CHANNEL_21: return MA_PA_CHANNEL_POSITION_AUX20; + case MA_CHANNEL_22: return MA_PA_CHANNEL_POSITION_AUX21; + case MA_CHANNEL_23: return MA_PA_CHANNEL_POSITION_AUX22; + case MA_CHANNEL_24: return MA_PA_CHANNEL_POSITION_AUX23; + case MA_CHANNEL_25: return MA_PA_CHANNEL_POSITION_AUX24; + case MA_CHANNEL_26: return MA_PA_CHANNEL_POSITION_AUX25; + case MA_CHANNEL_27: return MA_PA_CHANNEL_POSITION_AUX26; + case MA_CHANNEL_28: return MA_PA_CHANNEL_POSITION_AUX27; + case MA_CHANNEL_29: return MA_PA_CHANNEL_POSITION_AUX28; + case MA_CHANNEL_30: return MA_PA_CHANNEL_POSITION_AUX29; + case MA_CHANNEL_31: return MA_PA_CHANNEL_POSITION_AUX30; + case MA_CHANNEL_32: return MA_PA_CHANNEL_POSITION_AUX31; + default: return (ma_pa_channel_position_t)position; + } +} +#endif + +ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_pa_mainloop* pMainLoop, ma_pa_operation* pOP) +{ + 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); + } + } + + return MA_SUCCESS; +} + +ma_result ma_device__wait_for_operation__pulse(ma_device* pDevice, ma_pa_operation* pOP) +{ + ma_assert(pDevice != NULL); + ma_assert(pOP != NULL); + + return ma_wait_for_operation__pulse(pDevice->pContext, (ma_pa_mainloop*)pDevice->pulse.pMainLoop, pOP); +} + + +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; + +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); + } + + pData->isTerminated = !pData->callback(pData->pContext, ma_device_type_playback, &deviceInfo, pData->pUserData); + + (void)pPulseContext; /* Unused. */ +} + +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. */ +} + +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 +{ + ma_device_info* pDeviceInfo; + ma_bool32 foundDevice; +} ma_context_get_device_info_callback_data__pulse; + +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. */ +} + +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. */ +} + +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); + } + + 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. */ + } + } + + 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; +} + + +void ma_pulse_device_state_callback(ma_pa_context* pPulseContext, void* pUserData) +{ + ma_device* pDevice; + ma_context* pContext; + + pDevice = (ma_device*)pUserData; + ma_assert(pDevice != NULL); + + pContext = pDevice->pContext; + ma_assert(pContext != NULL); + + pDevice->pulse.pulseContextState = ((ma_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)(pPulseContext); +} + +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; + + if (endOfList > 0) { + return; + } + + pInfoOut = (ma_pa_sink_info*)pUserData; + ma_assert(pInfoOut != NULL); + + *pInfoOut = *pInfo; + + (void)pPulseContext; /* Unused. */ +} + +void ma_device_source_info_callback(ma_pa_context* pPulseContext, const ma_pa_source_info* pInfo, int endOfList, void* pUserData) +{ + ma_pa_source_info* pInfoOut; + + if (endOfList > 0) { + return; + } + + pInfoOut = (ma_pa_source_info*)pUserData; + ma_assert(pInfoOut != NULL); + + *pInfoOut = *pInfo; + + (void)pPulseContext; /* Unused. */ +} + +void ma_device_sink_name_callback(ma_pa_context* pPulseContext, const ma_pa_sink_info* pInfo, int endOfList, void* pUserData) +{ + ma_device* pDevice; + + if (endOfList > 0) { + return; + } + + pDevice = (ma_device*)pUserData; + ma_assert(pDevice != NULL); + + ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), pInfo->description, (size_t)-1); + + (void)pPulseContext; /* Unused. */ +} + +void ma_device_source_name_callback(ma_pa_context* pPulseContext, const ma_pa_source_info* pInfo, int endOfList, void* pUserData) +{ + ma_device* pDevice; + + if (endOfList > 0) { + return; + } + + pDevice = (ma_device*)pUserData; + ma_assert(pDevice != NULL); + + ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), pInfo->description, (size_t)-1); + + (void)pPulseContext; /* Unused. */ +} + +void ma_device_uninit__pulse(ma_device* pDevice) +{ + ma_context* pContext; + + ma_assert(pDevice != NULL); + + pContext = pDevice->pContext; + ma_assert(pContext != NULL); + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + ((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); +} + +ma_pa_buffer_attr ma_device__pa_buffer_attr_new(ma_uint32 bufferSizeInFrames, ma_uint32 periods, const ma_pa_sample_spec* ss) +{ + ma_pa_buffer_attr attr; + attr.maxlength = bufferSizeInFrames * ma_get_bytes_per_sample(ma_format_from_pulse(ss->format)) * ss->channels; + attr.tlength = attr.maxlength / periods; + attr.prebuf = (ma_uint32)-1; + attr.minreq = (ma_uint32)-1; + attr.fragsize = attr.maxlength / periods; + + return attr; +} + +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 int g_StreamCounter = 0; + char actualStreamName[256]; + + if (pStreamName != NULL) { + ma_strncpy_s(actualStreamName, sizeof(actualStreamName), pStreamName, (size_t)-1); + } else { + ma_strcpy_s(actualStreamName, sizeof(actualStreamName), "miniaudio:"); + ma_itoa_s(g_StreamCounter, actualStreamName + 8, sizeof(actualStreamName)-8, 10); /* 8 = strlen("miniaudio:") */ + } + g_StreamCounter += 1; + + return ((ma_pa_stream_new_proc)pDevice->pContext->pulse.pa_stream_new)((ma_pa_context*)pDevice->pulse.pPulseContext, actualStreamName, ss, cmap); +} + +ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +{ + ma_result result = MA_SUCCESS; + int error = 0; + const char* devPlayback = NULL; + const char* devCapture = NULL; + ma_uint32 bufferSizeInMilliseconds; + 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; + const ma_pa_sample_spec* pActualSS = NULL; + const ma_pa_channel_map* pActualCMap = NULL; + const ma_pa_buffer_attr* pActualAttr = NULL; + ma_uint32 iChannel; + ma_pa_stream_flags_t streamFlags; + + ma_assert(pDevice != NULL); + ma_zero_object(&pDevice->pulse); + + /* No exclusive mode with the PulseAudio 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)) { + 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; + } + + bufferSizeInMilliseconds = pConfig->bufferSizeInMilliseconds; + if (bufferSizeInMilliseconds == 0) { + bufferSizeInMilliseconds = ma_calculate_buffer_size_in_milliseconds_from_frames(pConfig->bufferSizeInFrames, 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; + } + + /* 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; + } + + 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) { + 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; + } + + ss = sourceInfo.sample_spec; + cmap = sourceInfo.channel_map; + + pDevice->capture.internalBufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(bufferSizeInMilliseconds, ss.rate); + pDevice->capture.internalPeriods = pConfig->periods; + + attr = ma_device__pa_buffer_attr_new(pDevice->capture.internalBufferSizeInFrames, pConfig->periods, &ss); + #ifdef MA_DEBUG_OUTPUT + printf("[PulseAudio] Capture attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; internalBufferSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDevice->capture.internalBufferSizeInFrames); + #endif + + pDevice->pulse.pStreamCapture = ma_device__pa_stream_new__pulse(pDevice, 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; + } + + streamFlags = MA_PA_STREAM_START_CORKED | 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); + 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; + } + + 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; + } + } + + /* Internal format. */ + pActualSS = ((ma_pa_stream_get_sample_spec_proc)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.internalBufferSizeInFrames, 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; + + /* Internal channel map. */ + pActualCMap = ((ma_pa_stream_get_channel_map_proc)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]); + } + + /* Buffer. */ + pActualAttr = ((ma_pa_stream_get_buffer_attr_proc)pContext->pulse.pa_stream_get_buffer_attr)((ma_pa_stream*)pDevice->pulse.pStreamCapture); + if (pActualAttr != NULL) { + attr = *pActualAttr; + } + pDevice->capture.internalBufferSizeInFrames = attr.maxlength / (ma_get_bytes_per_sample(pDevice->capture.internalFormat) * pDevice->capture.internalChannels); + pDevice->capture.internalPeriods = attr.maxlength / attr.fragsize; + #ifdef MA_DEBUG_OUTPUT + printf("[PulseAudio] Capture actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; internalBufferSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDevice->capture.internalBufferSizeInFrames); + #endif + + /* Name. */ + devCapture = ((ma_pa_stream_get_device_name_proc)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); + } + } + } + + 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; + } + + ss = sinkInfo.sample_spec; + cmap = sinkInfo.channel_map; + + pDevice->playback.internalBufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(bufferSizeInMilliseconds, ss.rate); + pDevice->playback.internalPeriods = pConfig->periods; + + attr = ma_device__pa_buffer_attr_new(pDevice->playback.internalBufferSizeInFrames, pConfig->periods, &ss); + #ifdef MA_DEBUG_OUTPUT + printf("[PulseAudio] Playback attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; internalBufferSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDevice->playback.internalBufferSizeInFrames); + #endif + + pDevice->pulse.pStreamPlayback = ma_device__pa_stream_new__pulse(pDevice, 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; + } + + streamFlags = MA_PA_STREAM_START_CORKED | 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); + 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; + } + + 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; + } + } + + /* Internal format. */ + pActualSS = ((ma_pa_stream_get_sample_spec_proc)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.internalBufferSizeInFrames, 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; + + /* Internal channel map. */ + pActualCMap = ((ma_pa_stream_get_channel_map_proc)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]); + } + + /* Buffer. */ + pActualAttr = ((ma_pa_stream_get_buffer_attr_proc)pContext->pulse.pa_stream_get_buffer_attr)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); + if (pActualAttr != NULL) { + attr = *pActualAttr; + } + pDevice->playback.internalBufferSizeInFrames = attr.maxlength / (ma_get_bytes_per_sample(pDevice->playback.internalFormat) * pDevice->playback.internalChannels); + pDevice->playback.internalPeriods = /*pConfig->periods;*/attr.maxlength / attr.tlength; + #ifdef MA_DEBUG_OUTPUT + printf("[PulseAudio] Playback actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; internalBufferSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDevice->playback.internalBufferSizeInFrames); + #endif + + /* Name. */ + devPlayback = ((ma_pa_stream_get_device_name_proc)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); + } + } + } + + 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); + } +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; +} + + +void ma_pulse_operation_complete_callback(ma_pa_stream* pStream, int success, void* pUserData) +{ + ma_bool32* pIsSuccessful = (ma_bool32*)pUserData; + ma_assert(pIsSuccessful != NULL); + + *pIsSuccessful = (ma_bool32)success; + + (void)pStream; /* Unused. */ +} + +ma_result ma_device__cork_stream__pulse(ma_device* pDevice, ma_device_type deviceType, int cork) +{ + ma_context* pContext = pDevice->pContext; + ma_bool32 wasSuccessful; + ma_pa_stream* pStream; + ma_pa_operation* pOP; + ma_result result; + + /* This should not be called with a duplex device type. */ + if (deviceType == ma_device_type_duplex) { + return MA_INVALID_ARGS; + } + + wasSuccessful = MA_FALSE; + + pStream = (ma_pa_stream*)((deviceType == ma_device_type_capture) ? pDevice->pulse.pStreamCapture : pDevice->pulse.pStreamPlayback); + ma_assert(pStream != NULL); + + pOP = ((ma_pa_stream_cork_proc)pContext->pulse.pa_stream_cork)(pStream, cork, ma_pulse_operation_complete_callback, &wasSuccessful); + if (pOP == NULL) { + 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); + + 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); + } + + if (!wasSuccessful) { + if (cork) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to stop PulseAudio stream.", MA_FAILED_TO_STOP_BACKEND_DEVICE); + } else { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to start PulseAudio stream.", MA_FAILED_TO_START_BACKEND_DEVICE); + } + } + + return MA_SUCCESS; +} + +ma_result ma_device_stop__pulse(ma_device* pDevice) +{ + ma_result result; + ma_bool32 wasSuccessful; + ma_pa_operation* pOP; + + 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, 1); + if (result != MA_SUCCESS) { + return result; + } + } + + 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); + } + + result = ma_device__cork_stream__pulse(pDevice, ma_device_type_playback, 1); + if (result != MA_SUCCESS) { + return result; + } + } + + return MA_SUCCESS; +} + +ma_result ma_device_write__pulse(ma_device* pDevice, const void* pPCMFrames, ma_uint32 frameCount) +{ + ma_uint32 totalFramesWritten; + + ma_assert(pDevice != NULL); + ma_assert(pPCMFrames != NULL); + ma_assert(frameCount > 0); + + /* The stream needs to be uncorked first. */ + if (((ma_pa_stream_is_corked_proc)pDevice->pContext->pulse.pa_stream_is_corked)((ma_pa_stream*)pDevice->pulse.pStreamPlayback)) { + ma_result result = ma_device__cork_stream__pulse(pDevice, ma_device_type_playback, 0); + if (result != MA_SUCCESS) { + return result; + } + } + + totalFramesWritten = 0; + while (totalFramesWritten < frameCount) { + /* 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) { + 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) { + /*size_t periodSizeInBytes = (pDevice->playback.internalBufferSizeInFrames / pDevice->playback.internalPeriods) * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels);*/ + 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); + } + } + } + + return MA_SUCCESS; +} + +ma_result ma_device_read__pulse(ma_device* pDevice, void* pPCMFrames, ma_uint32 frameCount) +{ + ma_uint32 totalFramesRead; + + ma_assert(pDevice != NULL); + ma_assert(pPCMFrames != NULL); + ma_assert(frameCount > 0); + + /* The stream needs to be uncorked first. */ + if (((ma_pa_stream_is_corked_proc)pDevice->pContext->pulse.pa_stream_is_corked)((ma_pa_stream*)pDevice->pulse.pStreamCapture)) { + ma_result result = ma_device__cork_stream__pulse(pDevice, ma_device_type_capture, 0); + if (result != MA_SUCCESS) { + return result; + } + } + + totalFramesRead = 0; + while (totalFramesRead < frameCount) { + if (ma_device__get_state(pDevice) != MA_STATE_STARTED) { + break; + } + + /* If a buffer is mapped we need to write to that first. Once it's consumed we reset the event and unmap it. */ + if (pDevice->pulse.pMappedBufferCapture != NULL && 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); + } + + 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 = ((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 (;;) { + size_t readableSizeInBytes; + + 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)) { + break; + } + + readableSizeInBytes = ((ma_pa_stream_readable_size_proc)pDevice->pContext->pulse.pa_stream_readable_size)((ma_pa_stream*)pDevice->pulse.pStreamCapture); + if (readableSizeInBytes != (size_t)-1) { + /*size_t periodSizeInBytes = (pDevice->capture.internalBufferSizeInFrames / pDevice->capture.internalPeriods) * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels);*/ + if (readableSizeInBytes > 0) { + /* Data is avaialable. */ + size_t bytesMapped = (size_t)-1; + int 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 (pDevice->pulse.pMappedBufferCapture == NULL && bytesMapped == 0) { + /* Nothing available. This shouldn't happen because we checked earlier with pa_stream_readable_size(). I'm going to throw an error in this case. */ + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Nothing available after peeking capture buffer.", MA_ERROR); + } + + pDevice->pulse.mappedBufferFramesCapacityCapture = bytesMapped / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + pDevice->pulse.mappedBufferFramesRemainingCapture = pDevice->pulse.mappedBufferFramesCapacityCapture; + + break; + } else { + /* No data available. 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. + */ + int 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); + } + + continue; + } + } else { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to query the stream's readable size.", MA_ERROR); + } + } + } + + return MA_SUCCESS; +} + + +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->pulse.pServerName = NULL; + + ma_free(pContext->pulse.pApplicationName); + pContext->pulse.pApplicationName = NULL; + +#ifndef MA_NO_RUNTIME_LINKING + ma_dlclose(pContext, pContext->pulse.pulseSO); +#endif + + return MA_SUCCESS; +} + +ma_result ma_context_init__pulse(const ma_context_config* pConfig, ma_context* pContext) +{ +#ifndef MA_NO_RUNTIME_LINKING + const char* libpulseNames[] = { + "libpulse.so", + "libpulse.so.0" + }; + size_t i; + + for (i = 0; i < ma_countof(libpulseNames); ++i) { + pContext->pulse.pulseSO = ma_dlopen(pContext, libpulseNames[i]); + if (pContext->pulse.pulseSO != NULL) { + break; + } + } + + if (pContext->pulse.pulseSO == NULL) { + return MA_NO_BACKEND; + } + + 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_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_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"); + pContext->pulse.pa_context_disconnect = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_context_disconnect"); + pContext->pulse.pa_context_set_state_callback = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_context_set_state_callback"); + pContext->pulse.pa_context_get_state = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_context_get_state"); + pContext->pulse.pa_context_get_sink_info_list = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_context_get_sink_info_list"); + pContext->pulse.pa_context_get_source_info_list = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_context_get_source_info_list"); + pContext->pulse.pa_context_get_sink_info_by_name = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_context_get_sink_info_by_name"); + pContext->pulse.pa_context_get_source_info_by_name = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_context_get_source_info_by_name"); + pContext->pulse.pa_operation_unref = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_operation_unref"); + pContext->pulse.pa_operation_get_state = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_operation_get_state"); + pContext->pulse.pa_channel_map_init_extend = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_channel_map_init_extend"); + pContext->pulse.pa_channel_map_valid = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_channel_map_valid"); + pContext->pulse.pa_channel_map_compatible = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_channel_map_compatible"); + pContext->pulse.pa_stream_new = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_new"); + pContext->pulse.pa_stream_unref = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_unref"); + pContext->pulse.pa_stream_connect_playback = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_connect_playback"); + pContext->pulse.pa_stream_connect_record = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_connect_record"); + pContext->pulse.pa_stream_disconnect = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_disconnect"); + pContext->pulse.pa_stream_get_state = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_get_state"); + pContext->pulse.pa_stream_get_sample_spec = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_get_sample_spec"); + pContext->pulse.pa_stream_get_channel_map = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_get_channel_map"); + pContext->pulse.pa_stream_get_buffer_attr = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_get_buffer_attr"); + pContext->pulse.pa_stream_set_buffer_attr = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_set_buffer_attr"); + pContext->pulse.pa_stream_get_device_name = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_get_device_name"); + pContext->pulse.pa_stream_set_write_callback = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_set_write_callback"); + pContext->pulse.pa_stream_set_read_callback = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_set_read_callback"); + pContext->pulse.pa_stream_flush = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_flush"); + pContext->pulse.pa_stream_drain = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_drain"); + pContext->pulse.pa_stream_is_corked = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_is_corked"); + pContext->pulse.pa_stream_cork = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_cork"); + pContext->pulse.pa_stream_trigger = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_trigger"); + pContext->pulse.pa_stream_begin_write = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_begin_write"); + pContext->pulse.pa_stream_write = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_write"); + pContext->pulse.pa_stream_peek = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_peek"); + pContext->pulse.pa_stream_drop = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_drop"); + pContext->pulse.pa_stream_writable_size = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_writable_size"); + pContext->pulse.pa_stream_readable_size = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_readable_size"); +#else + /* 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_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_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; + ma_pa_context_disconnect_proc _pa_context_disconnect = pa_context_disconnect; + ma_pa_context_set_state_callback_proc _pa_context_set_state_callback = pa_context_set_state_callback; + ma_pa_context_get_state_proc _pa_context_get_state = pa_context_get_state; + ma_pa_context_get_sink_info_list_proc _pa_context_get_sink_info_list = pa_context_get_sink_info_list; + ma_pa_context_get_source_info_list_proc _pa_context_get_source_info_list = pa_context_get_source_info_list; + ma_pa_context_get_sink_info_by_name_proc _pa_context_get_sink_info_by_name = pa_context_get_sink_info_by_name; + ma_pa_context_get_source_info_by_name_proc _pa_context_get_source_info_by_name= pa_context_get_source_info_by_name; + ma_pa_operation_unref_proc _pa_operation_unref = pa_operation_unref; + ma_pa_operation_get_state_proc _pa_operation_get_state = pa_operation_get_state; + ma_pa_channel_map_init_extend_proc _pa_channel_map_init_extend = pa_channel_map_init_extend; + ma_pa_channel_map_valid_proc _pa_channel_map_valid = pa_channel_map_valid; + ma_pa_channel_map_compatible_proc _pa_channel_map_compatible = pa_channel_map_compatible; + ma_pa_stream_new_proc _pa_stream_new = pa_stream_new; + ma_pa_stream_unref_proc _pa_stream_unref = pa_stream_unref; + ma_pa_stream_connect_playback_proc _pa_stream_connect_playback = pa_stream_connect_playback; + ma_pa_stream_connect_record_proc _pa_stream_connect_record = pa_stream_connect_record; + ma_pa_stream_disconnect_proc _pa_stream_disconnect = pa_stream_disconnect; + ma_pa_stream_get_state_proc _pa_stream_get_state = pa_stream_get_state; + ma_pa_stream_get_sample_spec_proc _pa_stream_get_sample_spec = pa_stream_get_sample_spec; + ma_pa_stream_get_channel_map_proc _pa_stream_get_channel_map = pa_stream_get_channel_map; + ma_pa_stream_get_buffer_attr_proc _pa_stream_get_buffer_attr = pa_stream_get_buffer_attr; + ma_pa_stream_set_buffer_attr_proc _pa_stream_set_buffer_attr = pa_stream_set_buffer_attr; + ma_pa_stream_get_device_name_proc _pa_stream_get_device_name = pa_stream_get_device_name; + ma_pa_stream_set_write_callback_proc _pa_stream_set_write_callback = pa_stream_set_write_callback; + ma_pa_stream_set_read_callback_proc _pa_stream_set_read_callback = pa_stream_set_read_callback; + ma_pa_stream_flush_proc _pa_stream_flush = pa_stream_flush; + ma_pa_stream_drain_proc _pa_stream_drain = pa_stream_drain; + ma_pa_stream_is_corked_proc _pa_stream_is_corked = pa_stream_is_corked; + ma_pa_stream_cork_proc _pa_stream_cork = pa_stream_cork; + ma_pa_stream_trigger_proc _pa_stream_trigger = pa_stream_trigger; + ma_pa_stream_begin_write_proc _pa_stream_begin_write = pa_stream_begin_write; + ma_pa_stream_write_proc _pa_stream_write = pa_stream_write; + ma_pa_stream_peek_proc _pa_stream_peek = pa_stream_peek; + ma_pa_stream_drop_proc _pa_stream_drop = pa_stream_drop; + ma_pa_stream_writable_size_proc _pa_stream_writable_size = pa_stream_writable_size; + ma_pa_stream_readable_size_proc _pa_stream_readable_size = pa_stream_readable_size; + + pContext->pulse.pa_mainloop_new = (ma_proc)_pa_mainloop_new; + pContext->pulse.pa_mainloop_free = (ma_proc)_pa_mainloop_free; + 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_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; + pContext->pulse.pa_context_disconnect = (ma_proc)_pa_context_disconnect; + pContext->pulse.pa_context_set_state_callback = (ma_proc)_pa_context_set_state_callback; + pContext->pulse.pa_context_get_state = (ma_proc)_pa_context_get_state; + pContext->pulse.pa_context_get_sink_info_list = (ma_proc)_pa_context_get_sink_info_list; + pContext->pulse.pa_context_get_source_info_list = (ma_proc)_pa_context_get_source_info_list; + pContext->pulse.pa_context_get_sink_info_by_name = (ma_proc)_pa_context_get_sink_info_by_name; + pContext->pulse.pa_context_get_source_info_by_name = (ma_proc)_pa_context_get_source_info_by_name; + pContext->pulse.pa_operation_unref = (ma_proc)_pa_operation_unref; + pContext->pulse.pa_operation_get_state = (ma_proc)_pa_operation_get_state; + pContext->pulse.pa_channel_map_init_extend = (ma_proc)_pa_channel_map_init_extend; + pContext->pulse.pa_channel_map_valid = (ma_proc)_pa_channel_map_valid; + pContext->pulse.pa_channel_map_compatible = (ma_proc)_pa_channel_map_compatible; + pContext->pulse.pa_stream_new = (ma_proc)_pa_stream_new; + pContext->pulse.pa_stream_unref = (ma_proc)_pa_stream_unref; + pContext->pulse.pa_stream_connect_playback = (ma_proc)_pa_stream_connect_playback; + pContext->pulse.pa_stream_connect_record = (ma_proc)_pa_stream_connect_record; + pContext->pulse.pa_stream_disconnect = (ma_proc)_pa_stream_disconnect; + pContext->pulse.pa_stream_get_state = (ma_proc)_pa_stream_get_state; + pContext->pulse.pa_stream_get_sample_spec = (ma_proc)_pa_stream_get_sample_spec; + pContext->pulse.pa_stream_get_channel_map = (ma_proc)_pa_stream_get_channel_map; + pContext->pulse.pa_stream_get_buffer_attr = (ma_proc)_pa_stream_get_buffer_attr; + pContext->pulse.pa_stream_set_buffer_attr = (ma_proc)_pa_stream_set_buffer_attr; + pContext->pulse.pa_stream_get_device_name = (ma_proc)_pa_stream_get_device_name; + pContext->pulse.pa_stream_set_write_callback = (ma_proc)_pa_stream_set_write_callback; + pContext->pulse.pa_stream_set_read_callback = (ma_proc)_pa_stream_set_read_callback; + pContext->pulse.pa_stream_flush = (ma_proc)_pa_stream_flush; + pContext->pulse.pa_stream_drain = (ma_proc)_pa_stream_drain; + pContext->pulse.pa_stream_is_corked = (ma_proc)_pa_stream_is_corked; + pContext->pulse.pa_stream_cork = (ma_proc)_pa_stream_cork; + pContext->pulse.pa_stream_trigger = (ma_proc)_pa_stream_trigger; + pContext->pulse.pa_stream_begin_write = (ma_proc)_pa_stream_begin_write; + pContext->pulse.pa_stream_write = (ma_proc)_pa_stream_write; + pContext->pulse.pa_stream_peek = (ma_proc)_pa_stream_peek; + pContext->pulse.pa_stream_drop = (ma_proc)_pa_stream_drop; + pContext->pulse.pa_stream_writable_size = (ma_proc)_pa_stream_writable_size; + 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 = ma_device_stop__pulse; + pContext->onDeviceWrite = ma_device_write__pulse; + pContext->onDeviceRead = ma_device_read__pulse; + + if (pConfig->pulse.pApplicationName) { + pContext->pulse.pApplicationName = ma_copy_string(pConfig->pulse.pApplicationName); + } + if (pConfig->pulse.pServerName) { + pContext->pulse.pServerName = ma_copy_string(pConfig->pulse.pServerName); + } + 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); + ma_free(pContext->pulse.pApplicationName); + #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); + ma_free(pContext->pulse.pApplicationName); + ((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); + ma_free(pContext->pulse.pApplicationName); + ((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); + ma_free(pContext->pulse.pApplicationName); + ((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); + } + + return MA_SUCCESS; +} +#endif + + +/****************************************************************************** + +JACK Backend + +******************************************************************************/ +#ifdef MA_HAS_JACK + +/* It is assumed jack.h is available when compile-time linking is being used. */ +#ifdef MA_NO_RUNTIME_LINKING +#include + +typedef jack_nframes_t ma_jack_nframes_t; +typedef jack_options_t ma_jack_options_t; +typedef jack_status_t ma_jack_status_t; +typedef jack_client_t ma_jack_client_t; +typedef jack_port_t ma_jack_port_t; +typedef JackProcessCallback ma_JackProcessCallback; +typedef JackBufferSizeCallback ma_JackBufferSizeCallback; +typedef JackShutdownCallback ma_JackShutdownCallback; +#define MA_JACK_DEFAULT_AUDIO_TYPE JACK_DEFAULT_AUDIO_TYPE +#define ma_JackNoStartServer JackNoStartServer +#define ma_JackPortIsInput JackPortIsInput +#define ma_JackPortIsOutput JackPortIsOutput +#define ma_JackPortIsPhysical JackPortIsPhysical +#else +typedef ma_uint32 ma_jack_nframes_t; +typedef int ma_jack_options_t; +typedef int ma_jack_status_t; +typedef struct ma_jack_client_t ma_jack_client_t; +typedef struct ma_jack_port_t ma_jack_port_t; +typedef int (* ma_JackProcessCallback) (ma_jack_nframes_t nframes, void* arg); +typedef int (* ma_JackBufferSizeCallback)(ma_jack_nframes_t nframes, void* arg); +typedef void (* ma_JackShutdownCallback) (void* arg); +#define MA_JACK_DEFAULT_AUDIO_TYPE "32 bit float mono audio" +#define ma_JackNoStartServer 1 +#define ma_JackPortIsInput 1 +#define ma_JackPortIsOutput 2 +#define ma_JackPortIsPhysical 4 +#endif + +typedef ma_jack_client_t* (* ma_jack_client_open_proc) (const char* client_name, ma_jack_options_t options, ma_jack_status_t* status, ...); +typedef int (* ma_jack_client_close_proc) (ma_jack_client_t* client); +typedef int (* ma_jack_client_name_size_proc) (); +typedef int (* ma_jack_set_process_callback_proc) (ma_jack_client_t* client, ma_JackProcessCallback process_callback, void* arg); +typedef int (* ma_jack_set_buffer_size_callback_proc)(ma_jack_client_t* client, ma_JackBufferSizeCallback bufsize_callback, void* arg); +typedef void (* ma_jack_on_shutdown_proc) (ma_jack_client_t* client, ma_JackShutdownCallback function, void* arg); +typedef ma_jack_nframes_t (* ma_jack_get_sample_rate_proc) (ma_jack_client_t* client); +typedef ma_jack_nframes_t (* ma_jack_get_buffer_size_proc) (ma_jack_client_t* client); +typedef const char** (* ma_jack_get_ports_proc) (ma_jack_client_t* client, const char* port_name_pattern, const char* type_name_pattern, unsigned long flags); +typedef int (* ma_jack_activate_proc) (ma_jack_client_t* client); +typedef int (* ma_jack_deactivate_proc) (ma_jack_client_t* client); +typedef int (* ma_jack_connect_proc) (ma_jack_client_t* client, const char* source_port, const char* destination_port); +typedef ma_jack_port_t* (* ma_jack_port_register_proc) (ma_jack_client_t* client, const char* port_name, const char* port_type, unsigned long flags, unsigned long buffer_size); +typedef const char* (* ma_jack_port_name_proc) (const ma_jack_port_t* port); +typedef void* (* ma_jack_port_get_buffer_proc) (ma_jack_port_t* port, ma_jack_nframes_t nframes); +typedef void (* ma_jack_free_proc) (void* ptr); + +ma_result ma_context_open_client__jack(ma_context* pContext, ma_jack_client_t** ppClient) +{ + size_t maxClientNameSize; + char clientName[256]; + ma_jack_status_t status; + ma_jack_client_t* pClient; + + ma_assert(pContext != NULL); + ma_assert(ppClient != NULL); + + if (ppClient) { + *ppClient = NULL; + } + + maxClientNameSize = ((ma_jack_client_name_size_proc)pContext->jack.jack_client_name_size)(); /* Includes null terminator. */ + ma_strncpy_s(clientName, ma_min(sizeof(clientName), maxClientNameSize), (pContext->jack.pClientName != NULL) ? pContext->jack.pClientName : "miniaudio", (size_t)-1); + + pClient = ((ma_jack_client_open_proc)pContext->jack.jack_client_open)(clientName, (pContext->jack.tryStartServer) ? 0 : ma_JackNoStartServer, &status, NULL); + if (pClient == NULL) { + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + } + + if (ppClient) { + *ppClient = pClient; + } + + return MA_SUCCESS; +} + +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; +} + +ma_result ma_context_enumerate_devices__jack(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_bool32 cbResult = MA_TRUE; + + ma_assert(pContext != NULL); + ma_assert(callback != NULL); + + /* Playback. */ + if (cbResult) { + ma_device_info deviceInfo; + ma_zero_object(&deviceInfo); + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); + } + + /* Capture. */ + if (cbResult) { + ma_device_info deviceInfo; + ma_zero_object(&deviceInfo); + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); + } + + return MA_SUCCESS; +} + +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) +{ + ma_jack_client_t* pClient; + ma_result result; + const char** ppPorts; + + 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. */ + } + + /* Name / Description */ + if (deviceType == ma_device_type_playback) { + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + } + + /* Jack only supports f32 and has a specific channel count and sample rate. */ + pDeviceInfo->formatCount = 1; + pDeviceInfo->formats[0] = 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); + if (result != MA_SUCCESS) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[JACK] Failed to open client.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + 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; + + ppPorts = ((ma_jack_get_ports_proc)pContext->jack.jack_get_ports)((ma_jack_client_t*)pClient, NULL, NULL, ma_JackPortIsPhysical | ((deviceType == ma_device_type_playback) ? ma_JackPortIsInput : ma_JackPortIsOutput)); + if (ppPorts == NULL) { + ((ma_jack_client_close_proc)pContext->jack.jack_client_close)((ma_jack_client_t*)pClient); + 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; + } + + ((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); + + (void)pContext; + return MA_SUCCESS; +} + + +void ma_device_uninit__jack(ma_device* pDevice) +{ + ma_context* pContext; + + ma_assert(pDevice != NULL); + + pContext = pDevice->pContext; + ma_assert(pContext != NULL); + + if (pDevice->jack.pClient != NULL) { + ((ma_jack_client_close_proc)pContext->jack.jack_client_close)((ma_jack_client_t*)pDevice->jack.pClient); + } + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + ma_free(pDevice->jack.pIntermediaryBufferCapture); + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ma_free(pDevice->jack.pIntermediaryBufferPlayback); + } + + if (pDevice->type == ma_device_type_duplex) { + ma_pcm_rb_uninit(&pDevice->jack.duplexRB); + } +} + +void ma_device__jack_shutdown_callback(void* pUserData) +{ + /* JACK died. Stop the device. */ + ma_device* pDevice = (ma_device*)pUserData; + ma_assert(pDevice != NULL); + + ma_device_stop(pDevice); +} + +int ma_device__jack_buffer_size_callback(ma_jack_nframes_t frameCount, void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_assert(pDevice != NULL); + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + float* pNewBuffer = (float*)ma_realloc(pDevice->jack.pIntermediaryBufferCapture, frameCount * (pDevice->capture.internalChannels * ma_get_bytes_per_sample(pDevice->capture.internalFormat))); + if (pNewBuffer == NULL) { + return MA_OUT_OF_MEMORY; + } + + pDevice->jack.pIntermediaryBufferCapture = pNewBuffer; + pDevice->playback.internalBufferSizeInFrames = frameCount * pDevice->capture.internalPeriods; + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + float* pNewBuffer = (float*)ma_realloc(pDevice->jack.pIntermediaryBufferPlayback, frameCount * (pDevice->playback.internalChannels * ma_get_bytes_per_sample(pDevice->playback.internalFormat))); + if (pNewBuffer == NULL) { + return MA_OUT_OF_MEMORY; + } + + pDevice->jack.pIntermediaryBufferPlayback = pNewBuffer; + pDevice->playback.internalBufferSizeInFrames = frameCount * pDevice->playback.internalPeriods; + } + + return 0; +} + +int ma_device__jack_process_callback(ma_jack_nframes_t frameCount, void* pUserData) +{ + ma_device* pDevice; + ma_context* pContext; + ma_uint32 iChannel; + + pDevice = (ma_device*)pUserData; + ma_assert(pDevice != NULL); + + pContext = pDevice->pContext; + ma_assert(pContext != NULL); + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + /* Channels need to be interleaved. */ + for (iChannel = 0; iChannel < pDevice->capture.internalChannels; ++iChannel) { + const float* pSrc = (const float*)((ma_jack_port_get_buffer_proc)pContext->jack.jack_port_get_buffer)((ma_jack_port_t*)pDevice->jack.pPortsCapture[iChannel], frameCount); + if (pSrc != NULL) { + float* pDst = pDevice->jack.pIntermediaryBufferCapture + iChannel; + ma_jack_nframes_t iFrame; + for (iFrame = 0; iFrame < frameCount; ++iFrame) { + *pDst = *pSrc; + + pDst += pDevice->capture.internalChannels; + pSrc += 1; + } + } + } + + 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); + } + } + + 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); + } + + /* Channels need to be deinterleaved. */ + for (iChannel = 0; iChannel < pDevice->playback.internalChannels; ++iChannel) { + float* pDst = (float*)((ma_jack_port_get_buffer_proc)pContext->jack.jack_port_get_buffer)((ma_jack_port_t*)pDevice->jack.pPortsPlayback[iChannel], frameCount); + if (pDst != NULL) { + const float* pSrc = pDevice->jack.pIntermediaryBufferPlayback + iChannel; + ma_jack_nframes_t iFrame; + for (iFrame = 0; iFrame < frameCount; ++iFrame) { + *pDst = *pSrc; + + pDst += 1; + pSrc += pDevice->playback.internalChannels; + } + } + } + } + + return 0; +} + +ma_result ma_device_init__jack(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +{ + ma_result result; + ma_uint32 periods; + ma_uint32 bufferSizeInFrames; + + ma_assert(pContext != NULL); + ma_assert(pConfig != NULL); + ma_assert(pDevice != NULL); + + /* 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)) { + 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)) { + return MA_SHARE_MODE_NOT_SUPPORTED; + } + + /* Open the client. */ + result = ma_context_open_client__jack(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.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + /* 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) { + 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) { + 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); + + + /* The buffer size in frames can change. */ + periods = 2; + bufferSizeInFrames = ((ma_jack_get_buffer_size_proc)pContext->jack.jack_get_buffer_size)((ma_jack_client_t*)pDevice->jack.pClient) * periods; + + 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); + + ppPorts = ((ma_jack_get_ports_proc)pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, NULL, 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) { + 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" */ + + 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); + 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; + } + + ((ma_jack_free_proc)pContext->jack.jack_free)((void*)ppPorts); + + pDevice->capture.internalBufferSizeInFrames = bufferSizeInFrames; + pDevice->capture.internalPeriods = periods; + + pDevice->jack.pIntermediaryBufferCapture = (float*)ma_malloc((pDevice->capture.internalBufferSizeInFrames/pDevice->capture.internalPeriods) * (pDevice->capture.internalChannels * ma_get_bytes_per_sample(pDevice->capture.internalFormat))); + if (pDevice->jack.pIntermediaryBufferCapture == NULL) { + ma_device_uninit__jack(pDevice); + return MA_OUT_OF_MEMORY; + } + } + + 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); + + ppPorts = ((ma_jack_get_ports_proc)pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, NULL, 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) { + 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" */ + + 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); + 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; + } + + ((ma_jack_free_proc)pContext->jack.jack_free)((void*)ppPorts); + + pDevice->playback.internalBufferSizeInFrames = bufferSizeInFrames; + pDevice->playback.internalPeriods = periods; + + pDevice->jack.pIntermediaryBufferPlayback = (float*)ma_malloc((pDevice->playback.internalBufferSizeInFrames/pDevice->playback.internalPeriods) * (pDevice->playback.internalChannels * ma_get_bytes_per_sample(pDevice->playback.internalFormat))); + 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_src(pDevice->sampleRate, pDevice->capture.internalSampleRate, pDevice->capture.internalBufferSizeInFrames); + result = ma_pcm_rb_init(pDevice->capture.format, pDevice->capture.channels, rbSizeInFrames, NULL, &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); + } + } + + return MA_SUCCESS; +} + + +ma_result ma_device_start__jack(ma_device* pDevice) +{ + ma_context* pContext = pDevice->pContext; + int resultJACK; + size_t i; + + resultJACK = ((ma_jack_activate_proc)pContext->jack.jack_activate)((ma_jack_client_t*)pDevice->jack.pClient); + if (resultJACK != 0) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to activate the JACK client.", MA_FAILED_TO_START_BACKEND_DEVICE); + } + + if (pDevice->type == ma_device_type_capture || 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, NULL, ma_JackPortIsPhysical | ma_JackPortIsOutput); + if (ppServerPorts == NULL) { + ((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to retrieve physical ports.", MA_ERROR); + } + + for (i = 0; ppServerPorts[i] != NULL; ++i) { + const char* pServerPort = ppServerPorts[i]; + const char* pClientPort = ((ma_jack_port_name_proc)pContext->jack.jack_port_name)((ma_jack_port_t*)pDevice->jack.pPortsCapture[i]); + + resultJACK = ((ma_jack_connect_proc)pContext->jack.jack_connect)((ma_jack_client_t*)pDevice->jack.pClient, pServerPort, pClientPort); + if (resultJACK != 0) { + ((ma_jack_free_proc)pContext->jack.jack_free)((void*)ppServerPorts); + ((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to connect ports.", MA_ERROR); + } + } + + ((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, NULL, ma_JackPortIsPhysical | ma_JackPortIsInput); + if (ppServerPorts == NULL) { + ((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to retrieve physical ports.", MA_ERROR); + } + + for (i = 0; ppServerPorts[i] != NULL; ++i) { + const char* pServerPort = ppServerPorts[i]; + const char* pClientPort = ((ma_jack_port_name_proc)pContext->jack.jack_port_name)((ma_jack_port_t*)pDevice->jack.pPortsPlayback[i]); + + resultJACK = ((ma_jack_connect_proc)pContext->jack.jack_connect)((ma_jack_client_t*)pDevice->jack.pClient, pClientPort, pServerPort); + if (resultJACK != 0) { + ((ma_jack_free_proc)pContext->jack.jack_free)((void*)ppServerPorts); + ((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to connect ports.", MA_ERROR); + } + } + + ((ma_jack_free_proc)pContext->jack.jack_free)((void*)ppServerPorts); + } + + return MA_SUCCESS; +} + +ma_result ma_device_stop__jack(ma_device* pDevice) +{ + ma_context* pContext = pDevice->pContext; + ma_stop_proc onStop; + + 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); + } + + return MA_SUCCESS; +} + + +ma_result ma_context_uninit__jack(ma_context* pContext) +{ + ma_assert(pContext != NULL); + ma_assert(pContext->backend == ma_backend_jack); + + ma_free(pContext->jack.pClientName); + pContext->jack.pClientName = NULL; + +#ifndef MA_NO_RUNTIME_LINKING + ma_dlclose(pContext, pContext->jack.jackSO); +#endif + + return MA_SUCCESS; +} + +ma_result ma_context_init__jack(const ma_context_config* pConfig, ma_context* pContext) +{ +#ifndef MA_NO_RUNTIME_LINKING + const char* libjackNames[] = { +#ifdef MA_WIN32 + "libjack.dll" +#else + "libjack.so", + "libjack.so.0" +#endif + }; + size_t i; + + for (i = 0; i < ma_countof(libjackNames); ++i) { + pContext->jack.jackSO = ma_dlopen(pContext, libjackNames[i]); + if (pContext->jack.jackSO != NULL) { + break; + } + } + + if (pContext->jack.jackSO == NULL) { + return MA_NO_BACKEND; + } + + pContext->jack.jack_client_open = (ma_proc)ma_dlsym(pContext, pContext->jack.jackSO, "jack_client_open"); + pContext->jack.jack_client_close = (ma_proc)ma_dlsym(pContext, pContext->jack.jackSO, "jack_client_close"); + pContext->jack.jack_client_name_size = (ma_proc)ma_dlsym(pContext, pContext->jack.jackSO, "jack_client_name_size"); + pContext->jack.jack_set_process_callback = (ma_proc)ma_dlsym(pContext, pContext->jack.jackSO, "jack_set_process_callback"); + pContext->jack.jack_set_buffer_size_callback = (ma_proc)ma_dlsym(pContext, pContext->jack.jackSO, "jack_set_buffer_size_callback"); + pContext->jack.jack_on_shutdown = (ma_proc)ma_dlsym(pContext, pContext->jack.jackSO, "jack_on_shutdown"); + pContext->jack.jack_get_sample_rate = (ma_proc)ma_dlsym(pContext, pContext->jack.jackSO, "jack_get_sample_rate"); + pContext->jack.jack_get_buffer_size = (ma_proc)ma_dlsym(pContext, pContext->jack.jackSO, "jack_get_buffer_size"); + pContext->jack.jack_get_ports = (ma_proc)ma_dlsym(pContext, pContext->jack.jackSO, "jack_get_ports"); + pContext->jack.jack_activate = (ma_proc)ma_dlsym(pContext, pContext->jack.jackSO, "jack_activate"); + pContext->jack.jack_deactivate = (ma_proc)ma_dlsym(pContext, pContext->jack.jackSO, "jack_deactivate"); + pContext->jack.jack_connect = (ma_proc)ma_dlsym(pContext, pContext->jack.jackSO, "jack_connect"); + pContext->jack.jack_port_register = (ma_proc)ma_dlsym(pContext, pContext->jack.jackSO, "jack_port_register"); + pContext->jack.jack_port_name = (ma_proc)ma_dlsym(pContext, pContext->jack.jackSO, "jack_port_name"); + pContext->jack.jack_port_get_buffer = (ma_proc)ma_dlsym(pContext, pContext->jack.jackSO, "jack_port_get_buffer"); + pContext->jack.jack_free = (ma_proc)ma_dlsym(pContext, pContext->jack.jackSO, "jack_free"); +#else + /* + This strange assignment system is here just to ensure type safety of miniaudio's function pointer + types. If anything differs slightly the compiler should throw a warning. + */ + ma_jack_client_open_proc _jack_client_open = jack_client_open; + ma_jack_client_close_proc _jack_client_close = jack_client_close; + ma_jack_client_name_size_proc _jack_client_name_size = jack_client_name_size; + ma_jack_set_process_callback_proc _jack_set_process_callback = jack_set_process_callback; + ma_jack_set_buffer_size_callback_proc _jack_set_buffer_size_callback = jack_set_buffer_size_callback; + ma_jack_on_shutdown_proc _jack_on_shutdown = jack_on_shutdown; + ma_jack_get_sample_rate_proc _jack_get_sample_rate = jack_get_sample_rate; + ma_jack_get_buffer_size_proc _jack_get_buffer_size = jack_get_buffer_size; + ma_jack_get_ports_proc _jack_get_ports = jack_get_ports; + ma_jack_activate_proc _jack_activate = jack_activate; + ma_jack_deactivate_proc _jack_deactivate = jack_deactivate; + ma_jack_connect_proc _jack_connect = jack_connect; + ma_jack_port_register_proc _jack_port_register = jack_port_register; + ma_jack_port_name_proc _jack_port_name = jack_port_name; + ma_jack_port_get_buffer_proc _jack_port_get_buffer = jack_port_get_buffer; + ma_jack_free_proc _jack_free = jack_free; + + pContext->jack.jack_client_open = (ma_proc)_jack_client_open; + pContext->jack.jack_client_close = (ma_proc)_jack_client_close; + pContext->jack.jack_client_name_size = (ma_proc)_jack_client_name_size; + pContext->jack.jack_set_process_callback = (ma_proc)_jack_set_process_callback; + pContext->jack.jack_set_buffer_size_callback = (ma_proc)_jack_set_buffer_size_callback; + pContext->jack.jack_on_shutdown = (ma_proc)_jack_on_shutdown; + pContext->jack.jack_get_sample_rate = (ma_proc)_jack_get_sample_rate; + pContext->jack.jack_get_buffer_size = (ma_proc)_jack_get_buffer_size; + pContext->jack.jack_get_ports = (ma_proc)_jack_get_ports; + pContext->jack.jack_activate = (ma_proc)_jack_activate; + pContext->jack.jack_deactivate = (ma_proc)_jack_deactivate; + pContext->jack.jack_connect = (ma_proc)_jack_connect; + pContext->jack.jack_port_register = (ma_proc)_jack_port_register; + pContext->jack.jack_port_name = (ma_proc)_jack_port_name; + pContext->jack.jack_port_get_buffer = (ma_proc)_jack_port_get_buffer; + 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->jack.tryStartServer = pConfig->jack.tryStartServer; + + /* + Getting here means the JACK library is installed, but it doesn't necessarily mean it's usable. We need to quickly test this by connecting + a temporary client. + */ + { + ma_jack_client_t* pDummyClient; + ma_result result = ma_context_open_client__jack(pContext, &pDummyClient); + if (result != MA_SUCCESS) { + ma_free(pContext->jack.pClientName); + #ifndef MA_NO_RUNTIME_LINKING + ma_dlclose(pContext, pContext->jack.jackSO); + #endif + return MA_NO_BACKEND; + } + + ((ma_jack_client_close_proc)pContext->jack.jack_client_close)((ma_jack_client_t*)pDummyClient); + } + + return MA_SUCCESS; +} +#endif /* JACK */ + + + +/****************************************************************************** + +Core Audio Backend + +******************************************************************************/ +#ifdef MA_HAS_COREAUDIO +#include + +#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1 + #define MA_APPLE_MOBILE +#else + #define MA_APPLE_DESKTOP +#endif + +#if defined(MA_APPLE_DESKTOP) +#include +#else +#include +#endif + +#include + +/* CoreFoundation */ +typedef Boolean (* ma_CFStringGetCString_proc)(CFStringRef theString, char* buffer, CFIndex bufferSize, CFStringEncoding encoding); + +/* CoreAudio */ +#if defined(MA_APPLE_DESKTOP) +typedef OSStatus (* ma_AudioObjectGetPropertyData_proc)(AudioObjectID inObjectID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32* ioDataSize, void* outData); +typedef OSStatus (* ma_AudioObjectGetPropertyDataSize_proc)(AudioObjectID inObjectID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32* outDataSize); +typedef OSStatus (* ma_AudioObjectSetPropertyData_proc)(AudioObjectID inObjectID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData); +typedef OSStatus (* ma_AudioObjectAddPropertyListener_proc)(AudioObjectID inObjectID, const AudioObjectPropertyAddress* inAddress, AudioObjectPropertyListenerProc inListener, void* inClientData); +#endif + +/* AudioToolbox */ +typedef AudioComponent (* ma_AudioComponentFindNext_proc)(AudioComponent inComponent, const AudioComponentDescription* inDesc); +typedef OSStatus (* ma_AudioComponentInstanceDispose_proc)(AudioComponentInstance inInstance); +typedef OSStatus (* ma_AudioComponentInstanceNew_proc)(AudioComponent inComponent, AudioComponentInstance* outInstance); +typedef OSStatus (* ma_AudioOutputUnitStart_proc)(AudioUnit inUnit); +typedef OSStatus (* ma_AudioOutputUnitStop_proc)(AudioUnit inUnit); +typedef OSStatus (* ma_AudioUnitAddPropertyListener_proc)(AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitPropertyListenerProc inProc, void* inProcUserData); +typedef OSStatus (* ma_AudioUnitGetPropertyInfo_proc)(AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32* outDataSize, Boolean* outWriteable); +typedef OSStatus (* ma_AudioUnitGetProperty_proc)(AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void* outData, UInt32* ioDataSize); +typedef OSStatus (* ma_AudioUnitSetProperty_proc)(AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void* inData, UInt32 inDataSize); +typedef OSStatus (* ma_AudioUnitInitialize_proc)(AudioUnit inUnit); +typedef OSStatus (* ma_AudioUnitRender_proc)(AudioUnit inUnit, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inOutputBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData); + + +#define MA_COREAUDIO_OUTPUT_BUS 0 +#define MA_COREAUDIO_INPUT_BUS 1 + +ma_result ma_device_reinit_internal__coreaudio(ma_device* pDevice, ma_device_type deviceType, ma_bool32 disposePreviousAudioUnit); + +/* +Core Audio + +So far, Core Audio has been the worst backend to work with due to being both unintuitive and having almost no documentation +apart from comments in the headers (which admittedly are quite good). For my own purposes, and for anybody out there whose +needing to figure out how this darn thing works, I'm going to outline a few things here. + +Since miniaudio is a fairly low-level API, one of the things it needs is control over specific devices, and it needs to be +able to identify whether or not it can be used as playback and/or capture. The AudioObject API is the only one I've seen +that supports this level of detail. There was some public domain sample code I stumbled across that used the AudioComponent +and AudioUnit APIs, but I couldn't see anything that gave low-level control over device selection and capabilities (the +distinction between playback and capture in particular). Therefore, miniaudio is using the AudioObject API. + +Most (all?) functions in the AudioObject API take a AudioObjectID as it's input. This is the device identifier. When +retrieving global information, such as the device list, you use kAudioObjectSystemObject. When retrieving device-specific +data, you pass in the ID for that device. In order to retrieve device-specific IDs you need to enumerate over each of the +devices. This is done using the AudioObjectGetPropertyDataSize() and AudioObjectGetPropertyData() APIs which seem to be +the central APIs for retrieving information about the system and specific devices. + +To use the AudioObjectGetPropertyData() API you need to use the notion of a property address. A property address is a +structure with three variables and is used to identify which property you are getting or setting. The first is the "selector" +which is basically the specific property that you're wanting to retrieve or set. The second is the "scope", which is +typically set to kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyScopeInput for input-specific properties and +kAudioObjectPropertyScopeOutput for output-specific properties. The last is the "element" which is always set to +kAudioObjectPropertyElementMaster in miniaudio's case. I don't know of any cases where this would be set to anything different. + +Back to the earlier issue of device retrieval, you first use the AudioObjectGetPropertyDataSize() API to retrieve the size +of the raw data which is just a list of AudioDeviceID's. You use the kAudioObjectSystemObject AudioObjectID, and a property +address with the kAudioHardwarePropertyDevices selector and the kAudioObjectPropertyScopeGlobal scope. Once you have the +size, allocate a block of memory of that size and then call AudioObjectGetPropertyData(). The data is just a list of +AudioDeviceID's so just do "dataSize/sizeof(AudioDeviceID)" to know the device count. +*/ + +ma_result ma_result_from_OSStatus(OSStatus status) +{ + switch (status) + { + case noErr: return MA_SUCCESS; + #if defined(MA_APPLE_DESKTOP) + case kAudioHardwareNotRunningError: return MA_DEVICE_NOT_STARTED; + case kAudioHardwareUnspecifiedError: return MA_ERROR; + case kAudioHardwareUnknownPropertyError: return MA_INVALID_ARGS; + case kAudioHardwareBadPropertySizeError: return MA_INVALID_OPERATION; + case kAudioHardwareIllegalOperationError: return MA_INVALID_OPERATION; + case kAudioHardwareBadObjectError: return MA_INVALID_ARGS; + case kAudioHardwareBadDeviceError: return MA_INVALID_ARGS; + case kAudioHardwareBadStreamError: return MA_INVALID_ARGS; + case kAudioHardwareUnsupportedOperationError: return MA_INVALID_OPERATION; + case kAudioDeviceUnsupportedFormatError: return MA_FORMAT_NOT_SUPPORTED; + case kAudioDevicePermissionsError: return MA_ACCESS_DENIED; + #endif + default: return MA_ERROR; + } +} + +#if 0 +ma_channel ma_channel_from_AudioChannelBitmap(AudioChannelBitmap bit) +{ + switch (bit) + { + case kAudioChannelBit_Left: return MA_CHANNEL_LEFT; + case kAudioChannelBit_Right: return MA_CHANNEL_RIGHT; + case kAudioChannelBit_Center: return MA_CHANNEL_FRONT_CENTER; + case kAudioChannelBit_LFEScreen: return MA_CHANNEL_LFE; + case kAudioChannelBit_LeftSurround: return MA_CHANNEL_BACK_LEFT; + case kAudioChannelBit_RightSurround: return MA_CHANNEL_BACK_RIGHT; + case kAudioChannelBit_LeftCenter: return MA_CHANNEL_FRONT_LEFT_CENTER; + case kAudioChannelBit_RightCenter: return MA_CHANNEL_FRONT_RIGHT_CENTER; + case kAudioChannelBit_CenterSurround: return MA_CHANNEL_BACK_CENTER; + case kAudioChannelBit_LeftSurroundDirect: return MA_CHANNEL_SIDE_LEFT; + case kAudioChannelBit_RightSurroundDirect: return MA_CHANNEL_SIDE_RIGHT; + case kAudioChannelBit_TopCenterSurround: return MA_CHANNEL_TOP_CENTER; + case kAudioChannelBit_VerticalHeightLeft: return MA_CHANNEL_TOP_FRONT_LEFT; + case kAudioChannelBit_VerticalHeightCenter: return MA_CHANNEL_TOP_FRONT_CENTER; + case kAudioChannelBit_VerticalHeightRight: return MA_CHANNEL_TOP_FRONT_RIGHT; + case kAudioChannelBit_TopBackLeft: return MA_CHANNEL_TOP_BACK_LEFT; + case kAudioChannelBit_TopBackCenter: return MA_CHANNEL_TOP_BACK_CENTER; + case kAudioChannelBit_TopBackRight: return MA_CHANNEL_TOP_BACK_RIGHT; + default: return MA_CHANNEL_NONE; + } +} +#endif + +ma_channel ma_channel_from_AudioChannelLabel(AudioChannelLabel label) +{ + switch (label) + { + case kAudioChannelLabel_Unknown: return MA_CHANNEL_NONE; + case kAudioChannelLabel_Unused: return MA_CHANNEL_NONE; + case kAudioChannelLabel_UseCoordinates: return MA_CHANNEL_NONE; + case kAudioChannelLabel_Left: return MA_CHANNEL_LEFT; + case kAudioChannelLabel_Right: return MA_CHANNEL_RIGHT; + case kAudioChannelLabel_Center: return MA_CHANNEL_FRONT_CENTER; + case kAudioChannelLabel_LFEScreen: return MA_CHANNEL_LFE; + case kAudioChannelLabel_LeftSurround: return MA_CHANNEL_BACK_LEFT; + case kAudioChannelLabel_RightSurround: return MA_CHANNEL_BACK_RIGHT; + case kAudioChannelLabel_LeftCenter: return MA_CHANNEL_FRONT_LEFT_CENTER; + case kAudioChannelLabel_RightCenter: return MA_CHANNEL_FRONT_RIGHT_CENTER; + case kAudioChannelLabel_CenterSurround: return MA_CHANNEL_BACK_CENTER; + case kAudioChannelLabel_LeftSurroundDirect: return MA_CHANNEL_SIDE_LEFT; + case kAudioChannelLabel_RightSurroundDirect: return MA_CHANNEL_SIDE_RIGHT; + case kAudioChannelLabel_TopCenterSurround: return MA_CHANNEL_TOP_CENTER; + case kAudioChannelLabel_VerticalHeightLeft: return MA_CHANNEL_TOP_FRONT_LEFT; + case kAudioChannelLabel_VerticalHeightCenter: return MA_CHANNEL_TOP_FRONT_CENTER; + case kAudioChannelLabel_VerticalHeightRight: return MA_CHANNEL_TOP_FRONT_RIGHT; + case kAudioChannelLabel_TopBackLeft: return MA_CHANNEL_TOP_BACK_LEFT; + case kAudioChannelLabel_TopBackCenter: return MA_CHANNEL_TOP_BACK_CENTER; + case kAudioChannelLabel_TopBackRight: return MA_CHANNEL_TOP_BACK_RIGHT; + case kAudioChannelLabel_RearSurroundLeft: return MA_CHANNEL_BACK_LEFT; + case kAudioChannelLabel_RearSurroundRight: return MA_CHANNEL_BACK_RIGHT; + case kAudioChannelLabel_LeftWide: return MA_CHANNEL_SIDE_LEFT; + case kAudioChannelLabel_RightWide: return MA_CHANNEL_SIDE_RIGHT; + case kAudioChannelLabel_LFE2: return MA_CHANNEL_LFE; + case kAudioChannelLabel_LeftTotal: return MA_CHANNEL_LEFT; + case kAudioChannelLabel_RightTotal: return MA_CHANNEL_RIGHT; + case kAudioChannelLabel_HearingImpaired: return MA_CHANNEL_NONE; + case kAudioChannelLabel_Narration: return MA_CHANNEL_MONO; + case kAudioChannelLabel_Mono: return MA_CHANNEL_MONO; + case kAudioChannelLabel_DialogCentricMix: return MA_CHANNEL_MONO; + case kAudioChannelLabel_CenterSurroundDirect: return MA_CHANNEL_BACK_CENTER; + case kAudioChannelLabel_Haptic: return MA_CHANNEL_NONE; + case kAudioChannelLabel_Ambisonic_W: return MA_CHANNEL_NONE; + case kAudioChannelLabel_Ambisonic_X: return MA_CHANNEL_NONE; + case kAudioChannelLabel_Ambisonic_Y: return MA_CHANNEL_NONE; + case kAudioChannelLabel_Ambisonic_Z: return MA_CHANNEL_NONE; + case kAudioChannelLabel_MS_Mid: return MA_CHANNEL_LEFT; + case kAudioChannelLabel_MS_Side: return MA_CHANNEL_RIGHT; + case kAudioChannelLabel_XY_X: return MA_CHANNEL_LEFT; + case kAudioChannelLabel_XY_Y: return MA_CHANNEL_RIGHT; + case kAudioChannelLabel_HeadphonesLeft: return MA_CHANNEL_LEFT; + case kAudioChannelLabel_HeadphonesRight: return MA_CHANNEL_RIGHT; + case kAudioChannelLabel_ClickTrack: return MA_CHANNEL_NONE; + case kAudioChannelLabel_ForeignLanguage: return MA_CHANNEL_NONE; + case kAudioChannelLabel_Discrete: return MA_CHANNEL_NONE; + case kAudioChannelLabel_Discrete_0: return MA_CHANNEL_AUX_0; + case kAudioChannelLabel_Discrete_1: return MA_CHANNEL_AUX_1; + case kAudioChannelLabel_Discrete_2: return MA_CHANNEL_AUX_2; + case kAudioChannelLabel_Discrete_3: return MA_CHANNEL_AUX_3; + case kAudioChannelLabel_Discrete_4: return MA_CHANNEL_AUX_4; + case kAudioChannelLabel_Discrete_5: return MA_CHANNEL_AUX_5; + case kAudioChannelLabel_Discrete_6: return MA_CHANNEL_AUX_6; + case kAudioChannelLabel_Discrete_7: return MA_CHANNEL_AUX_7; + case kAudioChannelLabel_Discrete_8: return MA_CHANNEL_AUX_8; + case kAudioChannelLabel_Discrete_9: return MA_CHANNEL_AUX_9; + case kAudioChannelLabel_Discrete_10: return MA_CHANNEL_AUX_10; + case kAudioChannelLabel_Discrete_11: return MA_CHANNEL_AUX_11; + case kAudioChannelLabel_Discrete_12: return MA_CHANNEL_AUX_12; + case kAudioChannelLabel_Discrete_13: return MA_CHANNEL_AUX_13; + 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; + case kAudioChannelLabel_HOA_ACN_1: return MA_CHANNEL_AUX_1; + case kAudioChannelLabel_HOA_ACN_2: return MA_CHANNEL_AUX_2; + case kAudioChannelLabel_HOA_ACN_3: return MA_CHANNEL_AUX_3; + case kAudioChannelLabel_HOA_ACN_4: return MA_CHANNEL_AUX_4; + case kAudioChannelLabel_HOA_ACN_5: return MA_CHANNEL_AUX_5; + case kAudioChannelLabel_HOA_ACN_6: return MA_CHANNEL_AUX_6; + case kAudioChannelLabel_HOA_ACN_7: return MA_CHANNEL_AUX_7; + case kAudioChannelLabel_HOA_ACN_8: return MA_CHANNEL_AUX_8; + case kAudioChannelLabel_HOA_ACN_9: return MA_CHANNEL_AUX_9; + case kAudioChannelLabel_HOA_ACN_10: return MA_CHANNEL_AUX_10; + case kAudioChannelLabel_HOA_ACN_11: return MA_CHANNEL_AUX_11; + case kAudioChannelLabel_HOA_ACN_12: return MA_CHANNEL_AUX_12; + case kAudioChannelLabel_HOA_ACN_13: return MA_CHANNEL_AUX_13; + case kAudioChannelLabel_HOA_ACN_14: return MA_CHANNEL_AUX_14; + 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; + } +} + +ma_result ma_format_from_AudioStreamBasicDescription(const AudioStreamBasicDescription* pDescription, ma_format* pFormatOut) +{ + 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; + } + + /* Only supporting native-endian. */ + 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; + }*/ + + if ((pDescription->mFormatFlags & kLinearPCMFormatFlagIsFloat) != 0) { + if (pDescription->mBitsPerChannel == 32) { + *pFormatOut = ma_format_f32; + return MA_SUCCESS; + } + } else { + if ((pDescription->mFormatFlags & kLinearPCMFormatFlagIsSignedInteger) != 0) { + if (pDescription->mBitsPerChannel == 16) { + *pFormatOut = ma_format_s16; + return MA_SUCCESS; + } else if (pDescription->mBitsPerChannel == 24) { + if (pDescription->mBytesPerFrame == (pDescription->mBitsPerChannel/8 * pDescription->mChannelsPerFrame)) { + *pFormatOut = ma_format_s24; + return MA_SUCCESS; + } else { + if (pDescription->mBytesPerFrame/pDescription->mChannelsPerFrame == sizeof(ma_int32)) { + /* TODO: Implement ma_format_s24_32. */ + /**pFormatOut = ma_format_s24_32;*/ + /*return MA_SUCCESS;*/ + return MA_FORMAT_NOT_SUPPORTED; + } + } + } else if (pDescription->mBitsPerChannel == 32) { + *pFormatOut = ma_format_s32; + return MA_SUCCESS; + } + } else { + if (pDescription->mBitsPerChannel == 8) { + *pFormatOut = ma_format_u8; + return MA_SUCCESS; + } + } + } + + /* Getting here means the format is not supported. */ + return MA_FORMAT_NOT_SUPPORTED; +} + +ma_result ma_get_channel_map_from_AudioChannelLayout(AudioChannelLayout* pChannelLayout, ma_channel channelMap[MA_MAX_CHANNELS]) +{ + ma_assert(pChannelLayout != NULL); + + if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) { + UInt32 iChannel; + for (iChannel = 0; iChannel < pChannelLayout->mNumberChannelDescriptions; ++iChannel) { + channelMap[iChannel] = ma_channel_from_AudioChannelLabel(pChannelLayout->mChannelDescriptions[iChannel].mChannelLabel); + } + } else +#if 0 + if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap) { + /* This is the same kind of system that's used by Windows audio APIs. */ + UInt32 iChannel = 0; + UInt32 iBit; + AudioChannelBitmap bitmap = pChannelLayout->mChannelBitmap; + for (iBit = 0; iBit < 32; ++iBit) { + AudioChannelBitmap bit = bitmap & (1 << iBit); + if (bit != 0) { + channelMap[iChannel++] = ma_channel_from_AudioChannelBit(bit); + } + } + } else +#endif + { + /* + 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 = AudioChannelLayoutTag_GetNumberOfChannels(pChannelLayout->mChannelLayoutTag); + switch (pChannelLayout->mChannelLayoutTag) + { + case kAudioChannelLayoutTag_Mono: + case kAudioChannelLayoutTag_Stereo: + case kAudioChannelLayoutTag_StereoHeadphones: + case kAudioChannelLayoutTag_MatrixStereo: + case kAudioChannelLayoutTag_MidSide: + case kAudioChannelLayoutTag_XY: + case kAudioChannelLayoutTag_Binaural: + case kAudioChannelLayoutTag_Ambisonic_B_Format: + { + ma_get_standard_channel_map(ma_standard_channel_map_default, channelCount, channelMap); + } break; + + case kAudioChannelLayoutTag_Octagonal: + { + channelMap[7] = MA_CHANNEL_SIDE_RIGHT; + channelMap[6] = MA_CHANNEL_SIDE_LEFT; + } /* Intentional fallthrough. */ + case kAudioChannelLayoutTag_Hexagonal: + { + channelMap[5] = MA_CHANNEL_BACK_CENTER; + } /* Intentional fallthrough. */ + case kAudioChannelLayoutTag_Pentagonal: + { + channelMap[4] = MA_CHANNEL_FRONT_CENTER; + } /* Intentional fallghrough. */ + case kAudioChannelLayoutTag_Quadraphonic: + { + channelMap[3] = MA_CHANNEL_BACK_RIGHT; + channelMap[2] = MA_CHANNEL_BACK_LEFT; + channelMap[1] = MA_CHANNEL_RIGHT; + channelMap[0] = MA_CHANNEL_LEFT; + } break; + + /* TODO: Add support for more tags here. */ + + default: + { + ma_get_standard_channel_map(ma_standard_channel_map_default, channelCount, channelMap); + } break; + } + } + + return MA_SUCCESS; +} + + +#if defined(MA_APPLE_DESKTOP) +ma_result ma_get_device_object_ids__coreaudio(ma_context* pContext, UInt32* pDeviceCount, AudioObjectID** ppDeviceObjectIDs) /* NOTE: Free the returned buffer with ma_free(). */ +{ + AudioObjectPropertyAddress propAddressDevices; + UInt32 deviceObjectsDataSize; + OSStatus status; + AudioObjectID* pDeviceObjectIDs; + + ma_assert(pContext != NULL); + ma_assert(pDeviceCount != NULL); + ma_assert(ppDeviceObjectIDs != NULL); + + /* Safety. */ + *pDeviceCount = 0; + *ppDeviceObjectIDs = NULL; + + propAddressDevices.mSelector = kAudioHardwarePropertyDevices; + propAddressDevices.mScope = kAudioObjectPropertyScopeGlobal; + propAddressDevices.mElement = kAudioObjectPropertyElementMaster; + + status = ((ma_AudioObjectGetPropertyDataSize_proc)pContext->coreaudio.AudioObjectGetPropertyDataSize)(kAudioObjectSystemObject, &propAddressDevices, 0, NULL, &deviceObjectsDataSize); + if (status != noErr) { + return ma_result_from_OSStatus(status); + } + + pDeviceObjectIDs = (AudioObjectID*)ma_malloc(deviceObjectsDataSize); + 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); + return ma_result_from_OSStatus(status); + } + + *pDeviceCount = deviceObjectsDataSize / sizeof(AudioObjectID); + *ppDeviceObjectIDs = pDeviceObjectIDs; + + (void)pContext; /* Unused. */ + return MA_SUCCESS; +} + +ma_result ma_get_AudioObject_uid_as_CFStringRef(ma_context* pContext, AudioObjectID objectID, CFStringRef* pUID) +{ + AudioObjectPropertyAddress propAddress; + UInt32 dataSize; + OSStatus status; + + ma_assert(pContext != NULL); + + propAddress.mSelector = kAudioDevicePropertyDeviceUID; + propAddress.mScope = kAudioObjectPropertyScopeGlobal; + propAddress.mElement = kAudioObjectPropertyElementMaster; + + dataSize = sizeof(*pUID); + status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(objectID, &propAddress, 0, NULL, &dataSize, pUID); + if (status != noErr) { + return ma_result_from_OSStatus(status); + } + + return MA_SUCCESS; +} + +ma_result ma_get_AudioObject_uid(ma_context* pContext, AudioObjectID objectID, size_t bufferSize, char* bufferOut) +{ + CFStringRef uid; + ma_result result; + + ma_assert(pContext != NULL); + + result = ma_get_AudioObject_uid_as_CFStringRef(pContext, objectID, &uid); + if (result != MA_SUCCESS) { + return result; + } + + if (!((ma_CFStringGetCString_proc)pContext->coreaudio.CFStringGetCString)(uid, bufferOut, bufferSize, kCFStringEncodingUTF8)) { + return MA_ERROR; + } + + return MA_SUCCESS; +} + +ma_result ma_get_AudioObject_name(ma_context* pContext, AudioObjectID objectID, size_t bufferSize, char* bufferOut) +{ + AudioObjectPropertyAddress propAddress; + CFStringRef deviceName = NULL; + UInt32 dataSize; + OSStatus status; + + ma_assert(pContext != NULL); + + propAddress.mSelector = kAudioDevicePropertyDeviceNameCFString; + propAddress.mScope = kAudioObjectPropertyScopeGlobal; + propAddress.mElement = kAudioObjectPropertyElementMaster; + + dataSize = sizeof(deviceName); + status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(objectID, &propAddress, 0, NULL, &dataSize, &deviceName); + if (status != noErr) { + return ma_result_from_OSStatus(status); + } + + if (!((ma_CFStringGetCString_proc)pContext->coreaudio.CFStringGetCString)(deviceName, bufferOut, bufferSize, kCFStringEncodingUTF8)) { + return MA_ERROR; + } + + return MA_SUCCESS; +} + +ma_bool32 ma_does_AudioObject_support_scope(ma_context* pContext, AudioObjectID deviceObjectID, AudioObjectPropertyScope scope) +{ + AudioObjectPropertyAddress propAddress; + UInt32 dataSize; + OSStatus status; + AudioBufferList* pBufferList; + ma_bool32 isSupported; + + ma_assert(pContext != NULL); + + /* To know whether or not a device is an input device we need ot look at the stream configuration. If it has an output channel it's a playback device. */ + 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(dataSize); + 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(pBufferList); + return MA_FALSE; + } + + isSupported = MA_FALSE; + if (pBufferList->mNumberBuffers > 0) { + isSupported = MA_TRUE; + } + + ma_free(pBufferList); + return isSupported; +} + +ma_bool32 ma_does_AudioObject_support_playback(ma_context* pContext, AudioObjectID deviceObjectID) +{ + return ma_does_AudioObject_support_scope(pContext, deviceObjectID, kAudioObjectPropertyScopeOutput); +} + +ma_bool32 ma_does_AudioObject_support_capture(ma_context* pContext, AudioObjectID deviceObjectID) +{ + return ma_does_AudioObject_support_scope(pContext, deviceObjectID, kAudioObjectPropertyScopeInput); +} + + +ma_result ma_get_AudioObject_stream_descriptions(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, UInt32* pDescriptionCount, AudioStreamRangedDescription** ppDescriptions) /* NOTE: Free the returned pointer with ma_free(). */ +{ + AudioObjectPropertyAddress propAddress; + UInt32 dataSize; + OSStatus status; + AudioStreamRangedDescription* pDescriptions; + + 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. + */ + 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); + 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); + return ma_result_from_OSStatus(status); + } + + *pDescriptionCount = dataSize / sizeof(*pDescriptions); + *ppDescriptions = pDescriptions; + return MA_SUCCESS; +} + + +ma_result ma_get_AudioObject_channel_layout(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, AudioChannelLayout** ppChannelLayout) /* NOTE: Free the returned pointer with ma_free(). */ +{ + AudioObjectPropertyAddress propAddress; + UInt32 dataSize; + OSStatus status; + AudioChannelLayout* pChannelLayout; + + 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); + 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); + return ma_result_from_OSStatus(status); + } + + *ppChannelLayout = pChannelLayout; + return MA_SUCCESS; +} + +ma_result ma_get_AudioObject_channel_count(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, ma_uint32* pChannelCount) +{ + AudioChannelLayout* pChannelLayout; + ma_result result; + + 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) { + *pChannelCount = ma_count_set_bits(pChannelLayout->mChannelBitmap); + } else { + *pChannelCount = AudioChannelLayoutTag_GetNumberOfChannels(pChannelLayout->mChannelLayoutTag); + } + + ma_free(pChannelLayout); + return MA_SUCCESS; +} + +ma_result ma_get_AudioObject_channel_map(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, ma_channel channelMap[MA_MAX_CHANNELS]) +{ + AudioChannelLayout* pChannelLayout; + 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, channelMap); + if (result != MA_SUCCESS) { + ma_free(pChannelLayout); + return result; + } + + ma_free(pChannelLayout); + return result; +} + +ma_result ma_get_AudioObject_sample_rates(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, UInt32* pSampleRateRangesCount, AudioValueRange** ppSampleRateRanges) /* NOTE: Free the returned pointer with ma_free(). */ +{ + AudioObjectPropertyAddress propAddress; + UInt32 dataSize; + OSStatus status; + AudioValueRange* pSampleRateRanges; + + 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); + 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); + return ma_result_from_OSStatus(status); + } + + *pSampleRateRangesCount = dataSize / sizeof(*pSampleRateRanges); + *ppSampleRateRanges = pSampleRateRanges; + return MA_SUCCESS; +} + +ma_result ma_get_AudioObject_get_closest_sample_rate(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, ma_uint32 sampleRateIn, ma_uint32* pSampleRateOut) +{ + UInt32 sampleRateRangeCount; + AudioValueRange* pSampleRateRanges; + ma_result result; + + 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); + return MA_ERROR; /* Should never hit this case should we? */ + } + + if (sampleRateIn == 0) { + /* Search in order of miniaudio's preferred priority. */ + UInt32 iMALSampleRate; + for (iMALSampleRate = 0; iMALSampleRate < ma_countof(g_maStandardSampleRatePriorities); ++iMALSampleRate) { + ma_uint32 malSampleRate = g_maStandardSampleRatePriorities[iMALSampleRate]; + UInt32 iCASampleRate; + for (iCASampleRate = 0; iCASampleRate < sampleRateRangeCount; ++iCASampleRate) { + AudioValueRange caSampleRate = pSampleRateRanges[iCASampleRate]; + if (caSampleRate.mMinimum <= malSampleRate && caSampleRate.mMaximum >= malSampleRate) { + *pSampleRateOut = malSampleRate; + ma_free(pSampleRateRanges); + return MA_SUCCESS; + } + } + } + + /* + 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); + return MA_SUCCESS; + } else { + /* Find the closest match to this sample rate. */ + UInt32 currentAbsoluteDifference = INT32_MAX; + UInt32 iCurrentClosestRange = (UInt32)-1; + UInt32 iRange; + for (iRange = 0; iRange < sampleRateRangeCount; ++iRange) { + if (pSampleRateRanges[iRange].mMinimum <= sampleRateIn && pSampleRateRanges[iRange].mMaximum >= sampleRateIn) { + *pSampleRateOut = sampleRateIn; + ma_free(pSampleRateRanges); + return MA_SUCCESS; + } else { + UInt32 absoluteDifference; + if (pSampleRateRanges[iRange].mMinimum > sampleRateIn) { + absoluteDifference = pSampleRateRanges[iRange].mMinimum - sampleRateIn; + } 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); + return MA_SUCCESS; + } + + /* Should never get here, but it would mean we weren't able to find any suitable sample rates. */ + /*ma_free(pSampleRateRanges);*/ + /*return MA_ERROR;*/ +} + + +ma_result ma_get_AudioObject_closest_buffer_size_in_frames(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, ma_uint32 bufferSizeInFramesIn, ma_uint32* pBufferSizeInFramesOut) +{ + AudioObjectPropertyAddress propAddress; + AudioValueRange bufferSizeRange; + UInt32 dataSize; + OSStatus status; + + 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; + + dataSize = sizeof(bufferSizeRange); + status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, &bufferSizeRange); + if (status != noErr) { + return ma_result_from_OSStatus(status); + } + + /* This is just a clamp. */ + if (bufferSizeInFramesIn < bufferSizeRange.mMinimum) { + *pBufferSizeInFramesOut = (ma_uint32)bufferSizeRange.mMinimum; + } else if (bufferSizeInFramesIn > bufferSizeRange.mMaximum) { + *pBufferSizeInFramesOut = (ma_uint32)bufferSizeRange.mMaximum; + } else { + *pBufferSizeInFramesOut = bufferSizeInFramesIn; + } + + return MA_SUCCESS; +} + +ma_result ma_set_AudioObject_buffer_size_in_frames(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, ma_uint32* pBufferSizeInOut) +{ + ma_result result; + ma_uint32 chosenBufferSizeInFrames; + AudioObjectPropertyAddress propAddress; + UInt32 dataSize; + OSStatus status; + + ma_assert(pContext != NULL); + + result = ma_get_AudioObject_closest_buffer_size_in_frames(pContext, deviceObjectID, deviceType, *pBufferSizeInOut, &chosenBufferSizeInFrames); + if (result != MA_SUCCESS) { + return result; + } + + /* Try setting the size of the buffer... If this fails we just use whatever is currently set. */ + 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(*pBufferSizeInOut); + status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, &chosenBufferSizeInFrames); + if (status != noErr) { + return ma_result_from_OSStatus(status); + } + + *pBufferSizeInOut = chosenBufferSizeInFrames; + return MA_SUCCESS; +} + + +ma_result ma_find_AudioObjectID(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, AudioObjectID* pDeviceObjectID) +{ + ma_assert(pContext != NULL); + ma_assert(pDeviceObjectID != NULL); + + /* 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; + } + } else { + /* Explicit device. */ + UInt32 deviceCount; + AudioObjectID* pDeviceObjectIDs; + ma_result result; + UInt32 iDevice; + + 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]; + + 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) { + *pDeviceObjectID = deviceObjectID; + return MA_SUCCESS; + } + } + } else { + if (ma_does_AudioObject_support_capture(pContext, deviceObjectID)) { + if (strcmp(uid, pDeviceID->coreaudio) == 0) { + *pDeviceObjectID = deviceObjectID; + return MA_SUCCESS; + } + } + } + } + } + + /* If we get here it means we couldn't find the device. */ + return MA_NO_DEVICE; +} + + +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) +{ + UInt32 deviceFormatDescriptionCount; + AudioStreamRangedDescription* pDeviceFormatDescriptions; + ma_result result; + ma_uint32 desiredSampleRate; + ma_uint32 desiredChannelCount; + ma_format desiredFormat; + AudioStreamBasicDescription bestDeviceFormatSoFar; + ma_bool32 hasSupportedFormat; + UInt32 iFormat; + + result = ma_get_AudioObject_stream_descriptions(pContext, deviceObjectID, deviceType, &deviceFormatDescriptionCount, &pDeviceFormatDescriptions); + 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; + } + } + } + + 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; + ma_result formatResult = ma_format_from_AudioStreamBasicDescription(&pDeviceFormatDescriptions[iFormat].mFormat, &format); + if (formatResult == MA_SUCCESS && format != ma_format_unknown) { + hasSupportedFormat = MA_TRUE; + bestDeviceFormatSoFar = pDeviceFormatDescriptions[iFormat].mFormat; + break; + } + } + + if (!hasSupportedFormat) { + return MA_FORMAT_NOT_SUPPORTED; + } + + + for (iFormat = 0; iFormat < deviceFormatDescriptionCount; ++iFormat) { + AudioStreamBasicDescription thisDeviceFormat = pDeviceFormatDescriptions[iFormat].mFormat; + ma_format thisSampleFormat; + ma_result formatResult; + ma_format bestSampleFormatSoFar; + + /* If the format is not supported by miniaudio we need to skip this one entirely. */ + formatResult = ma_format_from_AudioStreamBasicDescription(&pDeviceFormatDescriptions[iFormat].mFormat, &thisSampleFormat); + 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) { + /* + The sample rate does not match, but this format could still be usable, although it's a very low priority. If the best format + so far has an equal sample rate we can just ignore this one. + */ + if (bestDeviceFormatSoFar.mSampleRate == desiredSampleRate) { + continue; /* The best sample rate so far has the same sample rate as what we requested which means it's still the best so far. Skip this format. */ + } else { + /* In this case, neither the best format so far nor this one have the same sample rate. Check the channel count next. */ + if (thisDeviceFormat.mChannelsPerFrame != desiredChannelCount) { + /* This format has a different sample rate _and_ a different channel count. */ + if (bestDeviceFormatSoFar.mChannelsPerFrame == desiredChannelCount) { + continue; /* No change to the best format. */ + } else { + /* + Both this format and the best so far have different sample rates and different channel counts. Whichever has the + best format is the new best. + */ + if (ma_get_format_priority_index(thisSampleFormat) < ma_get_format_priority_index(bestSampleFormatSoFar)) { + bestDeviceFormatSoFar = thisDeviceFormat; + continue; + } else { + continue; /* No change to the best format. */ + } + } + } else { + /* This format has a different sample rate but the desired channel count. */ + if (bestDeviceFormatSoFar.mChannelsPerFrame == desiredChannelCount) { + /* Both this format and the best so far have the desired channel count. Whichever has the best format is the new best. */ + if (ma_get_format_priority_index(thisSampleFormat) < ma_get_format_priority_index(bestSampleFormatSoFar)) { + bestDeviceFormatSoFar = thisDeviceFormat; + continue; + } else { + continue; /* No change to the best format for now. */ + } + } else { + /* This format has the desired channel count, but the best so far does not. We have a new best. */ + bestDeviceFormatSoFar = thisDeviceFormat; + continue; + } + } + } + } else { + /* + The sample rates match which makes this format a very high priority contender. If the best format so far has a different + sample rate it needs to be replaced with this one. + */ + if (bestDeviceFormatSoFar.mSampleRate != desiredSampleRate) { + bestDeviceFormatSoFar = thisDeviceFormat; + continue; + } else { + /* In this case both this format and the best format so far have the same sample rate. Check the channel count next. */ + if (thisDeviceFormat.mChannelsPerFrame == desiredChannelCount) { + /* + In this case this format has the same channel count as what the client is requesting. If the best format so far has + a different count, this one becomes the new best. + */ + if (bestDeviceFormatSoFar.mChannelsPerFrame != desiredChannelCount) { + bestDeviceFormatSoFar = thisDeviceFormat; + continue; + } else { + /* In this case both this format and the best so far have the ideal sample rate and channel count. Check the format. */ + if (thisSampleFormat == desiredFormat) { + bestDeviceFormatSoFar = thisDeviceFormat; + break; /* Found the exact match. */ + } else { + /* The formats are different. The new best format is the one with the highest priority format according to miniaudio. */ + if (ma_get_format_priority_index(thisSampleFormat) < ma_get_format_priority_index(bestSampleFormatSoFar)) { + bestDeviceFormatSoFar = thisDeviceFormat; + continue; + } else { + continue; /* No change to the best format for now. */ + } + } + } + } else { + /* + In this case the channel count is different to what the client has requested. If the best so far has the same channel + count as the requested count then it remains the best. + */ + if (bestDeviceFormatSoFar.mChannelsPerFrame == desiredChannelCount) { + continue; + } else { + /* + This is the case where both have the same sample rate (good) but different channel counts. Right now both have about + the same priority, but we need to compare the format now. + */ + if (thisSampleFormat == bestSampleFormatSoFar) { + if (ma_get_format_priority_index(thisSampleFormat) < ma_get_format_priority_index(bestSampleFormatSoFar)) { + bestDeviceFormatSoFar = thisDeviceFormat; + continue; + } else { + continue; /* No change to the best format for now. */ + } + } + } + } + } + } + } + + *pFormat = bestDeviceFormatSoFar; + return MA_SUCCESS; +} +#endif + +ma_result ma_get_AudioUnit_channel_map(ma_context* pContext, AudioUnit audioUnit, ma_device_type deviceType, ma_channel channelMap[MA_MAX_CHANNELS]) +{ + AudioUnitScope deviceScope; + AudioUnitElement deviceBus; + UInt32 channelLayoutSize; + OSStatus status; + AudioChannelLayout* pChannelLayout; + ma_result result; + + ma_assert(pContext != NULL); + + if (deviceType == ma_device_type_playback) { + deviceScope = kAudioUnitScope_Output; + deviceBus = MA_COREAUDIO_OUTPUT_BUS; + } else { + deviceScope = kAudioUnitScope_Input; + 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(channelLayoutSize); + 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(pChannelLayout); + return ma_result_from_OSStatus(status); + } + + result = ma_get_channel_map_from_AudioChannelLayout(pChannelLayout, channelMap); + if (result != MA_SUCCESS) { + ma_free(pChannelLayout); + return result; + } + + ma_free(pChannelLayout); + return MA_SUCCESS; +} + +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; +} + +ma_result ma_context_enumerate_devices__coreaudio(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ +#if defined(MA_APPLE_DESKTOP) + UInt32 deviceCount; + AudioObjectID* pDeviceObjectIDs; + ma_result result; + UInt32 iDevice; + + 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; + + ma_zero_object(&info); + if (ma_get_AudioObject_uid(pContext, deviceObjectID, sizeof(info.id.coreaudio), info.id.coreaudio) != MA_SUCCESS) { + continue; + } + if (ma_get_AudioObject_name(pContext, deviceObjectID, sizeof(info.name), info.name) != MA_SUCCESS) { + continue; + } + + if (ma_does_AudioObject_support_playback(pContext, deviceObjectID)) { + if (!callback(pContext, ma_device_type_playback, &info, pUserData)) { + break; + } + } + if (ma_does_AudioObject_support_capture(pContext, deviceObjectID)) { + if (!callback(pContext, ma_device_type_capture, &info, pUserData)) { + break; + } + } + } + + ma_free(pDeviceObjectIDs); +#else + /* Only supporting default devices on non-Desktop platforms. */ + ma_device_info info; + + ma_zero_object(&info); + ma_strncpy_s(info.name, sizeof(info.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + if (!callback(pContext, ma_device_type_playback, &info, pUserData)) { + return MA_SUCCESS; + } + + ma_zero_object(&info); + ma_strncpy_s(info.name, sizeof(info.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + if (!callback(pContext, ma_device_type_capture, &info, pUserData)) { + return MA_SUCCESS; + } +#endif + + return MA_SUCCESS; +} + +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) +{ + 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; + UInt32 streamDescriptionCount; + AudioStreamRangedDescription* pStreamDescriptions; + UInt32 iStreamDescription; + UInt32 sampleRateRangeCount; + AudioValueRange* pSampleRateRanges; + + 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 (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 < pDeviceInfo->formatCount; ++iOutputFormat) { + if (pDeviceInfo->formats[iOutputFormat] == format) { + formatExists = MA_TRUE; + break; + } + } + + if (!formatExists) { + pDeviceInfo->formats[pDeviceInfo->formatCount++] = format; + } + } + + ma_free(pStreamDescriptions); + + + /* 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; + } + } + } + } +#else + /* Mobile */ + { + AudioComponentDescription desc; + AudioComponent component; + AudioUnit audioUnit; + OSStatus status; + AudioUnitScope formatScope; + AudioUnitElement formatElement; + AudioStreamBasicDescription bestFormat; + UInt32 propSize; + + if (deviceType == ma_device_type_playback) { + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + 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 + retrieve from the AVAudioSession shared instance. + */ + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_RemoteIO; + 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]); + if (result != MA_SUCCESS) { + return result; + } + + /* + 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. + */ + @autoreleasepool { + AVAudioSession* pAudioSession = [AVAudioSession sharedInstance]; + ma_assert(pAudioSession != NULL); + + pDeviceInfo->minSampleRate = (ma_uint32)pAudioSession.sampleRate; + pDeviceInfo->maxSampleRate = pDeviceInfo->minSampleRate; + } + } +#endif + + (void)pDeviceInfo; /* Unused. */ + return MA_SUCCESS; +} + + +void ma_device_uninit__coreaudio(ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + ma_assert(ma_device__get_state(pDevice) == MA_STATE_UNINITIALIZED); + + 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(pDevice->coreaudio.pAudioBufferList); + } + + if (pDevice->type == ma_device_type_duplex) { + ma_pcm_rb_uninit(&pDevice->coreaudio.duplexRB); + } +} + + +OSStatus ma_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFlags* pActionFlags, const AudioTimeStamp* pTimeStamp, UInt32 busNumber, UInt32 frameCount, AudioBufferList* pBufferList) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_stream_layout layout; + + ma_assert(pDevice != NULL); + +#if defined(MA_DEBUG_OUTPUT) + printf("INFO: Output Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", busNumber, frameCount, pBufferList->mNumberBuffers); +#endif + + /* 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 (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; + for (iBuffer = 0; iBuffer < pBufferList->mNumberBuffers; ++iBuffer) { + 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); + } + } + + #if defined(MA_DEBUG_OUTPUT) + printf(" frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pBufferList->mBuffers[iBuffer].mNumberChannels, pBufferList->mBuffers[iBuffer].mDataByteSize); + #endif + } else { + /* + This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's + not interleaved, in which case we can't handle right now since miniaudio does not yet support non-interleaved streams. We just + output silence here. + */ + ma_zero_memory(pBufferList->mBuffers[iBuffer].mData, pBufferList->mBuffers[iBuffer].mDataByteSize); + + #if defined(MA_DEBUG_OUTPUT) + printf(" WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pBufferList->mBuffers[iBuffer].mNumberChannels, pBufferList->mBuffers[iBuffer].mDataByteSize); + #endif + } + } + } 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_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; + + while (framesRemaining > 0) { + void* ppDeinterleavedBuffers[MA_MAX_CHANNELS]; + ma_uint32 iChannel; + ma_uint32 framesToRead = sizeof(tempBuffer) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + if (framesToRead > framesRemaining) { + 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); + } + + for (iChannel = 0; iChannel < pDevice->playback.internalChannels; ++iChannel) { + ppDeinterleavedBuffers[iChannel] = (void*)ma_offset_ptr(pBufferList->mBuffers[iBuffer].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; + + return noErr; +} + +OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFlags* pActionFlags, const AudioTimeStamp* pTimeStamp, UInt32 busNumber, UInt32 frameCount, AudioBufferList* pUnusedBufferList) +{ + ma_device* pDevice = (ma_device*)pUserData; + AudioBufferList* pRenderedBufferList; + ma_stream_layout layout; + 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 + + 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) + printf(" ERROR: AudioUnitRender() failed with %d\n", status); + #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); + } + #if defined(MA_DEBUG_OUTPUT) + printf(" mDataByteSize=%d\n", pRenderedBufferList->mBuffers[iBuffer].mDataByteSize); + #endif + } else { + /* + This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's + not interleaved, in which case we can't handle right now since miniaudio does not yet support non-interleaved streams. + */ + 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); + if (framesToSend > framesRemaining) { + 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); + } + + 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 + } + } + } 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_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); + if (framesToSend > framesRemaining) { + framesToSend = framesRemaining; + } + + for (iChannel = 0; iChannel < pDevice->capture.internalChannels; ++iChannel) { + ppDeinterleavedBuffers[iChannel] = (void*)ma_offset_ptr(pRenderedBufferList->mBuffers[iBuffer].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); + } + + framesRemaining -= framesToSend; + } + } + } + + (void)pActionFlags; + (void)pTimeStamp; + (void)busNumber; + (void)frameCount; + (void)pUnusedBufferList; + + return noErr; +} + +void on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, AudioUnitPropertyID propertyID, AudioUnitScope scope, AudioUnitElement element) +{ + 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) { + ma_stop_proc onStop = pDevice->onStop; + if (onStop) { + onStop(pDevice); + } + + ma_event_signal(&pDevice->coreaudio.stopEvent); + } else { + UInt32 isRunning; + UInt32 isRunningSize = sizeof(isRunning); + OSStatus status = ((ma_AudioUnitGetProperty_proc)pDevice->pContext->coreaudio.AudioUnitGetProperty)(audioUnit, kAudioOutputUnitProperty_IsRunning, scope, element, &isRunning, &isRunningSize); + 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) || + ((audioUnit == pDevice->coreaudio.audioUnitCapture) && pDevice->coreaudio.isDefaultCaptureDevice)) { + /* + It looks like the device is switching through an external event, such as the user unplugging the device or changing the default device + via the operating system's sound settings. If we're re-initializing the device, we just terminate because we want the stopping of the + device to be seamless to the client (we don't want them receiving the onStop event and thinking that the device has stopped when it + hasn't!). + */ + if (((audioUnit == pDevice->coreaudio.audioUnitPlayback) && pDevice->coreaudio.isSwitchingPlaybackDevice) || + ((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) { + onStop(pDevice); + } + } + } + + (void)propertyID; /* Unused. */ +} + +#if defined(MA_APPLE_DESKTOP) +OSStatus ma_default_device_changed__coreaudio(AudioObjectID objectID, UInt32 addressCount, const AudioObjectPropertyAddress* pAddresses, void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_assert(pDevice != NULL); + + /* 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) { + ma_result reinitResult; + + pDevice->coreaudio.isSwitchingPlaybackDevice = MA_TRUE; + reinitResult = ma_device_reinit_internal__coreaudio(pDevice, ma_device_type_playback, MA_TRUE); + pDevice->coreaudio.isSwitchingPlaybackDevice = MA_FALSE; + + if (reinitResult == MA_SUCCESS) { + ma_device__post_init_setup(pDevice, ma_device_type_playback); + + /* Restart the device if required. If this fails we need to stop the device entirely. */ + if (ma_device__get_state(pDevice) == MA_STATE_STARTED) { + OSStatus status = ((ma_AudioOutputUnitStart_proc)pDevice->pContext->coreaudio.AudioOutputUnitStart)((AudioUnit)pDevice->coreaudio.audioUnitPlayback); + if (status != noErr) { + if (pDevice->type == ma_device_type_duplex) { + ((ma_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitCapture); + } + ma_device__set_state(pDevice, MA_STATE_STOPPED); + } + } + } + } + + if (pAddresses[0].mSelector == kAudioHardwarePropertyDefaultInputDevice) { + ma_result reinitResult; + + pDevice->coreaudio.isSwitchingPlaybackDevice = MA_TRUE; + reinitResult = ma_device_reinit_internal__coreaudio(pDevice, ma_device_type_capture, MA_TRUE); + pDevice->coreaudio.isSwitchingPlaybackDevice = MA_FALSE; + + if (reinitResult == MA_SUCCESS) { + ma_device__post_init_setup(pDevice, ma_device_type_capture); + + /* Restart the device if required. If this fails we need to stop the device entirely. */ + if (ma_device__get_state(pDevice) == MA_STATE_STARTED) { + OSStatus status = ((ma_AudioOutputUnitStart_proc)pDevice->pContext->coreaudio.AudioOutputUnitStart)((AudioUnit)pDevice->coreaudio.audioUnitCapture); + if (status != noErr) { + if (pDevice->type == ma_device_type_duplex) { + ((ma_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitPlayback); + } + ma_device__set_state(pDevice, MA_STATE_STOPPED); + } + } + } + } + + (void)objectID; /* Unused. */ + return noErr; +} +#endif + +typedef struct +{ + /* Input. */ + ma_format formatIn; + ma_uint32 channelsIn; + ma_uint32 sampleRateIn; + ma_channel channelMapIn[MA_MAX_CHANNELS]; + ma_uint32 bufferSizeInFramesIn; + ma_uint32 bufferSizeInMillisecondsIn; + ma_uint32 periodsIn; + ma_bool32 usingDefaultFormat; + ma_bool32 usingDefaultChannels; + ma_bool32 usingDefaultSampleRate; + ma_bool32 usingDefaultChannelMap; + ma_share_mode shareMode; + ma_bool32 registerStopEvent; + + /* Output. */ +#if defined(MA_APPLE_DESKTOP) + AudioObjectID deviceObjectID; +#endif + AudioComponent component; + AudioUnit audioUnit; + AudioBufferList* pAudioBufferList; /* Only used for input devices. */ + ma_format formatOut; + ma_uint32 channelsOut; + ma_uint32 sampleRateOut; + ma_channel channelMapOut[MA_MAX_CHANNELS]; + ma_uint32 bufferSizeInFramesOut; + ma_uint32 periodsOut; + char deviceName[256]; +} ma_device_init_internal_data__coreaudio; + +ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_init_internal_data__coreaudio* pData, void* pDevice_DoNotReference) /* <-- pDevice is typed as void* intentionally so as to avoid accidentally referencing it. */ +{ + ma_result result; + OSStatus status; + UInt32 enableIOFlag; + AudioStreamBasicDescription bestFormat; + ma_uint32 actualBufferSizeInFrames; + AURenderCallbackStruct callbackInfo; +#if defined(MA_APPLE_DESKTOP) + AudioObjectID deviceObjectID; +#endif + + /* This API should only be used for a single device type: playback or capture. No full-duplex mode. */ + if (deviceType == ma_device_type_duplex) { + return MA_INVALID_ARGS; + } + + ma_assert(pContext != NULL); + ma_assert(deviceType == ma_device_type_playback || deviceType == ma_device_type_capture); + +#if defined(MA_APPLE_DESKTOP) + pData->deviceObjectID = 0; +#endif + 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 < 1) { + pData->periodsOut = 1; + } + 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)); + if (status != noErr) { + ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); + return ma_result_from_OSStatus(result); + } +#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. + */ + { + 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 result; + } + + 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); + } + + /* + 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 + it depends on Apple's AVAudioSession API. To do this we just get the shared AVAudioSession instance and then set it. Note that from what I + can tell, it looks like the sample rate is shared between playback and capture for everything. + */ + @autoreleasepool { + AVAudioSession* pAudioSession = [AVAudioSession sharedInstance]; + ma_assert(pAudioSession != NULL); + + [pAudioSession setPreferredSampleRate:(double)pData->sampleRateIn error:nil]; + bestFormat.mSampleRate = pAudioSession.sampleRate; + } + + 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->sampleRateOut = bestFormat.mSampleRate; + } + + /* + 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 + this it looks like retrieving it from the AudioUnit will work. However, and this is where + it gets weird, it doesn't seem to work with capture devices, nor at all on iOS... Therefore + I'm going to fall back to a default assumption in these cases. + */ +#if defined(MA_APPLE_DESKTOP) + result = ma_get_AudioUnit_channel_map(pContext, pData->audioUnit, deviceType, pData->channelMapOut); + if (result != MA_SUCCESS) { + #if 0 + /* Try falling back to the channel map from the AudioObject. */ + result = ma_get_AudioObject_channel_map(pContext, deviceObjectID, deviceType, pData->channelMapOut); + if (result != MA_SUCCESS) { + return result; + } + #else + /* Fall back to default assumptions. */ + ma_get_standard_channel_map(ma_standard_channel_map_default, pData->channelsOut, pData->channelMapOut); + #endif + } +#else + /* 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. */ + actualBufferSizeInFrames = pData->bufferSizeInFramesIn; + +#if defined(MA_APPLE_DESKTOP) + if (actualBufferSizeInFrames == 0) { + actualBufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pData->bufferSizeInMillisecondsIn, pData->sampleRateOut); + } + + actualBufferSizeInFrames = actualBufferSizeInFrames / pData->periodsOut; + result = ma_set_AudioObject_buffer_size_in_frames(pContext, deviceObjectID, deviceType, &actualBufferSizeInFrames); + if (result != MA_SUCCESS) { + return result; + } + + pData->bufferSizeInFramesOut = actualBufferSizeInFrames * pData->periodsOut; +#else + actualBufferSizeInFrames = 4096; + pData->bufferSizeInFramesOut = actualBufferSizeInFrames; +#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, &actualBufferSizeInFrames, sizeof(actualBufferSizeInFrames)); + if (status != noErr) { + ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); + return ma_result_from_OSStatus(status); + } + } + + /* 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 += actualBufferSizeInFrames * 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 += actualBufferSizeInFrames * ma_get_bytes_per_sample(pData->formatOut) * pData->channelsOut; + } + + pBufferList = (AudioBufferList*)ma_malloc(allocationSize); + 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 = actualBufferSizeInFrames * 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 = actualBufferSizeInFrames * ma_get_bytes_per_sample(pData->formatOut); + pBufferList->mBuffers[iBuffer].mData = (ma_uint8*)pBufferList + ((sizeof(AudioBufferList) - sizeof(AudioBuffer)) + (sizeof(AudioBuffer) * pData->channelsOut)) + (actualBufferSizeInFrames * 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)); + 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)); + 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); + if (status != noErr) { + ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); + return ma_result_from_OSStatus(status); + } + } + + /* Initialize the audio unit. */ + status = ((ma_AudioUnitInitialize_proc)pContext->coreaudio.AudioUnitInitialize)(pData->audioUnit); + if (status != noErr) { + ma_free(pData->pAudioBufferList); + pData->pAudioBufferList = NULL; + ((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); +#else + if (deviceType == ma_device_type_playback) { + ma_strcpy_s(pData->deviceName, sizeof(pData->deviceName), MA_DEFAULT_PLAYBACK_DEVICE_NAME); + } else { + ma_strcpy_s(pData->deviceName, sizeof(pData->deviceName), MA_DEFAULT_CAPTURE_DEVICE_NAME); + } +#endif + + return result; +} + +ma_result ma_device_reinit_internal__coreaudio(ma_device* pDevice, ma_device_type deviceType, ma_bool32 disposePreviousAudioUnit) +{ + ma_device_init_internal_data__coreaudio data; + ma_result result; + + /* This should only be called for playback or capture, not duplex. */ + if (deviceType == ma_device_type_duplex) { + return MA_INVALID_ARGS; + } + + 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.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); + } + if (pDevice->coreaudio.pAudioBufferList) { + ma_free(pDevice->coreaudio.pAudioBufferList); + } + + #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; + } + if (deviceType == ma_device_type_playback) { + data.formatIn = pDevice->playback.format; + 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.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); + } + + #if defined(MA_APPLE_DESKTOP) + pDevice->coreaudio.deviceObjectIDPlayback = (ma_uint32)data.deviceObjectID; + #endif + pDevice->coreaudio.audioUnitPlayback = (ma_ptr)data.audioUnit; + } + data.bufferSizeInFramesIn = pDevice->coreaudio.originalBufferSizeInFrames; + data.bufferSizeInMillisecondsIn = pDevice->coreaudio.originalBufferSizeInMilliseconds; + data.periodsIn = pDevice->coreaudio.originalPeriods; + + result = ma_device_init_internal__coreaudio(pDevice->pContext, deviceType, NULL, &data, (void*)pDevice); + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + + +ma_result ma_device_init__coreaudio(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +{ + ma_result result; + + ma_assert(pContext != NULL); + ma_assert(pConfig != NULL); + ma_assert(pDevice != NULL); + + /* 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)) { + 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.bufferSizeInFramesIn = pConfig->bufferSizeInFrames; + data.bufferSizeInMillisecondsIn = pConfig->bufferSizeInMilliseconds; + data.registerStopEvent = MA_TRUE; + + result = ma_device_init_internal__coreaudio(pDevice->pContext, ma_device_type_capture, pConfig->capture.pDeviceID, &data, (void*)pDevice); + if (result != MA_SUCCESS) { + return result; + } + + pDevice->coreaudio.isDefaultCaptureDevice = (pConfig->capture.pDeviceID == NULL); + #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->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.internalBufferSizeInFrames = data.bufferSizeInFramesOut; + pDevice->capture.internalPeriods = data.periodsOut; + + /* TODO: This needs to be made global. */ + #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->capture.pDeviceID == NULL) { + AudioObjectPropertyAddress propAddress; + propAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; + propAddress.mScope = kAudioObjectPropertyScopeGlobal; + propAddress.mElement = kAudioObjectPropertyElementMaster; + ((ma_AudioObjectAddPropertyListener_proc)pDevice->pContext->coreaudio.AudioObjectAddPropertyListener)(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, pDevice); + } + #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; + + /* 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.bufferSizeInFramesIn = pDevice->capture.internalBufferSizeInFrames; + data.periodsIn = pDevice->capture.internalPeriods; + data.registerStopEvent = MA_FALSE; + } else { + data.bufferSizeInFramesIn = pConfig->bufferSizeInFrames; + data.bufferSizeInMillisecondsIn = pConfig->bufferSizeInMilliseconds; + data.periodsIn = pConfig->periods; + data.registerStopEvent = MA_TRUE; + } + + result = ma_device_init_internal__coreaudio(pDevice->pContext, ma_device_type_playback, pConfig->playback.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); + if (pDevice->coreaudio.pAudioBufferList) { + ma_free(pDevice->coreaudio.pAudioBufferList); + } + } + return result; + } + + pDevice->coreaudio.isDefaultPlaybackDevice = (pConfig->playback.pDeviceID == NULL); + #if defined(MA_APPLE_DESKTOP) + 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.internalBufferSizeInFrames = data.bufferSizeInFramesOut; + pDevice->playback.internalPeriods = data.periodsOut; + + /* TODO: This needs to be made global. */ + #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) { + AudioObjectPropertyAddress propAddress; + propAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + propAddress.mScope = kAudioObjectPropertyScopeGlobal; + propAddress.mElement = kAudioObjectPropertyElementMaster; + ((ma_AudioObjectAddPropertyListener_proc)pDevice->pContext->coreaudio.AudioObjectAddPropertyListener)(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, pDevice); + } + #endif + } + + pDevice->coreaudio.originalBufferSizeInFrames = pConfig->bufferSizeInFrames; + pDevice->coreaudio.originalBufferSizeInMilliseconds = pConfig->bufferSizeInMilliseconds; + 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(pContext, &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_src(pDevice->sampleRate, pDevice->capture.internalSampleRate, pDevice->capture.internalBufferSizeInFrames); + ma_result result = ma_pcm_rb_init(pDevice->capture.format, pDevice->capture.channels, rbSizeInFrames, NULL, &pDevice->coreaudio.duplexRB); + if (result != MA_SUCCESS) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[Core Audio] Failed to initialize ring buffer.", result); + } + } + + return MA_SUCCESS; +} + + +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) { + if (pDevice->type == ma_device_type_duplex) { + ((ma_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitCapture); + } + return ma_result_from_OSStatus(status); + } + } + + return MA_SUCCESS; +} + +ma_result ma_device_stop__coreaudio(ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + OSStatus status = ((ma_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((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_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; +} + + +ma_result ma_context_uninit__coreaudio(ma_context* pContext) +{ + ma_assert(pContext != NULL); + ma_assert(pContext->backend == ma_backend_coreaudio); + +#if !defined(MA_NO_RUNTIME_LINKING) && !defined(MA_APPLE_MOBILE) + ma_dlclose(pContext, pContext->coreaudio.hAudioUnit); + ma_dlclose(pContext, pContext->coreaudio.hCoreAudio); + ma_dlclose(pContext, pContext->coreaudio.hCoreFoundation); +#endif + + (void)pContext; + return MA_SUCCESS; +} + +ma_result ma_context_init__coreaudio(const ma_context_config* pConfig, ma_context* pContext) +{ + ma_assert(pContext != NULL); + + (void)pConfig; + +#if defined(MA_APPLE_MOBILE) + @autoreleasepool { + AVAudioSession* pAudioSession = [AVAudioSession sharedInstance]; + ma_assert(pAudioSession != NULL); + + [pAudioSession setCategory: AVAudioSessionCategoryPlayAndRecord error:nil]; + + /* By default we want miniaudio to use the speakers instead of the receiver. In the future this may be customizable. */ + ma_bool32 useSpeakers = MA_TRUE; + if (useSpeakers) { + [pAudioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:nil]; + } + } +#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.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"); + pContext->coreaudio.AudioObjectAddPropertyListener = ma_dlsym(pContext, pContext->coreaudio.hCoreAudio, "AudioObjectAddPropertyListener"); + + /* + It looks like Apple has moved some APIs from AudioUnit into AudioToolbox on more recent versions of macOS. They are still + defined in AudioUnit, but just in case they decide to remove them from there entirely I'm going to implement a fallback. + The way it'll work is that it'll first try AudioUnit, and if the required symbols are not present there we'll fall back to + AudioToolbox. + */ + pContext->coreaudio.hAudioUnit = ma_dlopen(pContext, "AudioUnit.framework/AudioUnit"); + if (pContext->coreaudio.hAudioUnit == NULL) { + ma_dlclose(pContext, pContext->coreaudio.hCoreAudio); + 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); + pContext->coreaudio.hAudioUnit = ma_dlopen(pContext, "AudioToolbox.framework/AudioToolbox"); + if (pContext->coreaudio.hAudioUnit == NULL) { + ma_dlclose(pContext, pContext->coreaudio.hCoreAudio); + ma_dlclose(pContext, pContext->coreaudio.hCoreFoundation); + 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"); + pContext->coreaudio.AudioOutputUnitStart = ma_dlsym(pContext, pContext->coreaudio.hAudioUnit, "AudioOutputUnitStart"); + pContext->coreaudio.AudioOutputUnitStop = ma_dlsym(pContext, pContext->coreaudio.hAudioUnit, "AudioOutputUnitStop"); + pContext->coreaudio.AudioUnitAddPropertyListener = ma_dlsym(pContext, pContext->coreaudio.hAudioUnit, "AudioUnitAddPropertyListener"); + pContext->coreaudio.AudioUnitGetPropertyInfo = ma_dlsym(pContext, pContext->coreaudio.hAudioUnit, "AudioUnitGetPropertyInfo"); + pContext->coreaudio.AudioUnitGetProperty = ma_dlsym(pContext, pContext->coreaudio.hAudioUnit, "AudioUnitGetProperty"); + pContext->coreaudio.AudioUnitSetProperty = ma_dlsym(pContext, pContext->coreaudio.hAudioUnit, "AudioUnitSetProperty"); + pContext->coreaudio.AudioUnitInitialize = ma_dlsym(pContext, pContext->coreaudio.hAudioUnit, "AudioUnitInitialize"); + pContext->coreaudio.AudioUnitRender = ma_dlsym(pContext, pContext->coreaudio.hAudioUnit, "AudioUnitRender"); +#else + pContext->coreaudio.CFStringGetCString = (ma_proc)CFStringGetCString; + + #if defined(MA_APPLE_DESKTOP) + pContext->coreaudio.AudioObjectGetPropertyData = (ma_proc)AudioObjectGetPropertyData; + pContext->coreaudio.AudioObjectGetPropertyDataSize = (ma_proc)AudioObjectGetPropertyDataSize; + pContext->coreaudio.AudioObjectSetPropertyData = (ma_proc)AudioObjectSetPropertyData; + pContext->coreaudio.AudioObjectAddPropertyListener = (ma_proc)AudioObjectAddPropertyListener; + #endif + + pContext->coreaudio.AudioComponentFindNext = (ma_proc)AudioComponentFindNext; + pContext->coreaudio.AudioComponentInstanceDispose = (ma_proc)AudioComponentInstanceDispose; + pContext->coreaudio.AudioComponentInstanceNew = (ma_proc)AudioComponentInstanceNew; + pContext->coreaudio.AudioOutputUnitStart = (ma_proc)AudioOutputUnitStart; + pContext->coreaudio.AudioOutputUnitStop = (ma_proc)AudioOutputUnitStop; + pContext->coreaudio.AudioUnitAddPropertyListener = (ma_proc)AudioUnitAddPropertyListener; + pContext->coreaudio.AudioUnitGetPropertyInfo = (ma_proc)AudioUnitGetPropertyInfo; + pContext->coreaudio.AudioUnitGetProperty = (ma_proc)AudioUnitGetProperty; + pContext->coreaudio.AudioUnitSetProperty = (ma_proc)AudioUnitSetProperty; + pContext->coreaudio.AudioUnitInitialize = (ma_proc)AudioUnitInitialize; + 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; + desc.componentType = kAudioUnitType_Output; + #if defined(MA_APPLE_DESKTOP) + desc.componentSubType = kAudioUnitSubType_HALOutput; + #else + desc.componentSubType = kAudioUnitSubType_RemoteIO; + #endif + 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) + ma_dlclose(pContext, pContext->coreaudio.hAudioUnit); + ma_dlclose(pContext, pContext->coreaudio.hCoreAudio); + ma_dlclose(pContext, pContext->coreaudio.hCoreFoundation); + #endif + return MA_FAILED_TO_INIT_BACKEND; + } + } + + return MA_SUCCESS; +} +#endif /* Core Audio */ + + + +/****************************************************************************** + +sndio Backend + +******************************************************************************/ +#ifdef MA_HAS_SNDIO +#include +#include + +/* +Only supporting OpenBSD. This did not work very well at all on FreeBSD when I tried it. Not sure if this is due +to miniaudio's implementation or if it's some kind of system configuration issue, but basically the default device +just doesn't emit any sound, or at times you'll hear tiny pieces. I will consider enabling this when there's +demand for it or if I can get it tested and debugged more thoroughly. +*/ +#if 0 +#if defined(__NetBSD__) || defined(__OpenBSD__) +#include +#endif +#if defined(__FreeBSD__) || defined(__DragonFly__) +#include +#endif +#endif + +#define MA_SIO_DEVANY "default" +#define MA_SIO_PLAY 1 +#define MA_SIO_REC 2 +#define MA_SIO_NENC 8 +#define MA_SIO_NCHAN 8 +#define MA_SIO_NRATE 16 +#define MA_SIO_NCONF 4 + +struct ma_sio_hdl; /* <-- Opaque */ + +struct ma_sio_par +{ + unsigned int bits; + unsigned int bps; + unsigned int sig; + unsigned int le; + unsigned int msb; + unsigned int rchan; + unsigned int pchan; + unsigned int rate; + unsigned int bufsz; + unsigned int xrun; + unsigned int round; + unsigned int appbufsz; + int __pad[3]; + unsigned int __magic; +}; + +struct ma_sio_enc +{ + unsigned int bits; + unsigned int bps; + unsigned int sig; + unsigned int le; + unsigned int msb; +}; + +struct ma_sio_conf +{ + unsigned int enc; + unsigned int rchan; + unsigned int pchan; + unsigned int rate; +}; + +struct ma_sio_cap +{ + struct ma_sio_enc enc[MA_SIO_NENC]; + unsigned int rchan[MA_SIO_NCHAN]; + unsigned int pchan[MA_SIO_NCHAN]; + unsigned int rate[MA_SIO_NRATE]; + int __pad[7]; + unsigned int nconf; + struct ma_sio_conf confs[MA_SIO_NCONF]; +}; + +typedef struct ma_sio_hdl* (* ma_sio_open_proc) (const char*, unsigned int, int); +typedef void (* ma_sio_close_proc) (struct ma_sio_hdl*); +typedef int (* ma_sio_setpar_proc) (struct ma_sio_hdl*, struct ma_sio_par*); +typedef int (* ma_sio_getpar_proc) (struct ma_sio_hdl*, struct ma_sio_par*); +typedef int (* ma_sio_getcap_proc) (struct ma_sio_hdl*, struct ma_sio_cap*); +typedef size_t (* ma_sio_write_proc) (struct ma_sio_hdl*, const void*, size_t); +typedef size_t (* ma_sio_read_proc) (struct ma_sio_hdl*, void*, size_t); +typedef int (* ma_sio_start_proc) (struct ma_sio_hdl*); +typedef int (* ma_sio_stop_proc) (struct ma_sio_hdl*); +typedef int (* ma_sio_initpar_proc)(struct ma_sio_par*); + +ma_format ma_format_from_sio_enc__sndio(unsigned int bits, unsigned int bps, unsigned int sig, unsigned int le, unsigned int msb) +{ + /* We only support native-endian right now. */ + 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; + } + if (bits == 16 && bps == 2 && sig == 1) { + return ma_format_s16; + } + if (bits == 24 && bps == 3 && sig == 1) { + return ma_format_s24; + } + if (bits == 24 && bps == 4 && sig == 1 && msb == 0) { + /*return ma_format_s24_32;*/ + } + if (bits == 32 && bps == 4 && sig == 1) { + return ma_format_s32; + } + + return ma_format_unknown; +} + +ma_format ma_find_best_format_from_sio_cap__sndio(struct ma_sio_cap* caps) +{ + ma_format bestFormat; + unsigned int iConfig; + + ma_assert(caps != NULL); + + bestFormat = ma_format_unknown; + for (iConfig = 0; iConfig < caps->nconf; iConfig += 1) { + unsigned int iEncoding; + for (iEncoding = 0; iEncoding < MA_SIO_NENC; iEncoding += 1) { + unsigned int bits; + unsigned int bps; + unsigned int sig; + unsigned int le; + unsigned int msb; + ma_format format; + + if ((caps->confs[iConfig].enc & (1UL << iEncoding)) == 0) { + continue; + } + + bits = caps->enc[iEncoding].bits; + bps = caps->enc[iEncoding].bps; + sig = caps->enc[iEncoding].sig; + le = caps->enc[iEncoding].le; + msb = caps->enc[iEncoding].msb; + format = ma_format_from_sio_enc__sndio(bits, bps, sig, le, msb); + if (format == ma_format_unknown) { + continue; /* Format not supported. */ + } + + if (bestFormat == ma_format_unknown) { + bestFormat = format; + } else { + if (ma_get_format_priority_index(bestFormat) > ma_get_format_priority_index(format)) { /* <-- Lower = better. */ + bestFormat = format; + } + } + } + } + + return ma_format_unknown; +} + +ma_uint32 ma_find_best_channels_from_sio_cap__sndio(struct ma_sio_cap* caps, ma_device_type deviceType, ma_format requiredFormat) +{ + ma_uint32 maxChannels; + unsigned int iConfig; + + 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) { + /* The encoding should be of requiredFormat. */ + unsigned int iEncoding; + for (iEncoding = 0; iEncoding < MA_SIO_NENC; iEncoding += 1) { + unsigned int iChannel; + unsigned int bits; + unsigned int bps; + unsigned int sig; + unsigned int le; + unsigned int msb; + ma_format format; + + if ((caps->confs[iConfig].enc & (1UL << iEncoding)) == 0) { + continue; + } + + bits = caps->enc[iEncoding].bits; + bps = caps->enc[iEncoding].bps; + sig = caps->enc[iEncoding].sig; + le = caps->enc[iEncoding].le; + msb = caps->enc[iEncoding].msb; + format = ma_format_from_sio_enc__sndio(bits, bps, sig, le, msb); + 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; + 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 (maxChannels < channels) { + maxChannels = channels; + } + } + } + } + + return maxChannels; +} + +ma_uint32 ma_find_best_sample_rate_from_sio_cap__sndio(struct ma_sio_cap* caps, ma_device_type deviceType, ma_format requiredFormat, ma_uint32 requiredChannels) +{ + ma_uint32 firstSampleRate; + ma_uint32 bestSampleRate; + unsigned int iConfig; + + ma_assert(caps != NULL); + 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; + + for (iConfig = 0; iConfig < caps->nconf; iConfig += 1) { + /* The encoding should be of requiredFormat. */ + unsigned int iEncoding; + for (iEncoding = 0; iEncoding < MA_SIO_NENC; iEncoding += 1) { + unsigned int iChannel; + unsigned int bits; + unsigned int bps; + unsigned int sig; + unsigned int le; + unsigned int msb; + ma_format format; + + if ((caps->confs[iConfig].enc & (1UL << iEncoding)) == 0) { + continue; + } + + bits = caps->enc[iEncoding].bits; + bps = caps->enc[iEncoding].bps; + sig = caps->enc[iEncoding].sig; + le = caps->enc[iEncoding].le; + msb = caps->enc[iEncoding].msb; + format = ma_format_from_sio_enc__sndio(bits, bps, sig, le, msb); + 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; + unsigned int channels; + unsigned int iRate; + + 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 (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(rate); + if (ratePriority == (ma_uint32)-1) { + continue; + } + + if (ma_get_standard_sample_rate_priority_index(bestSampleRate) > ratePriority) { /* Lower = better. */ + bestSampleRate = rate; + } + } + } + } + } + + /* 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; +} + + +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; +} + +ma_result ma_context_enumerate_devices__sndio(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_bool32 isTerminating = MA_FALSE; + struct ma_sio_hdl* handle; + + 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); + if (handle != NULL) { + /* Supports playback. */ + ma_device_info deviceInfo; + 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); + if (handle != NULL) { + /* Supports capture. */ + ma_device_info deviceInfo; + ma_zero_object(&deviceInfo); + ma_strcpy_s(deviceInfo.id.sndio, sizeof(deviceInfo.id.sndio), "default"); + 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; +} + +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) +{ + char devid[256]; + struct ma_sio_hdl* handle; + struct ma_sio_cap caps; + 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); + ma_strcpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), (deviceType == ma_device_type_playback) ? MA_DEFAULT_PLAYBACK_DEVICE_NAME : MA_DEFAULT_CAPTURE_DEVICE_NAME); + } else { + 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; + } + + 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 + preference to some formats over others. + */ + unsigned int iEncoding; + unsigned int iChannel; + unsigned int iRate; + + for (iEncoding = 0; iEncoding < MA_SIO_NENC; iEncoding += 1) { + unsigned int bits; + unsigned int bps; + unsigned int sig; + 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; + le = caps.enc[iEncoding].le; + msb = caps.enc[iEncoding].msb; + format = ma_format_from_sio_enc__sndio(bits, bps, sig, le, msb); + 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; + } + if (pDeviceInfo->maxSampleRate < rate) { + pDeviceInfo->maxSampleRate = rate; + } + } + } + } + + ((ma_sio_close_proc)pContext->sndio.sio_close)(handle); + return MA_SUCCESS; +} + +void ma_device_uninit__sndio(ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + 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.handleCapture); + } + + 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); + } +} + +ma_result ma_device_init_handle__sndio(ma_context* pContext, const ma_device_config* pConfig, ma_device_type deviceType, ma_device* pDevice) +{ + const char* pDeviceName; + ma_ptr handle; + int openFlags = 0; + struct ma_sio_cap caps; + struct ma_sio_par par; + ma_device_id* pDeviceID; + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + ma_format internalFormat; + ma_uint32 internalChannels; + ma_uint32 internalSampleRate; + ma_uint32 internalBufferSizeInFrames; + 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; + } + + pDeviceName = MA_SIO_DEVANY; + if (pDeviceID != NULL) { + pDeviceName = pDeviceID->sndio; + } + + handle = (ma_ptr)((ma_sio_open_proc)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); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve device caps.", MA_ERROR); + } + + /* + 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) { + format = ma_find_best_format_from_sio_cap__sndio(&caps); + } + if (pDevice->capture.usingDefaultChannels) { + if (strlen(pDeviceName) > strlen("rsnd/") && strncmp(pDeviceName, "rsnd/", strlen("rsnd/")) == 0) { + channels = ma_find_best_channels_from_sio_cap__sndio(&caps, deviceType, format); + } + } + } else { + if (pDevice->playback.usingDefaultFormat) { + format = ma_find_best_format_from_sio_cap__sndio(&caps); + } + if (pDevice->playback.usingDefaultChannels) { + if (strlen(pDeviceName) > strlen("rsnd/") && strncmp(pDeviceName, "rsnd/", strlen("rsnd/")) == 0) { + channels = ma_find_best_channels_from_sio_cap__sndio(&caps, deviceType, format); + } + } + } + + if (pDevice->usingDefaultSampleRate) { + sampleRate = ma_find_best_sample_rate_from_sio_cap__sndio(&caps, pConfig->deviceType, format, channels); + } + + + ((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: + { + par.bits = 8; + 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: + default: + { + par.bits = 16; + par.bps = 2; + par.sig = 1; + } break; + } + + if (deviceType == ma_device_type_capture) { + par.rchan = channels; + } else { + par.pchan = channels; + } + + par.rate = sampleRate; + + internalBufferSizeInFrames = pConfig->bufferSizeInFrames; + if (internalBufferSizeInFrames == 0) { + internalBufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pConfig->bufferSizeInMilliseconds, par.rate); + } + + par.round = internalBufferSizeInFrames / pConfig->periods; + 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); + 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); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve buffer size.", MA_FORMAT_NOT_SUPPORTED); + } + + internalFormat = ma_format_from_sio_enc__sndio(par.bits, par.bps, par.sig, par.le, par.msb); + internalChannels = (deviceType == ma_device_type_capture) ? par.rchan : par.pchan; + internalSampleRate = par.rate; + internalPeriods = par.appbufsz / par.round; + internalBufferSizeInFrames = par.appbufsz; + + 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.internalBufferSizeInFrames = internalBufferSizeInFrames; + pDevice->capture.internalPeriods = internalPeriods; + } 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.internalBufferSizeInFrames = internalBufferSizeInFrames; + pDevice->playback.internalPeriods = internalPeriods; + } + +#ifdef MA_DEBUG_OUTPUT + printf("DEVICE INFO\n"); + printf(" Format: %s\n", ma_get_format_name(internalFormat)); + printf(" Channels: %d\n", internalChannels); + printf(" Sample Rate: %d\n", internalSampleRate); + printf(" Buffer Size: %d\n", internalBufferSizeInFrames); + printf(" Periods: %d\n", internalPeriods); + printf(" appbufsz: %d\n", par.appbufsz); + printf(" round: %d\n", par.round); +#endif + + return MA_SUCCESS; +} + +ma_result ma_device_init__sndio(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + ma_zero_object(&pDevice->sndio); + + 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); + 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); + if (result != MA_SUCCESS) { + return result; + } + } + + return MA_SUCCESS; +} + +ma_result ma_device_stop__sndio(ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + ((ma_sio_stop_proc)pDevice->pContext->sndio.sio_stop)((struct ma_sio_hdl*)pDevice->sndio.handleCapture); + ma_atomic_exchange_32(&pDevice->sndio.isStartedCapture, MA_FALSE); + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ((ma_sio_stop_proc)pDevice->pContext->sndio.sio_stop)((struct ma_sio_hdl*)pDevice->sndio.handlePlayback); + ma_atomic_exchange_32(&pDevice->sndio.isStartedPlayback, MA_FALSE); + } + + return MA_SUCCESS; +} + +ma_result ma_device_write__sndio(ma_device* pDevice, const void* pPCMFrames, ma_uint32 frameCount) +{ + int result; + + if (!pDevice->sndio.isStartedPlayback) { + ((ma_sio_start_proc)pDevice->pContext->sndio.sio_start)((struct ma_sio_hdl*)pDevice->sndio.handlePlayback); /* <-- Doesn't actually playback until data is written. */ + ma_atomic_exchange_32(&pDevice->sndio.isStartedPlayback, MA_TRUE); + } + + result = ((ma_sio_write_proc)pDevice->pContext->sndio.sio_write)((struct ma_sio_hdl*)pDevice->sndio.handlePlayback, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + if (result == 0) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to send data from the client to the device.", MA_FAILED_TO_SEND_DATA_TO_DEVICE); + } + + return MA_SUCCESS; +} + +ma_result ma_device_read__sndio(ma_device* pDevice, void* pPCMFrames, ma_uint32 frameCount) +{ + int result; + + if (!pDevice->sndio.isStartedCapture) { + ((ma_sio_start_proc)pDevice->pContext->sndio.sio_start)((struct ma_sio_hdl*)pDevice->sndio.handleCapture); /* <-- Doesn't actually playback until data is written. */ + ma_atomic_exchange_32(&pDevice->sndio.isStartedCapture, MA_TRUE); + } + + result = ((ma_sio_read_proc)pDevice->pContext->sndio.sio_read)((struct ma_sio_hdl*)pDevice->sndio.handleCapture, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); + if (result == 0) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to read data from the device to be sent to the device.", MA_FAILED_TO_SEND_DATA_TO_DEVICE); + } + + return MA_SUCCESS; +} + +ma_result ma_context_uninit__sndio(ma_context* pContext) +{ + ma_assert(pContext != NULL); + ma_assert(pContext->backend == ma_backend_sndio); + + (void)pContext; + return MA_SUCCESS; +} + +ma_result ma_context_init__sndio(const ma_context_config* pConfig, ma_context* pContext) +{ +#ifndef MA_NO_RUNTIME_LINKING + const char* libsndioNames[] = { + "libsndio.so" + }; + size_t i; + + for (i = 0; i < ma_countof(libsndioNames); ++i) { + pContext->sndio.sndioSO = ma_dlopen(pContext, libsndioNames[i]); + if (pContext->sndio.sndioSO != NULL) { + break; + } + } + + 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"); + pContext->sndio.sio_getpar = (ma_proc)ma_dlsym(pContext, pContext->sndio.sndioSO, "sio_getpar"); + pContext->sndio.sio_getcap = (ma_proc)ma_dlsym(pContext, pContext->sndio.sndioSO, "sio_getcap"); + pContext->sndio.sio_write = (ma_proc)ma_dlsym(pContext, pContext->sndio.sndioSO, "sio_write"); + pContext->sndio.sio_read = (ma_proc)ma_dlsym(pContext, pContext->sndio.sndioSO, "sio_read"); + pContext->sndio.sio_start = (ma_proc)ma_dlsym(pContext, pContext->sndio.sndioSO, "sio_start"); + pContext->sndio.sio_stop = (ma_proc)ma_dlsym(pContext, pContext->sndio.sndioSO, "sio_stop"); + pContext->sndio.sio_initpar = (ma_proc)ma_dlsym(pContext, pContext->sndio.sndioSO, "sio_initpar"); +#else + pContext->sndio.sio_open = sio_open; + pContext->sndio.sio_close = sio_close; + pContext->sndio.sio_setpar = sio_setpar; + pContext->sndio.sio_getpar = sio_getpar; + pContext->sndio.sio_getcap = sio_getcap; + pContext->sndio.sio_write = sio_write; + pContext->sndio.sio_read = sio_read; + pContext->sndio.sio_start = sio_start; + pContext->sndio.sio_stop = sio_stop; + 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 = ma_device_stop__sndio; + pContext->onDeviceWrite = ma_device_write__sndio; + pContext->onDeviceRead = ma_device_read__sndio; + + (void)pConfig; + return MA_SUCCESS; +} +#endif /* sndio */ + + + +/****************************************************************************** + +audio(4) Backend + +******************************************************************************/ +#ifdef MA_HAS_AUDIO4 +#include +#include +#include +#include +#include +#include +#include + +#if defined(__OpenBSD__) + #include + #if defined(OpenBSD) && OpenBSD >= 201709 + #define MA_AUDIO4_USE_NEW_API + #endif +#endif + +void ma_construct_device_id__audio4(char* id, size_t idSize, const char* base, int deviceIndex) +{ + size_t baseLen; + + 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); +} + +ma_result ma_extract_device_index_from_id__audio4(const char* id, const char* base, int* pIndexOut) +{ + size_t idLen; + size_t baseLen; + const char* deviceIndexStr; + + 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; +} + +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 */ +ma_format ma_format_from_encoding__audio4(unsigned int encoding, unsigned int precision) +{ + if (precision == 8 && (encoding == AUDIO_ENCODING_ULINEAR || encoding == AUDIO_ENCODING_ULINEAR || encoding == AUDIO_ENCODING_ULINEAR_LE || encoding == AUDIO_ENCODING_ULINEAR_BE)) { + return ma_format_u8; + } else { + if (ma_is_little_endian() && encoding == AUDIO_ENCODING_SLINEAR_LE) { + if (precision == 16) { + return ma_format_s16; + } else if (precision == 24) { + return ma_format_s24; + } else if (precision == 32) { + return ma_format_s32; + } + } else if (ma_is_big_endian() && encoding == AUDIO_ENCODING_SLINEAR_BE) { + if (precision == 16) { + return ma_format_s16; + } else if (precision == 24) { + return ma_format_s24; + } else if (precision == 32) { + return ma_format_s32; + } + } + } + + return ma_format_unknown; /* Encoding not supported. */ +} + +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); + + switch (format) + { + case ma_format_u8: + { + *pEncoding = AUDIO_ENCODING_ULINEAR; + *pPrecision = 8; + } break; + + case ma_format_s24: + { + *pEncoding = (ma_is_little_endian()) ? AUDIO_ENCODING_SLINEAR_LE : AUDIO_ENCODING_SLINEAR_BE; + *pPrecision = 24; + } break; + + case ma_format_s32: + { + *pEncoding = (ma_is_little_endian()) ? AUDIO_ENCODING_SLINEAR_LE : AUDIO_ENCODING_SLINEAR_BE; + *pPrecision = 32; + } break; + + case ma_format_s16: + case ma_format_f32: + default: + { + *pEncoding = (ma_is_little_endian()) ? AUDIO_ENCODING_SLINEAR_LE : AUDIO_ENCODING_SLINEAR_BE; + *pPrecision = 16; + } break; + } +} + +ma_format ma_format_from_prinfo__audio4(struct audio_prinfo* prinfo) +{ + return ma_format_from_encoding__audio4(prinfo->encoding, prinfo->precision); +} +#else +ma_format ma_format_from_swpar__audio4(struct audio_swpar* par) +{ + if (par->bits == 8 && par->bps == 1 && par->sig == 0) { + return ma_format_u8; + } + if (par->bits == 16 && par->bps == 2 && par->sig == 1 && par->le == ma_is_little_endian()) { + return ma_format_s16; + } + if (par->bits == 24 && par->bps == 3 && par->sig == 1 && par->le == ma_is_little_endian()) { + return ma_format_s24; + } + if (par->bits == 32 && par->bps == 4 && par->sig == 1 && par->le == ma_is_little_endian()) { + return ma_format_f32; + } + + /* Format not supported. */ + return ma_format_unknown; +} +#endif + +ma_result ma_context_get_device_info_from_fd__audio4(ma_context* pContext, ma_device_type deviceType, int fd, ma_device_info* pInfoOut) +{ + 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); + + (void)pContext; + (void)deviceType; + + if (ioctl(fd, AUDIO_GETDEV, &fdDevice) < 0) { + return MA_ERROR; /* Failed to retrieve device info. */ + } + + /* Name. */ + ma_strcpy_s(pInfoOut->name, sizeof(pInfoOut->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; + 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) { + pInfoOut->formats[pInfoOut->formatCount++] = format; + } + + counter += 1; + } + + if (ioctl(fd, AUDIO_GETINFO, &fdInfo) < 0) { + return MA_ERROR; + } + + 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; + } +#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 + + return MA_SUCCESS; +} + +ma_result ma_context_enumerate_devices__audio4(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + const int maxDevices = 64; + char devpath[256]; + int iDevice; + + 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. + */ + for (iDevice = 0; iDevice < maxDevices; ++iDevice) { + struct stat st; + int fd; + ma_bool32 isTerminating = MA_FALSE; + + 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); + if (fd >= 0) { + /* Supports playback. */ + ma_device_info deviceInfo; + ma_zero_object(&deviceInfo); + ma_construct_device_id__audio4(deviceInfo.id.audio4, sizeof(deviceInfo.id.audio4), "/dev/audio", iDevice); + 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); + if (fd >= 0) { + /* Supports capture. */ + ma_device_info deviceInfo; + ma_zero_object(&deviceInfo); + ma_construct_device_id__audio4(deviceInfo.id.audio4, sizeof(deviceInfo.id.audio4), "/dev/audio", iDevice); + 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; +} + +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) +{ + int fd = -1; + int deviceIndex = -1; + char ctlid[256]; + 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. + */ + if (pDeviceID == NULL) { + /* Default device. */ + ma_strcpy_s(ctlid, sizeof(ctlid), "/dev/audioctl"); + } else { + /* Specific device. We need to convert from "/dev/audioN" to "/dev/audioctlN". */ + result = ma_extract_device_index_from_id__audio4(pDeviceID->audio4, "/dev/audio", &deviceIndex); + 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; +} + +void ma_device_uninit__audio4(ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + close(pDevice->audio4.fdCapture); + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + close(pDevice->audio4.fdPlayback); + } +} + +ma_result ma_device_init_fd__audio4(ma_context* pContext, const ma_device_config* pConfig, ma_device_type deviceType, ma_device* pDevice) +{ + const char* pDefaultDeviceNames[] = { + "/dev/audio", + "/dev/audio0" + }; + 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 internalBufferSizeInFrames; + 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; + } else { + fdFlags = O_WRONLY; + } + fdFlags |= O_NONBLOCK; + + if ((deviceType == ma_device_type_capture && pConfig->capture.pDeviceID == NULL) || (deviceType == ma_device_type_playback && pConfig->playback.pDeviceID == NULL)) { + /* Default device. */ + size_t iDevice; + for (iDevice = 0; iDevice < ma_countof(pDefaultDeviceNames); ++iDevice) { + fd = open(pDefaultDeviceNames[iDevice], fdFlags, 0); + if (fd != -1) { + break; + } + } + } else { + /* Specific device. */ + fd = open((deviceType == ma_device_type_capture) ? pConfig->capture.pDeviceID->audio4 : pConfig->playback.pDeviceID->audio4, fdFlags, 0); + } + + if (fd == -1) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to open device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + +#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. */ + { + ma_uint32 internalBufferSizeInBytes; + + internalBufferSizeInFrames = pConfig->bufferSizeInFrames; + if (internalBufferSizeInFrames == 0) { + internalBufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pConfig->bufferSizeInMilliseconds, internalSampleRate); + } + + internalBufferSizeInBytes = internalBufferSizeInFrames * ma_get_bytes_per_frame(internalFormat, internalChannels); + if (internalBufferSizeInBytes < 16) { + internalBufferSizeInBytes = 16; + } + + internalPeriods = pConfig->periods; + if (internalPeriods < 2) { + internalPeriods = 2; + } + + /* What miniaudio calls a fragment, audio4 calls a block. */ + AUDIO_INITINFO(&fdInfo); + fdInfo.hiwat = internalPeriods; + fdInfo.lowat = internalPeriods-1; + fdInfo.blocksize = internalBufferSizeInBytes / internalPeriods; + 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; + internalBufferSizeInFrames = (fdInfo.blocksize * fdInfo.hiwat) / 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 internalBufferSizeInBytes; + + internalBufferSizeInFrames = pConfig->bufferSizeInFrames; + if (internalBufferSizeInFrames == 0) { + internalBufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pConfig->bufferSizeInMilliseconds, internalSampleRate); + } + + /* What miniaudio calls a fragment, audio4 calls a block. */ + internalBufferSizeInBytes = internalBufferSizeInFrames * ma_get_bytes_per_frame(internalFormat, internalChannels); + if (internalBufferSizeInBytes < 16) { + internalBufferSizeInBytes = 16; + } + + fdPar.nblks = pConfig->periods; + fdPar.round = internalBufferSizeInBytes / fdPar.nblks; + + 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; + internalBufferSizeInFrames = (fdPar.nblks * fdPar.round) / ma_get_bytes_per_frame(internalFormat, internalChannels); +#endif + + 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); + } + + 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.internalBufferSizeInFrames = internalBufferSizeInFrames; + pDevice->capture.internalPeriods = internalPeriods; + } 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.internalBufferSizeInFrames = internalBufferSizeInFrames; + pDevice->playback.internalPeriods = internalPeriods; + } + + return MA_SUCCESS; +} + +ma_result ma_device_init__audio4(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + ma_zero_object(&pDevice->audio4); + + pDevice->audio4.fdCapture = -1; + pDevice->audio4.fdPlayback = -1; + + /* + The version of the operating system dictates whether or not the device is exclusive or shared. NetBSD + introduced in-kernel mixing which means it's shared. All other BSD flavours are exclusive as far as + I'm aware. + */ +#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)) { + return MA_SHARE_MODE_NOT_SUPPORTED; + } +#else + /* All other flavors. */ +#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); + 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); + if (result != MA_SUCCESS) { + if (pConfig->deviceType == ma_device_type_duplex) { + close(pDevice->audio4.fdCapture); + } + return result; + } + } + + return MA_SUCCESS; +} + +#if 0 +ma_result ma_device_start__audio4(ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + if (pDevice->audio4.fdCapture == -1) { + return MA_INVALID_ARGS; + } + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + if (pDevice->audio4.fdPlayback == -1) { + return MA_INVALID_ARGS; + } + } + + return MA_SUCCESS; +} +#endif + +ma_result ma_device_stop_fd__audio4(ma_device* pDevice, int fd) +{ + if (fd == -1) { + return MA_INVALID_ARGS; + } + +#if !defined(MA_AUDIO4_USE_NEW_API) + if (ioctl(fd, AUDIO_FLUSH, 0) < 0) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to stop device. AUDIO_FLUSH failed.", MA_FAILED_TO_STOP_BACKEND_DEVICE); + } +#else + if (ioctl(fd, AUDIO_STOP, 0) < 0) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to stop device. AUDIO_STOP failed.", MA_FAILED_TO_STOP_BACKEND_DEVICE); + } +#endif + + return MA_SUCCESS; +} + +ma_result ma_device_stop__audio4(ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + ma_result result = ma_device_stop_fd__audio4(pDevice, pDevice->audio4.fdCapture); + if (result != MA_SUCCESS) { + return result; + } + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ma_result result = ma_device_stop_fd__audio4(pDevice, pDevice->audio4.fdPlayback); + if (result != MA_SUCCESS) { + return result; + } + } + + return MA_SUCCESS; +} + +ma_result ma_device_write__audio4(ma_device* pDevice, const void* pPCMFrames, ma_uint32 frameCount) +{ + int result = write(pDevice->audio4.fdPlayback, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + if (result < 0) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to write data to the device.", MA_FAILED_TO_SEND_DATA_TO_DEVICE); + } + + return MA_SUCCESS; +} + +ma_result ma_device_read__audio4(ma_device* pDevice, void* pPCMFrames, ma_uint32 frameCount) +{ + int result = read(pDevice->audio4.fdCapture, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); + if (result < 0) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to read data from the device.", MA_FAILED_TO_READ_DATA_FROM_DEVICE); + } + + return MA_SUCCESS; +} + +ma_result ma_context_uninit__audio4(ma_context* pContext) +{ + ma_assert(pContext != NULL); + ma_assert(pContext->backend == ma_backend_audio4); + + (void)pContext; + return MA_SUCCESS; +} + +ma_result ma_context_init__audio4(const ma_context_config* pConfig, ma_context* pContext) +{ + 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; + pContext->onDeviceStop = ma_device_stop__audio4; + pContext->onDeviceWrite = ma_device_write__audio4; + pContext->onDeviceRead = ma_device_read__audio4; + + return MA_SUCCESS; +} +#endif /* audio4 */ + + +/****************************************************************************** + +OSS Backend + +******************************************************************************/ +#ifdef MA_HAS_OSS +#include +#include +#include +#include + +#ifndef SNDCTL_DSP_HALT +#define SNDCTL_DSP_HALT SNDCTL_DSP_RESET +#endif + +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. */ + int fd = open("/dev/mixer", O_RDONLY, 0); + if (fd >= 0) { + return fd; + } + + return -1; +} + +ma_result ma_context_open_device__oss(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, int* pfd) +{ + const char* deviceName; + int flags; + + ma_assert(pContext != NULL); + ma_assert(pfd != NULL); + (void)pContext; + + *pfd = -1; + + /* This function should only be called for playback or capture, not duplex. */ + if (deviceType == ma_device_type_duplex) { + return MA_INVALID_ARGS; + } + + deviceName = "/dev/dsp"; + if (pDeviceID != NULL) { + deviceName = pDeviceID->oss; + } + + flags = (deviceType == ma_device_type_playback) ? O_WRONLY : O_RDONLY; + if (shareMode == ma_share_mode_exclusive) { + flags |= O_EXCL; + } + + *pfd = open(deviceName, flags, 0); + if (*pfd == -1) { + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + } + + return MA_SUCCESS; +} + +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; +} + +ma_result ma_context_enumerate_devices__oss(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + int fd; + oss_sysinfo si; + int result; + + ma_assert(pContext != NULL); + ma_assert(callback != NULL); + + fd = ma_open_temp_device__oss(); + if (fd == -1) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open a temporary device for retrieving system information used for device enumeration.", MA_NO_BACKEND); + } + + result = ioctl(fd, SNDCTL_SYSINFO, &si); + if (result != -1) { + int iAudioDevice; + for (iAudioDevice = 0; iAudioDevice < si.numaudios; ++iAudioDevice) { + oss_audioinfo ai; + ai.dev = iAudioDevice; + result = ioctl(fd, SNDCTL_AUDIOINFO, &ai); + if (result != -1) { + if (ai.devnode[0] != '\0') { /* <-- Can be blank, according to documentation. */ + ma_device_info deviceInfo; + ma_bool32 isTerminating = MA_FALSE; + + ma_zero_object(&deviceInfo); + + /* ID */ + ma_strncpy_s(deviceInfo.id.oss, sizeof(deviceInfo.id.oss), ai.devnode, (size_t)-1); + + /* + The human readable device name should be in the "ai.handle" variable, but it can + sometimes be empty in which case we just fall back to "ai.name" which is less user + friendly, but usually has a value. + */ + if (ai.handle[0] != '\0') { + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), ai.handle, (size_t)-1); + } else { + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), ai.name, (size_t)-1); + } + + /* The device can be both playback and capture. */ + if (!isTerminating && (ai.caps & PCM_CAP_OUTPUT) != 0) { + isTerminating = !callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); + } + if (!isTerminating && (ai.caps & PCM_CAP_INPUT) != 0) { + isTerminating = !callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); + } + + if (isTerminating) { + break; + } + } + } + } + } else { + close(fd); + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve system information for device enumeration.", MA_NO_BACKEND); + } + + close(fd); + return MA_SUCCESS; +} + +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) +{ + ma_bool32 foundDevice; + int fdTemp; + oss_sysinfo si; + int result; + + ma_assert(pContext != NULL); + (void)shareMode; + + /* Handle the default device a little differently. */ + if (pDeviceID == NULL) { + if (deviceType == ma_device_type_playback) { + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + } + + return MA_SUCCESS; + } + + + /* If we get here it means we are _not_ using the default device. */ + foundDevice = MA_FALSE; + + fdTemp = ma_open_temp_device__oss(); + if (fdTemp == -1) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open a temporary device for retrieving system information used for device enumeration.", MA_NO_BACKEND); + } + + result = ioctl(fdTemp, SNDCTL_SYSINFO, &si); + if (result != -1) { + int iAudioDevice; + for (iAudioDevice = 0; iAudioDevice < si.numaudios; ++iAudioDevice) { + oss_audioinfo ai; + ai.dev = iAudioDevice; + result = ioctl(fdTemp, SNDCTL_AUDIOINFO, &ai); + if (result != -1) { + if (ma_strcmp(ai.devnode, pDeviceID->oss) == 0) { + /* It has the same name, so now just confirm the type. */ + if ((deviceType == ma_device_type_playback && ((ai.caps & PCM_CAP_OUTPUT) != 0)) || + (deviceType == ma_device_type_capture && ((ai.caps & PCM_CAP_INPUT) != 0))) { + unsigned int formatMask; + + /* ID */ + ma_strncpy_s(pDeviceInfo->id.oss, sizeof(pDeviceInfo->id.oss), ai.devnode, (size_t)-1); + + /* + The human readable device name should be in the "ai.handle" variable, but it can + sometimes be empty in which case we just fall back to "ai.name" which is less user + friendly, but usually has a value. + */ + if (ai.handle[0] != '\0') { + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), ai.handle, (size_t)-1); + } else { + 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; + + if (deviceType == ma_device_type_playback) { + formatMask = ai.oformats; + } else { + 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; + } + if (((formatMask & AFMT_S32_LE) != 0 && ma_is_little_endian()) || (AFMT_S32_BE && ma_is_big_endian())) { + pDeviceInfo->formats[pDeviceInfo->formatCount++] = ma_format_s32; + } + + foundDevice = MA_TRUE; + break; + } + } + } + } + } else { + close(fdTemp); + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve system information for device enumeration.", MA_NO_BACKEND); + } + + + close(fdTemp); + + if (!foundDevice) { + return MA_NO_DEVICE; + } + + return MA_SUCCESS; +} + +void 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); + } +} + +int ma_format_to_oss(ma_format format) +{ + int ossFormat = AFMT_U8; + switch (format) { + case ma_format_s16: ossFormat = (ma_is_little_endian()) ? AFMT_S16_LE : AFMT_S16_BE; break; + case ma_format_s24: ossFormat = (ma_is_little_endian()) ? AFMT_S32_LE : AFMT_S32_BE; break; + case ma_format_s32: ossFormat = (ma_is_little_endian()) ? AFMT_S32_LE : AFMT_S32_BE; break; + case ma_format_f32: ossFormat = (ma_is_little_endian()) ? AFMT_S16_LE : AFMT_S16_BE; break; + case ma_format_u8: + default: ossFormat = AFMT_U8; break; + } + + return ossFormat; +} + +ma_format ma_format_from_oss(int ossFormat) +{ + if (ossFormat == AFMT_U8) { + return ma_format_u8; + } else { + if (ma_is_little_endian()) { + switch (ossFormat) { + case AFMT_S16_LE: return ma_format_s16; + case AFMT_S32_LE: return ma_format_s32; + default: return ma_format_unknown; + } + } else { + switch (ossFormat) { + case AFMT_S16_BE: return ma_format_s16; + case AFMT_S32_BE: return ma_format_s32; + default: return ma_format_unknown; + } + } + } + + return ma_format_unknown; +} + +ma_result ma_device_init_fd__oss(ma_context* pContext, const ma_device_config* pConfig, ma_device_type deviceType, ma_device* pDevice) +{ + ma_result result; + int ossResult; + int fd; + const ma_device_id* pDeviceID = NULL; + ma_share_mode shareMode; + int ossFormat; + int ossChannels; + int ossSampleRate; + int ossFragment; + + ma_assert(pContext != NULL); + ma_assert(pConfig != NULL); + ma_assert(deviceType != ma_device_type_duplex); + ma_assert(pDevice != NULL); + + (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); + if (result != MA_SUCCESS) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + /* + The OSS documantation is very clear about the order we should be initializing the device's properties: + 1) Format + 2) Channels + 3) Sample rate. + */ + + /* Format. */ + ossResult = ioctl(fd, SNDCTL_DSP_SETFMT, &ossFormat); + if (ossResult == -1) { + close(fd); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to set format.", MA_FORMAT_NOT_SUPPORTED); + } + + /* Channels. */ + ossResult = ioctl(fd, SNDCTL_DSP_CHANNELS, &ossChannels); + if (ossResult == -1) { + close(fd); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to set channel count.", MA_FORMAT_NOT_SUPPORTED); + } + + /* Sample Rate. */ + ossResult = ioctl(fd, SNDCTL_DSP_SPEED, &ossSampleRate); + if (ossResult == -1) { + close(fd); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to set sample rate.", MA_FORMAT_NOT_SUPPORTED); + } + + /* + Buffer. + + 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. + */ + { + ma_uint32 fragmentSizeInBytes; + ma_uint32 bufferSizeInFrames; + ma_uint32 ossFragmentSizePower; + + bufferSizeInFrames = pConfig->bufferSizeInFrames; + if (bufferSizeInFrames == 0) { + bufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pConfig->bufferSizeInMilliseconds, (ma_uint32)ossSampleRate); + } + + fragmentSizeInBytes = ma_round_to_power_of_2((bufferSizeInFrames / pConfig->periods) * ma_get_bytes_per_frame(ma_format_from_oss(ossFormat), ossChannels)); + if (fragmentSizeInBytes < 16) { + fragmentSizeInBytes = 16; + } + + ossFragmentSizePower = 4; + fragmentSizeInBytes >>= 4; + while (fragmentSizeInBytes >>= 1) { + ossFragmentSizePower += 1; + } + + ossFragment = (int)((pConfig->periods << 16) | ossFragmentSizePower); + ossResult = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &ossFragment); + if (ossResult == -1) { + close(fd); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to set fragment size and period count.", MA_FORMAT_NOT_SUPPORTED); + } + } + + /* 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.internalBufferSizeInFrames = (((ma_uint32)(1 << (ossFragment & 0xFFFF))) * pDevice->capture.internalPeriods) / 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); + } + } 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.internalBufferSizeInFrames = (((ma_uint32)(1 << (ossFragment & 0xFFFF))) * pDevice->playback.internalPeriods) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + + 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); + } + } + + return MA_SUCCESS; +} + +ma_result ma_device_init__oss(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +{ + ma_assert(pContext != NULL); + ma_assert(pConfig != NULL); + ma_assert(pDevice != NULL); + + ma_zero_object(&pDevice->oss); + + 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); + if (result != MA_SUCCESS) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + } + + 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); + if (result != MA_SUCCESS) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + } + + return MA_SUCCESS; +} + +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_FAILED_TO_STOP_BACKEND_DEVICE); + } + } + + 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_FAILED_TO_STOP_BACKEND_DEVICE); + } + } + + return MA_SUCCESS; +} + +ma_result ma_device_write__oss(ma_device* pDevice, const void* pPCMFrames, ma_uint32 frameCount) +{ + int 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_FAILED_TO_SEND_DATA_TO_DEVICE); + } + + return MA_SUCCESS; +} + +ma_result ma_device_read__oss(ma_device* pDevice, void* pPCMFrames, ma_uint32 frameCount) +{ + int 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_FAILED_TO_READ_DATA_FROM_DEVICE); + } + + return MA_SUCCESS; +} + +ma_result ma_context_uninit__oss(ma_context* pContext) +{ + ma_assert(pContext != NULL); + ma_assert(pContext->backend == ma_backend_oss); + + (void)pContext; + return MA_SUCCESS; +} + +ma_result ma_context_init__oss(const ma_context_config* pConfig, ma_context* pContext) +{ + int fd; + int ossVersion; + int result; + + ma_assert(pContext != NULL); + + (void)pConfig; + + /* Try opening a temporary device first so we can get version information. This is closed at the end. */ + fd = ma_open_temp_device__oss(); + if (fd == -1) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open temporary device for retrieving system properties.", MA_NO_BACKEND); /* Looks liks OSS isn't installed, or there are no available devices. */ + } + + /* Grab the OSS version. */ + ossVersion = 0; + result = ioctl(fd, OSS_GETVERSION, &ossVersion); + if (result == -1) { + close(fd); + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve OSS version.", MA_NO_BACKEND); + } + + 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 = ma_device_stop__oss; + pContext->onDeviceWrite = ma_device_write__oss; + pContext->onDeviceRead = ma_device_read__oss; + + close(fd); + return MA_SUCCESS; +} +#endif /* OSS */ + + +/****************************************************************************** + +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_data_callback_result_t; + +/* Result codes. miniaudio only cares about the success code. */ +#define MA_AAUDIO_OK 0 + +/* Directions. */ +#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 + +/* Formats. */ +#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 + +/* 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 + +/* Callback results. */ +#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 ma_aaudio_result_t (* MA_PFN_AAudio_createStreamBuilder) (ma_AAudioStreamBuilder** ppBuilder); +typedef ma_aaudio_result_t (* MA_PFN_AAudioStreamBuilder_delete) (ma_AAudioStreamBuilder* pBuilder); +typedef void (* MA_PFN_AAudioStreamBuilder_setDeviceId) (ma_AAudioStreamBuilder* pBuilder, int32_t deviceId); +typedef void (* MA_PFN_AAudioStreamBuilder_setDirection) (ma_AAudioStreamBuilder* pBuilder, ma_aaudio_direction_t direction); +typedef void (* MA_PFN_AAudioStreamBuilder_setSharingMode) (ma_AAudioStreamBuilder* pBuilder, ma_aaudio_sharing_mode_t sharingMode); +typedef void (* MA_PFN_AAudioStreamBuilder_setFormat) (ma_AAudioStreamBuilder* pBuilder, ma_aaudio_format_t format); +typedef void (* MA_PFN_AAudioStreamBuilder_setChannelCount) (ma_AAudioStreamBuilder* pBuilder, int32_t channelCount); +typedef void (* MA_PFN_AAudioStreamBuilder_setSampleRate) (ma_AAudioStreamBuilder* pBuilder, int32_t sampleRate); +typedef void (* MA_PFN_AAudioStreamBuilder_setBufferCapacityInFrames)(ma_AAudioStreamBuilder* pBuilder, int32_t numFrames); +typedef void (* MA_PFN_AAudioStreamBuilder_setFramesPerDataCallback) (ma_AAudioStreamBuilder* pBuilder, int32_t numFrames); +typedef void (* MA_PFN_AAudioStreamBuilder_setDataCallback) (ma_AAudioStreamBuilder* pBuilder, ma_AAudioStream_dataCallback callback, void* pUserData); +typedef void (* MA_PFN_AAudioStreamBuilder_setPerformanceMode) (ma_AAudioStreamBuilder* pBuilder, ma_aaudio_performance_mode_t mode); +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); +typedef ma_aaudio_result_t (* MA_PFN_AAudioStream_waitForStateChange) (ma_AAudioStream* pStream, ma_aaudio_stream_state_t inputState, ma_aaudio_stream_state_t* pNextState, int64_t timeoutInNanoseconds); +typedef ma_aaudio_format_t (* MA_PFN_AAudioStream_getFormat) (ma_AAudioStream* pStream); +typedef int32_t (* MA_PFN_AAudioStream_getChannelCount) (ma_AAudioStream* pStream); +typedef int32_t (* MA_PFN_AAudioStream_getSampleRate) (ma_AAudioStream* pStream); +typedef int32_t (* MA_PFN_AAudioStream_getBufferCapacityInFrames) (ma_AAudioStream* pStream); +typedef int32_t (* MA_PFN_AAudioStream_getFramesPerDataCallback) (ma_AAudioStream* pStream); +typedef int32_t (* MA_PFN_AAudioStream_getFramesPerBurst) (ma_AAudioStream* pStream); +typedef ma_aaudio_result_t (* MA_PFN_AAudioStream_requestStart) (ma_AAudioStream* pStream); +typedef ma_aaudio_result_t (* MA_PFN_AAudioStream_requestStop) (ma_AAudioStream* pStream); + +ma_result ma_result_from_aaudio(ma_aaudio_result_t resultAA) +{ + switch (resultAA) + { + case MA_AAUDIO_OK: return MA_SUCCESS; + default: break; + } + + return MA_ERROR; +} + +ma_aaudio_data_callback_result_t ma_stream_data_callback_capture__aaudio(ma_AAudioStream* pStream, void* pUserData, void* pAudioData, int32_t frameCount) +{ + 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. */ + } + + (void)pStream; + return MA_AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +ma_aaudio_data_callback_result_t ma_stream_data_callback_playback__aaudio(ma_AAudioStream* pStream, void* pUserData, void* pAudioData, int32_t frameCount) +{ + 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. */ + } + + (void)pStream; + return MA_AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +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) +{ + ma_AAudioStreamBuilder* pBuilder; + ma_aaudio_result_t resultAA; + + ma_assert(deviceType != ma_device_type_duplex); /* This function should not be called for a full-duplex device type. */ + + *ppStream = NULL; + + resultAA = ((MA_PFN_AAudio_createStreamBuilder)pContext->aaudio.AAudio_createStreamBuilder)(&pBuilder); + if (resultAA != MA_AAUDIO_OK) { + return ma_result_from_aaudio(resultAA); + } + + if (pDeviceID != NULL) { + ((MA_PFN_AAudioStreamBuilder_setDeviceId)pContext->aaudio.AAudioStreamBuilder_setDeviceId)(pBuilder, pDeviceID->aaudio); + } + + ((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 (deviceType == ma_device_type_capture) { + if (pDevice == NULL || !pDevice->capture.usingDefaultChannels) { + ((MA_PFN_AAudioStreamBuilder_setChannelCount)pContext->aaudio.AAudioStreamBuilder_setChannelCount)(pBuilder, pConfig->capture.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); + } + } else { + if (pDevice == NULL || !pDevice->playback.usingDefaultChannels) { + ((MA_PFN_AAudioStreamBuilder_setChannelCount)pContext->aaudio.AAudioStreamBuilder_setChannelCount)(pBuilder, pConfig->playback.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); + } + } + + bufferCapacityInFrames = pConfig->bufferSizeInFrames; + if (bufferCapacityInFrames == 0) { + bufferCapacityInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pConfig->bufferSizeInMilliseconds, pConfig->sampleRate); + } + ((MA_PFN_AAudioStreamBuilder_setBufferCapacityInFrames)pContext->aaudio.AAudioStreamBuilder_setBufferCapacityInFrames)(pBuilder, bufferCapacityInFrames); + + ((MA_PFN_AAudioStreamBuilder_setFramesPerDataCallback)pContext->aaudio.AAudioStreamBuilder_setFramesPerDataCallback)(pBuilder, bufferCapacityInFrames / pConfig->periods); + + if (deviceType == ma_device_type_capture) { + ((MA_PFN_AAudioStreamBuilder_setDataCallback)pContext->aaudio.AAudioStreamBuilder_setDataCallback)(pBuilder, ma_stream_data_callback_capture__aaudio, (void*)pDevice); + } else { + ((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); + } + + 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; +} + +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)); +} + +ma_bool32 ma_has_default_device__aaudio(ma_context* pContext, ma_device_type deviceType) +{ + /* 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); + if (result != MA_SUCCESS) { + return MA_FALSE; + } + + ma_close_stream__aaudio(pContext, pStream); + return MA_TRUE; +} + +ma_result ma_wait_for_simple_state_transition__aaudio(ma_context* pContext, ma_AAudioStream* pStream, ma_aaudio_stream_state_t oldState, ma_aaudio_stream_state_t newState) +{ + ma_aaudio_stream_state_t actualNewState; + ma_aaudio_result_t resultAA = ((MA_PFN_AAudioStream_waitForStateChange)pContext->aaudio.AAudioStream_waitForStateChange)(pStream, oldState, &actualNewState, 5000000000); /* 5 second timeout. */ + if (resultAA != MA_AAUDIO_OK) { + return ma_result_from_aaudio(resultAA); + } + + if (newState != actualNewState) { + return MA_ERROR; /* Failed to transition into the expected state. */ + } + + return MA_SUCCESS; +} + + +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; +} + +ma_result ma_context_enumerate_devices__aaudio(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_bool32 cbResult = MA_TRUE; + + ma_assert(pContext != NULL); + ma_assert(callback != NULL); + + /* Unfortunately AAudio does not have an enumeration API. Therefore I'm only going to report default devices, but only if it can instantiate a stream. */ + + /* Playback. */ + if (cbResult) { + ma_device_info deviceInfo; + ma_zero_object(&deviceInfo); + deviceInfo.id.aaudio = MA_AAUDIO_UNSPECIFIED; + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + + if (ma_has_default_device__aaudio(pContext, ma_device_type_playback)) { + cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); + } + } + + /* Capture. */ + if (cbResult) { + ma_device_info deviceInfo; + ma_zero_object(&deviceInfo); + deviceInfo.id.aaudio = MA_AAUDIO_UNSPECIFIED; + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + + if (ma_has_default_device__aaudio(pContext, ma_device_type_capture)) { + cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); + } + } + + return MA_SUCCESS; +} + +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) +{ + 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); + } else { + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + } + + + /* 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); + 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_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; +} + + +void ma_device_uninit__aaudio(ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamCapture); + pDevice->aaudio.pStreamCapture = NULL; + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamPlayback); + pDevice->aaudio.pStreamPlayback = NULL; + } + + if (pDevice->type == ma_device_type_duplex) { + ma_pcm_rb_uninit(&pDevice->aaudio.duplexRB); + } +} + +ma_result ma_device_init__aaudio(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +{ + ma_result result; + + ma_assert(pDevice != NULL); + + /* 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)) { + 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 framesPerPeriod; + + result = ma_open_stream__aaudio(pContext, ma_device_type_capture, pConfig->capture.pDeviceID, pConfig->capture.shareMode, pConfig, pDevice, (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. */ + pDevice->capture.internalBufferSizeInFrames = ((MA_PFN_AAudioStream_getBufferCapacityInFrames)pContext->aaudio.AAudioStream_getBufferCapacityInFrames)((ma_AAudioStream*)pDevice->aaudio.pStreamCapture); + + /* + TODO: When synchronous reading and writing is supported, use AAudioStream_getFramesPerBurst() instead of AAudioStream_getFramesPerDataCallback(). Keep + using AAudioStream_getFramesPerDataCallback() for asynchronous mode, though. + */ + framesPerPeriod = ((MA_PFN_AAudioStream_getFramesPerDataCallback)pContext->aaudio.AAudioStream_getFramesPerDataCallback)((ma_AAudioStream*)pDevice->aaudio.pStreamCapture); + if (framesPerPeriod > 0) { + pDevice->capture.internalPeriods = 1; + } else { + pDevice->capture.internalPeriods = pDevice->capture.internalBufferSizeInFrames / framesPerPeriod; + } + } + + if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + int32_t framesPerPeriod; + + result = ma_open_stream__aaudio(pContext, ma_device_type_playback, pConfig->playback.pDeviceID, pConfig->playback.shareMode, pConfig, pDevice, (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. */ + pDevice->playback.internalBufferSizeInFrames = ((MA_PFN_AAudioStream_getBufferCapacityInFrames)pContext->aaudio.AAudioStream_getBufferCapacityInFrames)((ma_AAudioStream*)pDevice->aaudio.pStreamPlayback); + + framesPerPeriod = ((MA_PFN_AAudioStream_getFramesPerDataCallback)pContext->aaudio.AAudioStream_getFramesPerDataCallback)((ma_AAudioStream*)pDevice->aaudio.pStreamPlayback); + if (framesPerPeriod > 0) { + pDevice->playback.internalPeriods = 1; + } else { + pDevice->playback.internalPeriods = pDevice->playback.internalBufferSizeInFrames / framesPerPeriod; + } + } + + if (pConfig->deviceType == ma_device_type_duplex) { + ma_uint32 rbSizeInFrames = (ma_uint32)ma_calculate_frame_count_after_src(pDevice->sampleRate, pDevice->capture.internalSampleRate, pDevice->capture.internalBufferSizeInFrames); + ma_result result = ma_pcm_rb_init(pDevice->capture.format, pDevice->capture.channels, rbSizeInFrames, NULL, &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); + } + } + + return MA_SUCCESS; +} + +ma_result ma_device_start_stream__aaudio(ma_device* pDevice, ma_AAudioStream* pStream) +{ + ma_aaudio_result_t resultAA; + ma_aaudio_stream_state_t currentState; + + ma_assert(pDevice != NULL); + + resultAA = ((MA_PFN_AAudioStream_requestStart)pDevice->pContext->aaudio.AAudioStream_requestStart)(pStream); + if (resultAA != MA_AAUDIO_OK) { + return ma_result_from_aaudio(resultAA); + } + + /* Do we actually need to wait for the device to transition into it's started state? */ + + /* The device should be in either a starting or started state. If it's not set to started we need to wait for it to transition. It should go from starting to started. */ + currentState = ((MA_PFN_AAudioStream_getState)pDevice->pContext->aaudio.AAudioStream_getState)(pStream); + if (currentState != MA_AAUDIO_STREAM_STATE_STARTED) { + ma_result result; + + if (currentState != MA_AAUDIO_STREAM_STATE_STARTING) { + return MA_ERROR; /* Expecting the stream to be a starting or started state. */ + } + + result = ma_wait_for_simple_state_transition__aaudio(pDevice->pContext, pStream, currentState, MA_AAUDIO_STREAM_STATE_STARTED); + if (result != MA_SUCCESS) { + return result; + } + } + + return MA_SUCCESS; +} + +ma_result ma_device_stop_stream__aaudio(ma_device* pDevice, ma_AAudioStream* pStream) +{ + ma_aaudio_result_t resultAA; + ma_aaudio_stream_state_t currentState; + + ma_assert(pDevice != NULL); + + resultAA = ((MA_PFN_AAudioStream_requestStop)pDevice->pContext->aaudio.AAudioStream_requestStop)(pStream); + if (resultAA != MA_AAUDIO_OK) { + return ma_result_from_aaudio(resultAA); + } + + /* The device should be in either a stopping or stopped state. If it's not set to started we need to wait for it to transition. It should go from stopping to stopped. */ + currentState = ((MA_PFN_AAudioStream_getState)pDevice->pContext->aaudio.AAudioStream_getState)(pStream); + if (currentState != MA_AAUDIO_STREAM_STATE_STOPPED) { + ma_result result; + + if (currentState != MA_AAUDIO_STREAM_STATE_STOPPING) { + return MA_ERROR; /* Expecting the stream to be a stopping or stopped state. */ + } + + result = ma_wait_for_simple_state_transition__aaudio(pDevice->pContext, pStream, currentState, MA_AAUDIO_STREAM_STATE_STOPPED); + if (result != MA_SUCCESS) { + return result; + } + } + + return MA_SUCCESS; +} + +ma_result ma_device_start__aaudio(ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + ma_result result = ma_device_start_stream__aaudio(pDevice, (ma_AAudioStream*)pDevice->aaudio.pStreamCapture); + if (result != MA_SUCCESS) { + return result; + } + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ma_result result = ma_device_start_stream__aaudio(pDevice, (ma_AAudioStream*)pDevice->aaudio.pStreamPlayback); + if (result != MA_SUCCESS) { + if (pDevice->type == ma_device_type_duplex) { + ma_device_stop_stream__aaudio(pDevice, (ma_AAudioStream*)pDevice->aaudio.pStreamCapture); + } + return result; + } + } + + return MA_SUCCESS; +} + +ma_result ma_device_stop__aaudio(ma_device* pDevice) +{ + ma_stop_proc onStop; + + ma_assert(pDevice != NULL); + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + ma_result result = ma_device_stop_stream__aaudio(pDevice, (ma_AAudioStream*)pDevice->aaudio.pStreamCapture); + if (result != MA_SUCCESS) { + return result; + } + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ma_result result = ma_device_stop_stream__aaudio(pDevice, (ma_AAudioStream*)pDevice->aaudio.pStreamPlayback); + if (result != MA_SUCCESS) { + return result; + } + } + + onStop = pDevice->onStop; + if (onStop) { + onStop(pDevice); + } + + return MA_SUCCESS; +} + + +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; +} + +ma_result ma_context_init__aaudio(const ma_context_config* pConfig, ma_context* pContext) +{ + const char* libNames[] = { + "libaaudio.so" + }; + size_t i; + + for (i = 0; i < ma_countof(libNames); ++i) { + pContext->aaudio.hAAudio = ma_dlopen(pContext, libNames[i]); + if (pContext->aaudio.hAAudio != NULL) { + break; + } + } + + if (pContext->aaudio.hAAudio == NULL) { + return MA_FAILED_TO_INIT_BACKEND; + } + + pContext->aaudio.AAudio_createStreamBuilder = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudio_createStreamBuilder"); + pContext->aaudio.AAudioStreamBuilder_delete = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStreamBuilder_delete"); + pContext->aaudio.AAudioStreamBuilder_setDeviceId = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStreamBuilder_setDeviceId"); + pContext->aaudio.AAudioStreamBuilder_setDirection = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStreamBuilder_setDirection"); + pContext->aaudio.AAudioStreamBuilder_setSharingMode = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStreamBuilder_setSharingMode"); + pContext->aaudio.AAudioStreamBuilder_setFormat = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStreamBuilder_setFormat"); + pContext->aaudio.AAudioStreamBuilder_setChannelCount = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStreamBuilder_setChannelCount"); + pContext->aaudio.AAudioStreamBuilder_setSampleRate = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStreamBuilder_setSampleRate"); + pContext->aaudio.AAudioStreamBuilder_setBufferCapacityInFrames = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStreamBuilder_setBufferCapacityInFrames"); + pContext->aaudio.AAudioStreamBuilder_setFramesPerDataCallback = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStreamBuilder_setFramesPerDataCallback"); + pContext->aaudio.AAudioStreamBuilder_setDataCallback = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStreamBuilder_setDataCallback"); + pContext->aaudio.AAudioStreamBuilder_setPerformanceMode = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStreamBuilder_setPerformanceMode"); + 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"); + pContext->aaudio.AAudioStream_waitForStateChange = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStream_waitForStateChange"); + pContext->aaudio.AAudioStream_getFormat = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStream_getFormat"); + pContext->aaudio.AAudioStream_getChannelCount = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStream_getChannelCount"); + pContext->aaudio.AAudioStream_getSampleRate = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStream_getSampleRate"); + pContext->aaudio.AAudioStream_getBufferCapacityInFrames = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStream_getBufferCapacityInFrames"); + pContext->aaudio.AAudioStream_getFramesPerDataCallback = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStream_getFramesPerDataCallback"); + pContext->aaudio.AAudioStream_getFramesPerBurst = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStream_getFramesPerBurst"); + 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; + + (void)pConfig; + return MA_SUCCESS; +} +#endif /* AAudio */ + + +/****************************************************************************** + +OpenSL|ES Backend + +******************************************************************************/ +#ifdef MA_HAS_OPENSL +#include +#ifdef MA_ANDROID +#include +#endif + +/* OpenSL|ES has one-per-application objects :( */ +SLObjectItf g_maEngineObjectSL = NULL; +SLEngineItf g_maEngineSL = NULL; +ma_uint32 g_maOpenSLInitCounter = 0; + +#define MA_OPENSL_OBJ(p) (*((SLObjectItf)(p))) +#define MA_OPENSL_OUTPUTMIX(p) (*((SLOutputMixItf)(p))) +#define MA_OPENSL_PLAY(p) (*((SLPlayItf)(p))) +#define MA_OPENSL_RECORD(p) (*((SLRecordItf)(p))) + +#ifdef MA_ANDROID +#define MA_OPENSL_BUFFERQUEUE(p) (*((SLAndroidSimpleBufferQueueItf)(p))) +#else +#define MA_OPENSL_BUFFERQUEUE(p) (*((SLBufferQueueItf)(p))) +#endif + +/* Converts an individual OpenSL-style channel identifier (SL_SPEAKER_FRONT_LEFT, etc.) to miniaudio. */ +ma_uint8 ma_channel_id_to_ma__opensl(SLuint32 id) +{ + switch (id) + { + case SL_SPEAKER_FRONT_LEFT: return MA_CHANNEL_FRONT_LEFT; + case SL_SPEAKER_FRONT_RIGHT: return MA_CHANNEL_FRONT_RIGHT; + case SL_SPEAKER_FRONT_CENTER: return MA_CHANNEL_FRONT_CENTER; + case SL_SPEAKER_LOW_FREQUENCY: return MA_CHANNEL_LFE; + case SL_SPEAKER_BACK_LEFT: return MA_CHANNEL_BACK_LEFT; + case SL_SPEAKER_BACK_RIGHT: return MA_CHANNEL_BACK_RIGHT; + case SL_SPEAKER_FRONT_LEFT_OF_CENTER: return MA_CHANNEL_FRONT_LEFT_CENTER; + case SL_SPEAKER_FRONT_RIGHT_OF_CENTER: return MA_CHANNEL_FRONT_RIGHT_CENTER; + case SL_SPEAKER_BACK_CENTER: return MA_CHANNEL_BACK_CENTER; + case SL_SPEAKER_SIDE_LEFT: return MA_CHANNEL_SIDE_LEFT; + case SL_SPEAKER_SIDE_RIGHT: return MA_CHANNEL_SIDE_RIGHT; + case SL_SPEAKER_TOP_CENTER: return MA_CHANNEL_TOP_CENTER; + case SL_SPEAKER_TOP_FRONT_LEFT: return MA_CHANNEL_TOP_FRONT_LEFT; + case SL_SPEAKER_TOP_FRONT_CENTER: return MA_CHANNEL_TOP_FRONT_CENTER; + case SL_SPEAKER_TOP_FRONT_RIGHT: return MA_CHANNEL_TOP_FRONT_RIGHT; + case SL_SPEAKER_TOP_BACK_LEFT: return MA_CHANNEL_TOP_BACK_LEFT; + case SL_SPEAKER_TOP_BACK_CENTER: return MA_CHANNEL_TOP_BACK_CENTER; + case SL_SPEAKER_TOP_BACK_RIGHT: return MA_CHANNEL_TOP_BACK_RIGHT; + default: return 0; + } +} + +/* Converts an individual miniaudio channel identifier (MA_CHANNEL_FRONT_LEFT, etc.) to OpenSL-style. */ +SLuint32 ma_channel_id_to_opensl(ma_uint8 id) +{ + switch (id) + { + case MA_CHANNEL_MONO: return SL_SPEAKER_FRONT_CENTER; + case MA_CHANNEL_FRONT_LEFT: return SL_SPEAKER_FRONT_LEFT; + case MA_CHANNEL_FRONT_RIGHT: return SL_SPEAKER_FRONT_RIGHT; + case MA_CHANNEL_FRONT_CENTER: return SL_SPEAKER_FRONT_CENTER; + case MA_CHANNEL_LFE: return SL_SPEAKER_LOW_FREQUENCY; + case MA_CHANNEL_BACK_LEFT: return SL_SPEAKER_BACK_LEFT; + case MA_CHANNEL_BACK_RIGHT: return SL_SPEAKER_BACK_RIGHT; + case MA_CHANNEL_FRONT_LEFT_CENTER: return SL_SPEAKER_FRONT_LEFT_OF_CENTER; + case MA_CHANNEL_FRONT_RIGHT_CENTER: return SL_SPEAKER_FRONT_RIGHT_OF_CENTER; + case MA_CHANNEL_BACK_CENTER: return SL_SPEAKER_BACK_CENTER; + case MA_CHANNEL_SIDE_LEFT: return SL_SPEAKER_SIDE_LEFT; + case MA_CHANNEL_SIDE_RIGHT: return SL_SPEAKER_SIDE_RIGHT; + case MA_CHANNEL_TOP_CENTER: return SL_SPEAKER_TOP_CENTER; + case MA_CHANNEL_TOP_FRONT_LEFT: return SL_SPEAKER_TOP_FRONT_LEFT; + case MA_CHANNEL_TOP_FRONT_CENTER: return SL_SPEAKER_TOP_FRONT_CENTER; + case MA_CHANNEL_TOP_FRONT_RIGHT: return SL_SPEAKER_TOP_FRONT_RIGHT; + case MA_CHANNEL_TOP_BACK_LEFT: return SL_SPEAKER_TOP_BACK_LEFT; + case MA_CHANNEL_TOP_BACK_CENTER: return SL_SPEAKER_TOP_BACK_CENTER; + case MA_CHANNEL_TOP_BACK_RIGHT: return SL_SPEAKER_TOP_BACK_RIGHT; + default: return 0; + } +} + +/* Converts a channel mapping to an OpenSL-style channel mask. */ +SLuint32 ma_channel_map_to_channel_mask__opensl(const ma_channel channelMap[MA_MAX_CHANNELS], ma_uint32 channels) +{ + SLuint32 channelMask = 0; + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; ++iChannel) { + channelMask |= ma_channel_id_to_opensl(channelMap[iChannel]); + } + + return channelMask; +} + +/* Converts an OpenSL-style channel mask to a miniaudio channel map. */ +void ma_channel_mask_to_channel_map__opensl(SLuint32 channelMask, ma_uint32 channels, ma_channel channelMap[MA_MAX_CHANNELS]) +{ + if (channels == 1 && channelMask == 0) { + channelMap[0] = MA_CHANNEL_MONO; + } else if (channels == 2 && channelMask == 0) { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + } else { + if (channels == 1 && (channelMask & SL_SPEAKER_FRONT_CENTER) != 0) { + channelMap[0] = MA_CHANNEL_MONO; + } else { + /* Just iterate over each bit. */ + ma_uint32 iChannel = 0; + ma_uint32 iBit; + for (iBit = 0; iBit < 32; ++iBit) { + SLuint32 bitValue = (channelMask & (1UL << iBit)); + if (bitValue != 0) { + /* The bit is set. */ + channelMap[iChannel] = ma_channel_id_to_ma__opensl(bitValue); + iChannel += 1; + } + } + } + } +} + +SLuint32 ma_round_to_standard_sample_rate__opensl(SLuint32 samplesPerSec) +{ + if (samplesPerSec <= SL_SAMPLINGRATE_8) { + return SL_SAMPLINGRATE_8; + } + if (samplesPerSec <= SL_SAMPLINGRATE_11_025) { + return SL_SAMPLINGRATE_11_025; + } + if (samplesPerSec <= SL_SAMPLINGRATE_12) { + return SL_SAMPLINGRATE_12; + } + if (samplesPerSec <= SL_SAMPLINGRATE_16) { + return SL_SAMPLINGRATE_16; + } + if (samplesPerSec <= SL_SAMPLINGRATE_22_05) { + return SL_SAMPLINGRATE_22_05; + } + if (samplesPerSec <= SL_SAMPLINGRATE_24) { + return SL_SAMPLINGRATE_24; + } + if (samplesPerSec <= SL_SAMPLINGRATE_32) { + return SL_SAMPLINGRATE_32; + } + if (samplesPerSec <= SL_SAMPLINGRATE_44_1) { + return SL_SAMPLINGRATE_44_1; + } + if (samplesPerSec <= SL_SAMPLINGRATE_48) { + return SL_SAMPLINGRATE_48; + } + + /* Android doesn't support more than 48000. */ +#ifndef MA_ANDROID + if (samplesPerSec <= SL_SAMPLINGRATE_64) { + return SL_SAMPLINGRATE_64; + } + if (samplesPerSec <= SL_SAMPLINGRATE_88_2) { + return SL_SAMPLINGRATE_88_2; + } + if (samplesPerSec <= SL_SAMPLINGRATE_96) { + return SL_SAMPLINGRATE_96; + } + if (samplesPerSec <= SL_SAMPLINGRATE_192) { + return SL_SAMPLINGRATE_192; + } +#endif + + return SL_SAMPLINGRATE_16; +} + + +ma_bool32 ma_context_is_device_id_equal__opensl(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->opensl == pID1->opensl; +} + +ma_result ma_context_enumerate_devices__opensl(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_bool32 cbResult; + + ma_assert(pContext != NULL); + ma_assert(callback != NULL); + + 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 enumerate devices. */ + if (g_maOpenSLInitCounter == 0) { + return MA_INVALID_OPERATION; + } + + /* + TODO: Test Me. + + This is currently untested, so for now we are just returning default devices. + */ +#if 0 && !defined(MA_ANDROID) + ma_bool32 isTerminated = MA_FALSE; + + SLuint32 pDeviceIDs[128]; + SLint32 deviceCount = sizeof(pDeviceIDs) / sizeof(pDeviceIDs[0]); + + SLAudioIODeviceCapabilitiesItf deviceCaps; + SLresult resultSL = (*g_maEngineObjectSL)->GetInterface(g_maEngineObjectSL, SL_IID_AUDIOIODEVICECAPABILITIES, &deviceCaps); + if (resultSL != SL_RESULT_SUCCESS) { + /* The interface may not be supported so just report a default device. */ + goto return_default_device; + } + + /* Playback */ + if (!isTerminated) { + resultSL = (*deviceCaps)->GetAvailableAudioOutputs(deviceCaps, &deviceCount, pDeviceIDs); + if (resultSL != SL_RESULT_SUCCESS) { + return MA_NO_DEVICE; + } + + for (SLint32 iDevice = 0; iDevice < deviceCount; ++iDevice) { + ma_device_info deviceInfo; + ma_zero_object(&deviceInfo); + deviceInfo.id.opensl = pDeviceIDs[iDevice]; + + SLAudioOutputDescriptor desc; + resultSL = (*deviceCaps)->QueryAudioOutputCapabilities(deviceCaps, deviceInfo.id.opensl, &desc); + if (resultSL == SL_RESULT_SUCCESS) { + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), (const char*)desc.pDeviceName, (size_t)-1); + + ma_bool32 cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); + if (cbResult == MA_FALSE) { + isTerminated = MA_TRUE; + break; + } + } + } + } + + /* Capture */ + if (!isTerminated) { + resultSL = (*deviceCaps)->GetAvailableAudioInputs(deviceCaps, &deviceCount, pDeviceIDs); + if (resultSL != SL_RESULT_SUCCESS) { + return MA_NO_DEVICE; + } + + for (SLint32 iDevice = 0; iDevice < deviceCount; ++iDevice) { + ma_device_info deviceInfo; + ma_zero_object(&deviceInfo); + deviceInfo.id.opensl = pDeviceIDs[iDevice]; + + SLAudioInputDescriptor desc; + resultSL = (*deviceCaps)->QueryAudioInputCapabilities(deviceCaps, deviceInfo.id.opensl, &desc); + if (resultSL == SL_RESULT_SUCCESS) { + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), (const char*)desc.deviceName, (size_t)-1); + + ma_bool32 cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); + if (cbResult == MA_FALSE) { + isTerminated = MA_TRUE; + break; + } + } + } + } + + return MA_SUCCESS; +#else + goto return_default_device; +#endif + +return_default_device:; + cbResult = MA_TRUE; + + /* Playback. */ + if (cbResult) { + ma_device_info deviceInfo; + ma_zero_object(&deviceInfo); + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); + } + + /* Capture. */ + if (cbResult) { + ma_device_info deviceInfo; + ma_zero_object(&deviceInfo); + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); + } + + return MA_SUCCESS; +} + +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) +{ + ma_assert(pContext != NULL); + + 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 get device info. */ + if (g_maOpenSLInitCounter == 0) { + 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) + SLAudioIODeviceCapabilitiesItf deviceCaps; + SLresult resultSL = (*g_maEngineObjectSL)->GetInterface(g_maEngineObjectSL, SL_IID_AUDIOIODEVICECAPABILITIES, &deviceCaps); + if (resultSL != SL_RESULT_SUCCESS) { + /* The interface may not be supported so just report a default device. */ + goto return_default_device; + } + + if (deviceType == ma_device_type_playback) { + SLAudioOutputDescriptor desc; + resultSL = (*deviceCaps)->QueryAudioOutputCapabilities(deviceCaps, pDeviceID->opensl, &desc); + if (resultSL != SL_RESULT_SUCCESS) { + return MA_NO_DEVICE; + } + + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), (const char*)desc.pDeviceName, (size_t)-1); + } else { + SLAudioInputDescriptor desc; + resultSL = (*deviceCaps)->QueryAudioInputCapabilities(deviceCaps, pDeviceID->opensl, &desc); + if (resultSL != SL_RESULT_SUCCESS) { + return MA_NO_DEVICE; + } + + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), (const char*)desc.deviceName, (size_t)-1); + } + + goto return_detailed_info; +#else + goto return_default_device; +#endif + +return_default_device: + if (pDeviceID != NULL) { + if ((deviceType == ma_device_type_playback && pDeviceID->opensl != SL_DEFAULTDEVICEID_AUDIOOUTPUT) || + (deviceType == ma_device_type_capture && pDeviceID->opensl != SL_DEFAULTDEVICEID_AUDIOINPUT)) { + return MA_NO_DEVICE; /* Don't know the device. */ + } + } + + /* Name / Description */ + if (deviceType == ma_device_type_playback) { + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + } + + goto return_detailed_info; + + +return_detailed_info: + + /* + For now we're just outputting a set of values that are supported by the API but not necessarily supported + 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; +#if defined(MA_ANDROID) && __ANDROID_API__ >= 21 + pDeviceInfo->formats[pDeviceInfo->formatCount] = ma_format_f32; + pDeviceInfo->formatCount += 1; +#endif + + return MA_SUCCESS; +} + + +#ifdef MA_ANDROID +/*void ma_buffer_queue_callback_capture__opensl_android(SLAndroidSimpleBufferQueueItf pBufferQueue, SLuint32 eventFlags, const void* pBuffer, SLuint32 bufferSize, SLuint32 dataUsed, void* pContext)*/ +void ma_buffer_queue_callback_capture__opensl_android(SLAndroidSimpleBufferQueueItf pBufferQueue, void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + size_t periodSizeInBytes; + ma_uint8* pBuffer; + SLresult resultSL; + + ma_assert(pDevice != NULL); + + (void)pBufferQueue; + + /* + For now, don't do anything unless the buffer was fully processed. From what I can tell, it looks like + OpenSL|ES 1.1 improves on buffer queues to the point that we could much more intelligently handle this, + but unfortunately it looks like Android is only supporting OpenSL|ES 1.0.1 for now :( + */ + + /* Don't do anything if the device is not started. */ + if (pDevice->state != MA_STATE_STARTED) { + return; + } + + periodSizeInBytes = (pDevice->capture.internalBufferSizeInFrames / pDevice->capture.internalPeriods) * 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.internalBufferSizeInFrames / pDevice->capture.internalPeriods), pBuffer, &pDevice->opensl.duplexRB); + } else { + ma_device__send_frames_to_client(pDevice, (pDevice->capture.internalBufferSizeInFrames / pDevice->capture.internalPeriods), pBuffer); + } + + resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueueCapture)->Enqueue((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueueCapture, pBuffer, periodSizeInBytes); + if (resultSL != SL_RESULT_SUCCESS) { + return; + } + + pDevice->opensl.currentBufferIndexCapture = (pDevice->opensl.currentBufferIndexCapture + 1) % pDevice->capture.internalPeriods; +} + +void ma_buffer_queue_callback_playback__opensl_android(SLAndroidSimpleBufferQueueItf pBufferQueue, void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + size_t periodSizeInBytes; + ma_uint8* pBuffer; + SLresult resultSL; + + ma_assert(pDevice != NULL); + + (void)pBufferQueue; + + /* Don't do anything if the device is not started. */ + if (pDevice->state != MA_STATE_STARTED) { + return; + } + + periodSizeInBytes = (pDevice->playback.internalBufferSizeInFrames / pDevice->playback.internalPeriods) * 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.internalBufferSizeInFrames / pDevice->playback.internalPeriods), pBuffer, &pDevice->opensl.duplexRB); + } else { + ma_device__read_frames_from_client(pDevice, (pDevice->playback.internalBufferSizeInFrames / pDevice->playback.internalPeriods), pBuffer); + } + + resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueuePlayback)->Enqueue((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueuePlayback, pBuffer, periodSizeInBytes); + if (resultSL != SL_RESULT_SUCCESS) { + return; + } + + pDevice->opensl.currentBufferIndexPlayback = (pDevice->opensl.currentBufferIndexPlayback + 1) % pDevice->playback.internalPeriods; +} +#endif + +void 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; + } + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + if (pDevice->opensl.pAudioRecorderObj) { + MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->Destroy((SLObjectItf)pDevice->opensl.pAudioRecorderObj); + } + + ma_free(pDevice->opensl.pBufferCapture); + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + if (pDevice->opensl.pAudioPlayerObj) { + MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->Destroy((SLObjectItf)pDevice->opensl.pAudioPlayerObj); + } + if (pDevice->opensl.pOutputMixObj) { + MA_OPENSL_OBJ(pDevice->opensl.pOutputMixObj)->Destroy((SLObjectItf)pDevice->opensl.pOutputMixObj); + } + + ma_free(pDevice->opensl.pBufferPlayback); + } + + if (pDevice->type == ma_device_type_duplex) { + ma_pcm_rb_uninit(&pDevice->opensl.duplexRB); + } +} + +#if defined(MA_ANDROID) && __ANDROID_API__ >= 21 +typedef SLAndroidDataFormat_PCM_EX ma_SLDataFormat_PCM; +#else +typedef SLDataFormat_PCM ma_SLDataFormat_PCM; +#endif + +ma_result ma_SLDataFormat_PCM_init__opensl(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, const ma_channel* channelMap, ma_SLDataFormat_PCM* pDataFormat) +{ +#if defined(MA_ANDROID) && __ANDROID_API__ >= 21 + if (format == ma_format_f32) { + pDataFormat->formatType = SL_ANDROID_DATAFORMAT_PCM_EX; + pDataFormat->representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT; + } else { + pDataFormat->formatType = SL_DATAFORMAT_PCM; + } +#else + pDataFormat->formatType = SL_DATAFORMAT_PCM; +#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 */ + 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; + + /* + Android has a few restrictions on the format as documented here: https://developer.android.com/ndk/guides/audio/opensl-for-android.html + - Only mono and stereo is supported. + - Only u8 and s16 formats are supported. + - Maximum sample rate of 48000. + */ +#ifdef MA_ANDROID + if (pDataFormat->numChannels > 2) { + pDataFormat->numChannels = 2; + } +#if __ANDROID_API__ >= 21 + if (pDataFormat->formatType == SL_ANDROID_DATAFORMAT_PCM_EX) { + /* It's floating point. */ + ma_assert(pDataFormat->representation == SL_ANDROID_PCM_REPRESENTATION_FLOAT); + if (pDataFormat->bitsPerSample > 32) { + pDataFormat->bitsPerSample = 32; + } + } else { + if (pDataFormat->bitsPerSample > 16) { + pDataFormat->bitsPerSample = 16; + } + } +#else + if (pDataFormat->bitsPerSample > 16) { + pDataFormat->bitsPerSample = 16; + } +#endif + if (((SLDataFormat_PCM*)pDataFormat)->samplesPerSec > SL_SAMPLINGRATE_48) { + ((SLDataFormat_PCM*)pDataFormat)->samplesPerSec = SL_SAMPLINGRATE_48; + } +#endif + + pDataFormat->containerSize = pDataFormat->bitsPerSample; /* Always tightly packed for now. */ + + return MA_SUCCESS; +} + +ma_result ma_deconstruct_SLDataFormat_PCM__opensl(ma_SLDataFormat_PCM* pDataFormat, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap) +{ + ma_bool32 isFloatingPoint = MA_FALSE; +#if defined(MA_ANDROID) && __ANDROID_API__ >= 21 + if (pDataFormat->formatType == SL_ANDROID_DATAFORMAT_PCM_EX) { + ma_assert(pDataFormat->representation == SL_ANDROID_PCM_REPRESENTATION_FLOAT); + isFloatingPoint = MA_TRUE; + } +#endif + if (isFloatingPoint) { + if (pDataFormat->bitsPerSample == 32) { + *pFormat = ma_format_f32; + } + } else { + if (pDataFormat->bitsPerSample == 8) { + *pFormat = ma_format_u8; + } else if (pDataFormat->bitsPerSample == 16) { + *pFormat = ma_format_s16; + } else if (pDataFormat->bitsPerSample == 24) { + *pFormat = ma_format_s24; + } else if (pDataFormat->bitsPerSample == 32) { + *pFormat = ma_format_s32; + } + } + + *pChannels = pDataFormat->numChannels; + *pSampleRate = ((SLDataFormat_PCM*)pDataFormat)->samplesPerSec / 1000; + ma_channel_mask_to_channel_map__opensl(pDataFormat->channelMask, pDataFormat->numChannels, pChannelMap); + + return MA_SUCCESS; +} + +ma_result ma_device_init__opensl(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +{ +#ifdef MA_ANDROID + SLDataLocator_AndroidSimpleBufferQueue queue; + SLresult resultSL; + ma_uint32 bufferSizeInFrames; + size_t bufferSizeInBytes; + const SLInterfaceID itfIDs1[] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE}; + 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; + } + + /* + For now, only supporting Android implementations of OpenSL|ES since that's the only one I've + been able to test with and I currently depend on Android-specific extensions (simple buffer + queues). + */ +#ifdef MA_ANDROID + /* 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)) { + return MA_SHARE_MODE_NOT_SUPPORTED; + } + + /* Now we can start initializing the device properly. */ + ma_assert(pDevice != NULL); + 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; + + ma_SLDataFormat_PCM_init__opensl(pConfig->capture.format, pConfig->capture.channels, pConfig->sampleRate, pConfig->capture.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.device = NULL; + + source.pLocator = &locatorDevice; + source.pFormat = NULL; + + sink.pLocator = &queue; + sink.pFormat = (SLDataFormat_PCM*)&pcm; + + resultSL = (*g_maEngineSL)->CreateAudioRecorder(g_maEngineSL, (SLObjectItf*)&pDevice->opensl.pAudioRecorderObj, &source, &sink, 1, itfIDs1, itfIDsRequired1); + if (resultSL == SL_RESULT_CONTENT_UNSUPPORTED) { + /* Unsupported format. Fall back to something safer and try again. If this fails, just abort. */ + pcm.formatType = SL_DATAFORMAT_PCM; + pcm.numChannels = 1; + ((SLDataFormat_PCM*)&pcm)->samplesPerSec = SL_SAMPLINGRATE_16; /* The name of the sample rate variable is different between SLAndroidDataFormat_PCM_EX and SLDataFormat_PCM. */ + pcm.bitsPerSample = 16; + pcm.containerSize = pcm.bitsPerSample; /* Always tightly packed for now. */ + pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + resultSL = (*g_maEngineSL)->CreateAudioRecorder(g_maEngineSL, (SLObjectItf*)&pDevice->opensl.pAudioRecorderObj, &source, &sink, 1, itfIDs1, itfIDsRequired1); + } + + if (resultSL != SL_RESULT_SUCCESS) { + ma_device_uninit__opensl(pDevice); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create audio recorder.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->Realize((SLObjectItf)pDevice->opensl.pAudioRecorderObj, SL_BOOLEAN_FALSE) != SL_RESULT_SUCCESS) { + ma_device_uninit__opensl(pDevice); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize audio recorder.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioRecorderObj, SL_IID_RECORD, &pDevice->opensl.pAudioRecorder) != 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_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioRecorderObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &pDevice->opensl.pBufferQueueCapture) != 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_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueueCapture)->RegisterCallback((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueueCapture, ma_buffer_queue_callback_capture__opensl_android, pDevice) != SL_RESULT_SUCCESS) { + ma_device_uninit__opensl(pDevice); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to register buffer queue callback.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + /* 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); + + /* Buffer. */ + bufferSizeInFrames = pConfig->bufferSizeInFrames; + if (bufferSizeInFrames == 0) { + bufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pConfig->bufferSizeInMilliseconds, pDevice->capture.internalSampleRate); + } + pDevice->capture.internalPeriods = pConfig->periods; + pDevice->capture.internalBufferSizeInFrames = (bufferSizeInFrames / pDevice->capture.internalPeriods) * pDevice->capture.internalPeriods; + pDevice->opensl.currentBufferIndexCapture = 0; + + bufferSizeInBytes = pDevice->capture.internalBufferSizeInFrames * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + pDevice->opensl.pBufferCapture = (ma_uint8*)ma_malloc(bufferSizeInBytes); + 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); + } + MA_ZERO_MEMORY(pDevice->opensl.pBufferCapture, bufferSizeInBytes); + } + + if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + ma_SLDataFormat_PCM pcm; + SLDataSource source; + SLDataLocator_OutputMix outmixLocator; + SLDataSink sink; + + ma_SLDataFormat_PCM_init__opensl(pConfig->playback.format, pConfig->playback.channels, pConfig->sampleRate, pConfig->playback.channelMap, &pcm); + + resultSL = (*g_maEngineSL)->CreateOutputMix(g_maEngineSL, (SLObjectItf*)&pDevice->opensl.pOutputMixObj, 0, NULL, NULL); + if (resultSL != SL_RESULT_SUCCESS) { + ma_device_uninit__opensl(pDevice); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create output mix.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (MA_OPENSL_OBJ(pDevice->opensl.pOutputMixObj)->Realize((SLObjectItf)pDevice->opensl.pOutputMixObj, SL_BOOLEAN_FALSE)) { + ma_device_uninit__opensl(pDevice); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize output mix object.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (MA_OPENSL_OBJ(pDevice->opensl.pOutputMixObj)->GetInterface((SLObjectItf)pDevice->opensl.pOutputMixObj, SL_IID_OUTPUTMIX, &pDevice->opensl.pOutputMix) != 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_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + /* Set the output device. */ + if (pConfig->playback.pDeviceID != NULL) { + SLuint32 deviceID_OpenSL = pConfig->playback.pDeviceID->opensl; + MA_OPENSL_OUTPUTMIX(pDevice->opensl.pOutputMix)->ReRoute((SLOutputMixItf)pDevice->opensl.pOutputMix, 1, &deviceID_OpenSL); + } + + source.pLocator = &queue; + source.pFormat = (SLDataFormat_PCM*)&pcm; + + outmixLocator.locatorType = SL_DATALOCATOR_OUTPUTMIX; + outmixLocator.outputMix = (SLObjectItf)pDevice->opensl.pOutputMixObj; + + sink.pLocator = &outmixLocator; + sink.pFormat = NULL; + + resultSL = (*g_maEngineSL)->CreateAudioPlayer(g_maEngineSL, (SLObjectItf*)&pDevice->opensl.pAudioPlayerObj, &source, &sink, 1, itfIDs1, itfIDsRequired1); + if (resultSL == SL_RESULT_CONTENT_UNSUPPORTED) { + /* Unsupported format. Fall back to something safer and try again. If this fails, just abort. */ + pcm.formatType = SL_DATAFORMAT_PCM; + pcm.numChannels = 2; + ((SLDataFormat_PCM*)&pcm)->samplesPerSec = SL_SAMPLINGRATE_16; + pcm.bitsPerSample = 16; + pcm.containerSize = pcm.bitsPerSample; /* Always tightly packed for now. */ + pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + resultSL = (*g_maEngineSL)->CreateAudioPlayer(g_maEngineSL, (SLObjectItf*)&pDevice->opensl.pAudioPlayerObj, &source, &sink, 1, itfIDs1, itfIDsRequired1); + } + + if (resultSL != SL_RESULT_SUCCESS) { + ma_device_uninit__opensl(pDevice); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create audio player.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->Realize((SLObjectItf)pDevice->opensl.pAudioPlayerObj, SL_BOOLEAN_FALSE) != SL_RESULT_SUCCESS) { + ma_device_uninit__opensl(pDevice); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize audio player.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioPlayerObj, SL_IID_PLAY, &pDevice->opensl.pAudioPlayer) != 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_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioPlayerObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &pDevice->opensl.pBufferQueuePlayback) != 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_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueuePlayback)->RegisterCallback((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueuePlayback, ma_buffer_queue_callback_playback__opensl_android, pDevice) != SL_RESULT_SUCCESS) { + ma_device_uninit__opensl(pDevice); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to register buffer queue callback.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + /* 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); + + /* Buffer. */ + bufferSizeInFrames = pConfig->bufferSizeInFrames; + if (bufferSizeInFrames == 0) { + bufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pConfig->bufferSizeInMilliseconds, pDevice->playback.internalSampleRate); + } + pDevice->playback.internalPeriods = pConfig->periods; + pDevice->playback.internalBufferSizeInFrames = (bufferSizeInFrames / pDevice->playback.internalPeriods) * pDevice->playback.internalPeriods; + pDevice->opensl.currentBufferIndexPlayback = 0; + + bufferSizeInBytes = pDevice->playback.internalBufferSizeInFrames * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + pDevice->opensl.pBufferPlayback = (ma_uint8*)ma_malloc(bufferSizeInBytes); + 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); + } + MA_ZERO_MEMORY(pDevice->opensl.pBufferPlayback, bufferSizeInBytes); + } + + if (pConfig->deviceType == ma_device_type_duplex) { + ma_uint32 rbSizeInFrames = (ma_uint32)ma_calculate_frame_count_after_src(pDevice->sampleRate, pDevice->capture.internalSampleRate, pDevice->capture.internalBufferSizeInFrames); + ma_result result = ma_pcm_rb_init(pDevice->capture.format, pDevice->capture.channels, rbSizeInFrames, NULL, &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); + } + } + + return MA_SUCCESS; +#else + return MA_NO_BACKEND; /* Non-Android implementations are not supported. */ +#endif +} + +ma_result ma_device_start__opensl(ma_device* pDevice) +{ + SLresult resultSL; + size_t periodSizeInBytes; + ma_uint32 iPeriod; + + 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 and then attempted to start the device. */ + if (g_maOpenSLInitCounter == 0) { + return MA_INVALID_OPERATION; + } + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + resultSL = MA_OPENSL_RECORD(pDevice->opensl.pAudioRecorder)->SetRecordState((SLRecordItf)pDevice->opensl.pAudioRecorder, SL_RECORDSTATE_RECORDING); + if (resultSL != SL_RESULT_SUCCESS) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to start internal capture device.", MA_FAILED_TO_START_BACKEND_DEVICE); + } + + periodSizeInBytes = (pDevice->capture.internalBufferSizeInFrames / pDevice->capture.internalPeriods) * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + for (iPeriod = 0; iPeriod < pDevice->capture.internalPeriods; ++iPeriod) { + resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueueCapture)->Enqueue((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueueCapture, pDevice->opensl.pBufferCapture + (periodSizeInBytes * iPeriod), periodSizeInBytes); + if (resultSL != SL_RESULT_SUCCESS) { + MA_OPENSL_RECORD(pDevice->opensl.pAudioRecorder)->SetRecordState((SLRecordItf)pDevice->opensl.pAudioRecorder, SL_RECORDSTATE_STOPPED); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to enqueue buffer for capture device.", MA_FAILED_TO_START_BACKEND_DEVICE); + } + } + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + resultSL = MA_OPENSL_PLAY(pDevice->opensl.pAudioPlayer)->SetPlayState((SLPlayItf)pDevice->opensl.pAudioPlayer, SL_PLAYSTATE_PLAYING); + if (resultSL != SL_RESULT_SUCCESS) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to start internal playback device.", MA_FAILED_TO_START_BACKEND_DEVICE); + } + + /* In playback mode (no duplex) we need to load some initial buffers. In duplex mode we need to enqueu silent buffers. */ + if (pDevice->type == ma_device_type_duplex) { + MA_ZERO_MEMORY(pDevice->opensl.pBufferPlayback, pDevice->playback.internalBufferSizeInFrames * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + } else { + ma_device__read_frames_from_client(pDevice, pDevice->playback.internalBufferSizeInFrames, pDevice->opensl.pBufferPlayback); + } + + periodSizeInBytes = (pDevice->playback.internalBufferSizeInFrames / pDevice->playback.internalPeriods) * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + for (iPeriod = 0; iPeriod < pDevice->playback.internalPeriods; ++iPeriod) { + resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueuePlayback)->Enqueue((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueuePlayback, pDevice->opensl.pBufferPlayback + (periodSizeInBytes * iPeriod), periodSizeInBytes); + if (resultSL != SL_RESULT_SUCCESS) { + MA_OPENSL_PLAY(pDevice->opensl.pAudioPlayer)->SetPlayState((SLPlayItf)pDevice->opensl.pAudioPlayer, SL_PLAYSTATE_STOPPED); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to enqueue buffer for playback device.", MA_FAILED_TO_START_BACKEND_DEVICE); + } + } + } + + return MA_SUCCESS; +} + +ma_result ma_device_stop__opensl(ma_device* pDevice) +{ + SLresult resultSL; + ma_stop_proc onStop; + + 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 stopping/uninitializing the device. */ + if (g_maOpenSLInitCounter == 0) { + return MA_INVALID_OPERATION; + } + + /* TODO: Wait until all buffers have been processed. Hint: Maybe SLAndroidSimpleBufferQueue::GetState() could be used in a loop? */ + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + resultSL = MA_OPENSL_RECORD(pDevice->opensl.pAudioRecorder)->SetRecordState((SLRecordItf)pDevice->opensl.pAudioRecorder, SL_RECORDSTATE_STOPPED); + if (resultSL != SL_RESULT_SUCCESS) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to stop internal capture device.", MA_FAILED_TO_STOP_BACKEND_DEVICE); + } + + MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueueCapture)->Clear((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueueCapture); + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + resultSL = MA_OPENSL_PLAY(pDevice->opensl.pAudioPlayer)->SetPlayState((SLPlayItf)pDevice->opensl.pAudioPlayer, SL_PLAYSTATE_STOPPED); + if (resultSL != SL_RESULT_SUCCESS) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to stop internal playback device.", MA_FAILED_TO_STOP_BACKEND_DEVICE); + } + + MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueuePlayback)->Clear((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueuePlayback); + } + + /* Make sure the client is aware that the device has stopped. There may be an OpenSL|ES callback for this, but I haven't found it. */ + onStop = pDevice->onStop; + if (onStop) { + onStop(pDevice); + } + + return MA_SUCCESS; +} + + +ma_result ma_context_uninit__opensl(ma_context* pContext) +{ + ma_assert(pContext != NULL); + ma_assert(pContext->backend == ma_backend_opensl); + (void)pContext; + + /* Uninit global data. */ + if (g_maOpenSLInitCounter > 0) { + if (ma_atomic_decrement_32(&g_maOpenSLInitCounter) == 0) { + (*g_maEngineObjectSL)->Destroy(g_maEngineObjectSL); + } + } + + return MA_SUCCESS; +} + +ma_result ma_context_init__opensl(const ma_context_config* pConfig, ma_context* pContext) +{ + ma_assert(pContext != NULL); + + (void)pConfig; + + /* Initialize global data first if applicable. */ + if (ma_atomic_increment_32(&g_maOpenSLInitCounter) == 1) { + SLresult resultSL = slCreateEngine(&g_maEngineObjectSL, 0, NULL, 0, NULL, NULL); + if (resultSL != SL_RESULT_SUCCESS) { + ma_atomic_decrement_32(&g_maOpenSLInitCounter); + return MA_NO_BACKEND; + } + + (*g_maEngineObjectSL)->Realize(g_maEngineObjectSL, SL_BOOLEAN_FALSE); + + resultSL = (*g_maEngineObjectSL)->GetInterface(g_maEngineObjectSL, SL_IID_ENGINE, &g_maEngineSL); + if (resultSL != SL_RESULT_SUCCESS) { + (*g_maEngineObjectSL)->Destroy(g_maEngineObjectSL); + ma_atomic_decrement_32(&g_maOpenSLInitCounter); + return MA_NO_BACKEND; + } + } + + 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; + + return MA_SUCCESS; +} +#endif /* OpenSL|ES */ + + +/****************************************************************************** + +Web Audio Backend + +******************************************************************************/ +#ifdef MA_HAS_WEBAUDIO +#include + +ma_bool32 ma_is_capture_supported__webaudio() +{ + return EM_ASM_INT({ + return (navigator.mediaDevices !== undefined && navigator.mediaDevices.getUserMedia !== undefined); + }, 0) != 0; /* Must pass in a dummy argument for C99 compatibility. */ +} + +#ifdef __cplusplus +extern "C" { +#endif +EMSCRIPTEN_KEEPALIVE void 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. */ + } +} + +EMSCRIPTEN_KEEPALIVE void 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. */ + } +} +#ifdef __cplusplus +} +#endif + +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; +} + +ma_result ma_context_enumerate_devices__webaudio(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_bool32 cbResult = MA_TRUE; + + ma_assert(pContext != NULL); + ma_assert(callback != NULL); + + /* Only supporting default devices for now. */ + + /* Playback. */ + if (cbResult) { + ma_device_info deviceInfo; + ma_zero_object(&deviceInfo); + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); + } + + /* Capture. */ + if (cbResult) { + if (ma_is_capture_supported__webaudio()) { + ma_device_info deviceInfo; + ma_zero_object(&deviceInfo); + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); + } + } + + return MA_SUCCESS; +} + +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) +{ + 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. */ + if (deviceType == ma_device_type_playback) { + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + 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). */ + } + + /* We can query the sample rate by just using a temporary audio context. */ + pDeviceInfo->minSampleRate = EM_ASM_INT({ + try { + var temp = new (window.AudioContext || window.webkitAudioContext)(); + var sampleRate = temp.sampleRate; + temp.close(); + return sampleRate; + } catch(e) { + return 0; + } + }, 0); /* Must pass in a dummy argument for C99 compatibility. */ + pDeviceInfo->maxSampleRate = pDeviceInfo->minSampleRate; + if (pDeviceInfo->minSampleRate == 0) { + return MA_NO_DEVICE; + } + + /* Web Audio only supports f32. */ + pDeviceInfo->formatCount = 1; + pDeviceInfo->formats[0] = ma_format_f32; + + return MA_SUCCESS; +} + + +void ma_device_uninit_by_index__webaudio(ma_device* pDevice, ma_device_type deviceType, int deviceIndex) +{ + ma_assert(pDevice != NULL); + + EM_ASM({ + var device = miniaudio.get_device_by_index($0); + + /* Make sure all nodes are disconnected and marked for collection. */ + if (device.scriptNode !== undefined) { + device.scriptNode.onaudioprocess = function(e) {}; /* We want to reset the callback to ensure it doesn't get called after AudioContext.close() has returned. Shouldn't happen since we're disconnecting, but just to be safe... */ + device.scriptNode.disconnect(); + device.scriptNode = undefined; + } + if (device.streamNode !== undefined) { + device.streamNode.disconnect(); + device.streamNode = undefined; + } + + /* + Stop the device. I think there is a chance the callback could get fired after calling this, hence why we want + to clear the callback before closing. + */ + device.webaudio.close(); + device.webaudio = undefined; + + /* Can't forget to free the intermediary buffer. This is the buffer that's shared between JavaScript and C. */ + if (device.intermediaryBuffer !== undefined) { + Module._free(device.intermediaryBuffer); + device.intermediaryBuffer = undefined; + device.intermediaryBufferView = undefined; + device.intermediaryBufferSizeInBytes = undefined; + } + + /* Make sure the device is untracked so the slot can be reused later. */ + miniaudio.untrack_device_by_index($0); + }, deviceIndex, deviceType); +} + +void ma_device_uninit__webaudio(ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + 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); + } + + if (pDevice->type == ma_device_type_duplex) { + ma_pcm_rb_uninit(&pDevice->webaudio.duplexRB); + } +} + +ma_result ma_device_init_by_type__webaudio(ma_context* pContext, const ma_device_config* pConfig, ma_device_type deviceType, ma_device* pDevice) +{ + int deviceIndex; + ma_uint32 internalBufferSizeInFrames; + + 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 && !ma_is_capture_supported__webaudio()) { + return MA_NO_DEVICE; + } + + /* Try calculating an appropriate buffer size. */ + internalBufferSizeInFrames = pConfig->bufferSizeInFrames; + if (internalBufferSizeInFrames == 0) { + internalBufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pConfig->bufferSizeInMilliseconds, pConfig->sampleRate); + } + + /* The size of the buffer must be a power of 2 and between 256 and 16384. */ + if (internalBufferSizeInFrames < 256) { + internalBufferSizeInFrames = 256; + } else if (internalBufferSizeInFrames > 16384) { + internalBufferSizeInFrames = 16384; + } else { + internalBufferSizeInFrames = ma_next_power_of_2(internalBufferSizeInFrames); + } + + /* 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({ + var channels = $0; + var sampleRate = $1; + var bufferSize = $2; /* In PCM frames. */ + var isCapture = $3; + var pDevice = $4; + + if (typeof(miniaudio) === 'undefined') { + return -1; /* Context not initialized. */ + } + + var device = {}; + + /* The AudioContext must be created in a suspended state. */ + device.webaudio = new (window.AudioContext || window.webkitAudioContext)({sampleRate:sampleRate}); + device.webaudio.suspend(); + + /* + 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 + JavaScript and C it needs to be allocated and freed using Module._malloc() and Module._free(). + */ + device.intermediaryBufferSizeInBytes = channels * bufferSize * 4; + device.intermediaryBuffer = Module._malloc(device.intermediaryBufferSizeInBytes); + device.intermediaryBufferView = new Float32Array(Module.HEAPF32.buffer, device.intermediaryBuffer, device.intermediaryBufferSizeInBytes); + + /* + Both playback and capture devices use a ScriptProcessorNode for performing per-sample operations. + + ScriptProcessorNode is actually deprecated so this is likely to be temporary. The way this works for playback is very simple. You just set a callback + that's periodically fired, just like a normal audio callback function. But apparently this design is "flawed" and is now deprecated in favour of + something called AudioWorklets which _forces_ you to load a _separate_ .js file at run time... nice... Hopefully ScriptProcessorNode will continue to + work for years to come, but this may need to change to use AudioSourceBufferNode instead, which I think is what Emscripten uses for it's built-in SDL + implementation. I'll be avoiding that insane AudioWorklet API like the plague... + + For capture it is a bit unintuitive. We use the ScriptProccessorNode _only_ to get the raw PCM data. It is connected to an AudioContext just like the + playback case, however we just output silence to the AudioContext instead of passing any real data. It would make more sense to me to use the + MediaRecorder API, but unfortunately you need to specify a MIME time (Opus, Vorbis, etc.) for the binary blob that's returned to the client, but I've + been unable to figure out how to get this as raw PCM. The closes I can think is to use the MIME type for WAV files and just parse it, but I don't know + how well this would work. Although ScriptProccessorNode is deprecated, in practice it seems to have pretty good browser support so I'm leaving it like + this for now. If anything knows how I could get raw PCM data using the MediaRecorder API please let me know! + */ + device.scriptNode = device.webaudio.createScriptProcessor(bufferSize, channels, channels); + + if (isCapture) { + device.scriptNode.onaudioprocess = function(e) { + if (device.intermediaryBuffer === undefined) { + return; /* This means the device has been uninitialized. */ + } + + /* 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); + } + + /* There are some situations where we may want to send silence to the client. */ + var sendSilence = false; + if (device.streamNode === undefined) { + sendSilence = true; + } + + /* Sanity check. This will never happen, right? */ + if (e.inputBuffer.numberOfChannels != channels) { + console.log("Capture: Channel count mismatch. " + e.inputBufer.numberOfChannels + " != " + channels + ". Sending silence."); + sendSilence = true; + } + + /* This looped design guards against the situation where e.inputBuffer is a different size to the original buffer size. Should never happen in practice. */ + var totalFramesProcessed = 0; + while (totalFramesProcessed < e.inputBuffer.length) { + var framesRemaining = e.inputBuffer.length - totalFramesProcessed; + var framesToProcess = framesRemaining; + if (framesToProcess > (device.intermediaryBufferSizeInBytes/channels/4)) { + framesToProcess = (device.intermediaryBufferSizeInBytes/channels/4); + } + + /* We need to do the reverse of the playback case. We need to interleave the input data and copy it into the intermediary buffer. Then we send it to the client. */ + if (sendSilence) { + device.intermediaryBufferView.fill(0.0); + } else { + for (var iFrame = 0; iFrame < framesToProcess; ++iFrame) { + for (var iChannel = 0; iChannel < e.inputBuffer.numberOfChannels; ++iChannel) { + device.intermediaryBufferView[iFrame*channels + iChannel] = e.inputBuffer.getChannelData(iChannel)[totalFramesProcessed + iFrame]; + } + } + } + + /* Send data to the client from our intermediary buffer. */ + ccall("ma_device_process_pcm_frames_capture__webaudio", "undefined", ["number", "number", "number"], [pDevice, framesToProcess, device.intermediaryBuffer]); + + totalFramesProcessed += framesToProcess; + } + }; + + navigator.mediaDevices.getUserMedia({audio:true, video:false}) + .then(function(stream) { + device.streamNode = device.webaudio.createMediaStreamSource(stream); + device.streamNode.connect(device.scriptNode); + device.scriptNode.connect(device.webaudio.destination); + }) + .catch(function(error) { + /* I think this should output silence... */ + device.scriptNode.connect(device.webaudio.destination); + }); + } else { + device.scriptNode.onaudioprocess = function(e) { + if (device.intermediaryBuffer === undefined) { + return; /* This means the device has been uninitialized. */ + } + + var outputSilence = false; + + /* Sanity check. This will never happen, right? */ + if (e.outputBuffer.numberOfChannels != channels) { + console.log("Playback: Channel count mismatch. " + e.outputBufer.numberOfChannels + " != " + channels + ". Outputting silence."); + outputSilence = true; + return; + } + + /* This looped design guards against the situation where e.outputBuffer is a different size to the original buffer size. Should never happen in practice. */ + var totalFramesProcessed = 0; + while (totalFramesProcessed < e.outputBuffer.length) { + var framesRemaining = e.outputBuffer.length - totalFramesProcessed; + var framesToProcess = framesRemaining; + if (framesToProcess > (device.intermediaryBufferSizeInBytes/channels/4)) { + framesToProcess = (device.intermediaryBufferSizeInBytes/channels/4); + } + + /* Read data from the client into our intermediary buffer. */ + ccall("ma_device_process_pcm_frames_playback__webaudio", "undefined", ["number", "number", "number"], [pDevice, framesToProcess, device.intermediaryBuffer]); + + /* At this point we'll have data in our intermediary buffer which we now need to deinterleave and copy over to the output buffers. */ + if (outputSilence) { + for (var iChannel = 0; iChannel < e.outputBuffer.numberOfChannels; ++iChannel) { + e.outputBuffer.getChannelData(iChannel).fill(0.0); + } + } else { + for (var iChannel = 0; iChannel < e.outputBuffer.numberOfChannels; ++iChannel) { + for (var iFrame = 0; iFrame < framesToProcess; ++iFrame) { + e.outputBuffer.getChannelData(iChannel)[totalFramesProcessed + iFrame] = device.intermediaryBufferView[iFrame*channels + iChannel]; + } + } + } + + totalFramesProcessed += framesToProcess; + } + }; + + device.scriptNode.connect(device.webaudio.destination); + } + + return miniaudio.track_device(device); + }, (deviceType == ma_device_type_capture) ? pConfig->capture.channels : pConfig->playback.channels, pConfig->sampleRate, internalBufferSizeInFrames, 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.internalBufferSizeInFrames = internalBufferSizeInFrames; + pDevice->capture.internalPeriods = 1; + } 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.internalBufferSizeInFrames = internalBufferSizeInFrames; + pDevice->playback.internalPeriods = 1; + } + + return MA_SUCCESS; +} + +ma_result ma_device_init__webaudio(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +{ + ma_result result; + + /* 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)) { + 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); + 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); + 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); + } + return result; + } + } + + /* + 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 internalBufferSizeInFrames based on + the external sample rate. + */ + if (pConfig->deviceType == ma_device_type_duplex) { + ma_uint32 rbSizeInFrames = (ma_uint32)ma_calculate_frame_count_after_src(pDevice->sampleRate, pDevice->capture.internalSampleRate, pDevice->capture.internalBufferSizeInFrames) * 2; + result = ma_pcm_rb_init(pDevice->capture.format, pDevice->capture.channels, rbSizeInFrames, NULL, &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; + } + } + + return MA_SUCCESS; +} + +ma_result ma_device_start__webaudio(ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + EM_ASM({ + miniaudio.get_device_by_index($0).webaudio.resume(); + }, 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(); + }, pDevice->webaudio.indexPlayback); + } + + return MA_SUCCESS; +} + +ma_result ma_device_stop__webaudio(ma_device* pDevice) +{ + ma_assert(pDevice != NULL); + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + EM_ASM({ + miniaudio.get_device_by_index($0).webaudio.suspend(); + }, 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(); + }, pDevice->webaudio.indexPlayback); + } + + ma_stop_proc onStop = pDevice->onStop; + if (onStop) { + onStop(pDevice); + } + + return MA_SUCCESS; +} + +ma_result ma_context_uninit__webaudio(ma_context* pContext) +{ + ma_assert(pContext != NULL); + ma_assert(pContext->backend == ma_backend_webaudio); + + /* Nothing needs to be done here. */ + (void)pContext; + + return MA_SUCCESS; +} + +ma_result ma_context_init__webaudio(const ma_context_config* pConfig, ma_context* pContext) +{ + int resultFromJS; + + ma_assert(pContext != NULL); + + /* Here is where our global JavaScript object is initialized. */ + resultFromJS = EM_ASM_INT({ + if ((window.AudioContext || window.webkitAudioContext) === undefined) { + return 0; /* Web Audio not supported. */ + } + + 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) { + if (miniaudio.devices[iDevice] == null) { + miniaudio.devices[iDevice] = device; + 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) { + miniaudio.devices.pop(); + } else { + break; + } + } + }; + + miniaudio.untrack_device = function(device) { + for (var iDevice = 0; iDevice < miniaudio.devices.length; ++iDevice) { + if (miniaudio.devices[iDevice] == device) { + return miniaudio.untrack_device_by_index(iDevice); + } + } + }; + + miniaudio.get_device_by_index = function(deviceIndex) { + return miniaudio.devices[deviceIndex]; + }; + } + + return 1; + }, 0); /* Must pass in a dummy argument for C99 compatibility. */ + + if (resultFromJS != 1) { + return MA_FAILED_TO_INIT_BACKEND; + } + + + 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 */ + + + +ma_bool32 ma__is_channel_map_valid(const ma_channel* channelMap, ma_uint32 channels) +{ + /* A blank channel map should be allowed, in which case it should use an appropriate default which will depend on context. */ + if (channelMap[0] != MA_CHANNEL_NONE) { + ma_uint32 iChannel; + + if (channels == 0) { + return MA_FALSE; /* No channels. */ + } + + /* A channel cannot be present in the channel map more than once. */ + for (iChannel = 0; iChannel < channels; ++iChannel) { + ma_uint32 jChannel; + for (jChannel = iChannel + 1; jChannel < channels; ++jChannel) { + if (channelMap[iChannel] == channelMap[jChannel]) { + return MA_FALSE; + } + } + } + } + + return MA_TRUE; +} + + +void ma_device__post_init_setup(ma_device* pDevice, ma_device_type deviceType) +{ + ma_assert(pDevice != NULL); + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + if (pDevice->capture.usingDefaultFormat) { + pDevice->capture.format = pDevice->capture.internalFormat; + } + if (pDevice->capture.usingDefaultChannels) { + pDevice->capture.channels = pDevice->capture.internalChannels; + } + if (pDevice->capture.usingDefaultChannelMap) { + 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 (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + if (pDevice->playback.usingDefaultFormat) { + pDevice->playback.format = pDevice->playback.internalFormat; + } + if (pDevice->playback.usingDefaultChannels) { + pDevice->playback.channels = pDevice->playback.internalChannels; + } + if (pDevice->playback.usingDefaultChannelMap) { + 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->usingDefaultSampleRate) { + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + pDevice->sampleRate = pDevice->capture.internalSampleRate; + } else { + pDevice->sampleRate = pDevice->playback.internalSampleRate; + } + } + + /* PCM converters. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + /* Converting from internal device format to public format. */ + ma_pcm_converter_config converterConfig = ma_pcm_converter_config_init_new(); + converterConfig.neverConsumeEndOfInput = MA_TRUE; + converterConfig.pUserData = pDevice; + converterConfig.formatIn = pDevice->capture.internalFormat; + converterConfig.channelsIn = pDevice->capture.internalChannels; + converterConfig.sampleRateIn = pDevice->capture.internalSampleRate; + ma_channel_map_copy(converterConfig.channelMapIn, pDevice->capture.internalChannelMap, pDevice->capture.internalChannels); + converterConfig.formatOut = pDevice->capture.format; + converterConfig.channelsOut = pDevice->capture.channels; + converterConfig.sampleRateOut = pDevice->sampleRate; + ma_channel_map_copy(converterConfig.channelMapOut, pDevice->capture.channelMap, pDevice->capture.channels); + converterConfig.onRead = ma_device__pcm_converter__on_read_from_buffer_capture; + ma_pcm_converter_init(&converterConfig, &pDevice->capture.converter); + } + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + /* Converting from public format to device format. */ + ma_pcm_converter_config converterConfig = ma_pcm_converter_config_init_new(); + converterConfig.neverConsumeEndOfInput = MA_TRUE; + converterConfig.pUserData = pDevice; + converterConfig.formatIn = pDevice->playback.format; + converterConfig.channelsIn = pDevice->playback.channels; + converterConfig.sampleRateIn = pDevice->sampleRate; + ma_channel_map_copy(converterConfig.channelMapIn, pDevice->playback.channelMap, pDevice->playback.channels); + converterConfig.formatOut = pDevice->playback.internalFormat; + converterConfig.channelsOut = pDevice->playback.internalChannels; + converterConfig.sampleRateOut = pDevice->playback.internalSampleRate; + ma_channel_map_copy(converterConfig.channelMapOut, pDevice->playback.internalChannelMap, pDevice->playback.internalChannels); + if (deviceType == ma_device_type_playback) { + if (pDevice->type == ma_device_type_playback) { + converterConfig.onRead = ma_device__on_read_from_client; + } else { + converterConfig.onRead = ma_device__pcm_converter__on_read_from_buffer_playback; + } + } else { + converterConfig.onRead = ma_device__pcm_converter__on_read_from_buffer_playback; + } + ma_pcm_converter_init(&converterConfig, &pDevice->playback.converter); + } +} + + +ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) +{ + ma_device* pDevice = (ma_device*)pData; + ma_assert(pDevice != NULL); + +#ifdef MA_WIN32 + ma_CoInitializeEx(pDevice->pContext, NULL, MA_COINIT_VALUE); +#endif + + /* + When the device is being initialized it's initial state is set to MA_STATE_UNINITIALIZED. Before returning from + ma_device_init(), the state needs to be set to something valid. In miniaudio the device's default state immediately + after initialization is stopped, so therefore we need to mark the device as such. miniaudio will wait on the worker + thread to signal an event to know when the worker thread is ready for action. + */ + ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_event_signal(&pDevice->stopEvent); + + for (;;) { /* <-- This loop just keeps the thread alive. The main audio loop is inside. */ + ma_stop_proc onStop; + + /* We wait on an event to know when something has requested that the device be started and the main loop entered. */ + ma_event_wait(&pDevice->wakeupEvent); + + /* Default result code. */ + 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) { + break; + } + + /* + Getting to this point means the device is wanting to get started. The function that has requested that the device + 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); + + /* 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); + } else { + ma_uint32 periodSizeInFrames; + + /* When a device is using miniaudio's generic worker thread they must implement onDeviceRead or onDeviceWrite, depending on the device type. */ + ma_assert( + (pDevice->type == ma_device_type_playback && pDevice->pContext->onDeviceWrite != NULL) || + (pDevice->type == ma_device_type_capture && pDevice->pContext->onDeviceRead != NULL) || + (pDevice->type == ma_device_type_duplex && pDevice->pContext->onDeviceWrite != NULL && pDevice->pContext->onDeviceRead != NULL) + ); + + if (pDevice->type == ma_device_type_capture) { + ma_assert(pDevice->capture.internalBufferSizeInFrames >= pDevice->capture.internalPeriods); + periodSizeInFrames = pDevice->capture.internalBufferSizeInFrames / pDevice->capture.internalPeriods; + } else if (pDevice->type == ma_device_type_playback) { + ma_assert(pDevice->playback.internalBufferSizeInFrames >= pDevice->playback.internalPeriods); + periodSizeInFrames = pDevice->playback.internalBufferSizeInFrames / pDevice->playback.internalPeriods; + } else { + ma_assert(pDevice->capture.internalBufferSizeInFrames >= pDevice->capture.internalPeriods); + ma_assert(pDevice->playback.internalBufferSizeInFrames >= pDevice->playback.internalPeriods); + periodSizeInFrames = ma_min( + pDevice->capture.internalBufferSizeInFrames / pDevice->capture.internalPeriods, + pDevice->playback.internalBufferSizeInFrames / pDevice->playback.internalPeriods + ); + } + + /* + With the blocking API, the device is started automatically in read()/write(). All we need to do is enter the loop and just keep reading + or writing based on the period size. + */ + + /* Main Loop */ + ma_assert(periodSizeInFrames >= 1); + while (ma_device__get_state(pDevice) == MA_STATE_STARTED) { + ma_result result = MA_SUCCESS; + ma_uint32 totalFramesProcessed = 0; + + if (pDevice->type == ma_device_type_duplex) { + /* The process is device_read -> convert -> callback -> convert -> device_write. */ + ma_uint8 captureDeviceData[4096]; + ma_uint32 captureDeviceDataCapInFrames = sizeof(captureDeviceData) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + + while (totalFramesProcessed < periodSizeInFrames) { + ma_device_callback_proc onData; + ma_uint32 framesRemaining = periodSizeInFrames - totalFramesProcessed; + ma_uint32 framesToProcess = framesRemaining; + if (framesToProcess > captureDeviceDataCapInFrames) { + framesToProcess = captureDeviceDataCapInFrames; + } + + result = pDevice->pContext->onDeviceRead(pDevice, captureDeviceData, framesToProcess); + if (result != MA_SUCCESS) { + break; + } + + onData = pDevice->onData; + if (onData != NULL) { + pDevice->capture._dspFrameCount = framesToProcess; + pDevice->capture._dspFrames = captureDeviceData; + + /* We need to process every input frame. */ + for (;;) { + ma_uint8 capturedData[4096]; /* In capture.format/channels format */ + ma_uint8 playbackData[4096]; /* In playback.format/channels format */ + + ma_uint32 capturedDataCapInFrames = sizeof(capturedData) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); + ma_uint32 playbackDataCapInFrames = sizeof(playbackData) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); + + ma_uint32 capturedFramesToTryProcessing = ma_min(capturedDataCapInFrames, playbackDataCapInFrames); + ma_uint32 capturedFramesToProcess = (ma_uint32)ma_pcm_converter_read(&pDevice->capture.converter, capturedData, capturedFramesToTryProcessing); + if (capturedFramesToProcess == 0) { + break; /* Don't fire the data callback with zero frames. */ + } + + onData(pDevice, playbackData, capturedData, capturedFramesToProcess); + + /* At this point the playbackData buffer should be holding data that needs to be written to the device. */ + pDevice->playback._dspFrameCount = capturedFramesToProcess; + pDevice->playback._dspFrames = playbackData; + for (;;) { + ma_uint8 playbackDeviceData[4096]; + + ma_uint32 playbackDeviceDataCapInFrames = sizeof(playbackDeviceData) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + ma_uint32 playbackDeviceFramesCount = (ma_uint32)ma_pcm_converter_read(&pDevice->playback.converter, playbackDeviceData, playbackDeviceDataCapInFrames); + if (playbackDeviceFramesCount == 0) { + break; + } + + result = pDevice->pContext->onDeviceWrite(pDevice, playbackDeviceData, playbackDeviceFramesCount); + if (result != MA_SUCCESS) { + break; + } + + if (playbackDeviceFramesCount < playbackDeviceDataCapInFrames) { + break; + } + } + + if (capturedFramesToProcess < capturedFramesToTryProcessing) { + break; + } + + /* In case an error happened from onDeviceWrite()... */ + if (result != MA_SUCCESS) { + break; + } + } + } + + totalFramesProcessed += framesToProcess; + } + } else { + ma_uint8 buffer[4096]; + ma_uint32 bufferSizeInFrames; + if (pDevice->type == ma_device_type_capture) { + bufferSizeInFrames = sizeof(buffer) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + } else { + bufferSizeInFrames = sizeof(buffer) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + } + + while (totalFramesProcessed < periodSizeInFrames) { + ma_uint32 framesRemaining = periodSizeInFrames - totalFramesProcessed; + ma_uint32 framesToProcess = framesRemaining; + if (framesToProcess > bufferSizeInFrames) { + framesToProcess = bufferSizeInFrames; + } + + if (pDevice->type == ma_device_type_playback) { + ma_device__read_frames_from_client(pDevice, framesToProcess, buffer); + result = pDevice->pContext->onDeviceWrite(pDevice, buffer, framesToProcess); + } else { + result = pDevice->pContext->onDeviceRead(pDevice, buffer, framesToProcess); + ma_device__send_frames_to_client(pDevice, framesToProcess, buffer); + } + + totalFramesProcessed += framesToProcess; + } + } + + /* Get out of the loop if read()/write() returned an error. It probably means the device has been stopped. */ + if (result != MA_SUCCESS) { + break; + } + } + } + + /* + Getting here means we have broken from the main loop which happens the application has requested that device be stopped. Note that this + 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); + } + } + + /* After the device has stopped, make sure an event is posted. */ + onStop = pDevice->onStop; + if (onStop) { + onStop(pDevice); + } + + /* + A function somewhere is waiting for the device to have stopped for real so we need to signal an event to allow it to continue. Note that + 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) { + ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_event_signal(&pDevice->stopEvent); + } + } + + /* Make sure we aren't continuously waiting on a stop event. */ + ma_event_signal(&pDevice->stopEvent); /* <-- Is this still needed? */ + +#ifdef MA_WIN32 + ma_CoUninitialize(pDevice->pContext); +#endif + + return (ma_thread_result)0; +} + + +/* Helper for determining whether or not the given device is initialized. */ +ma_bool32 ma_device__is_initialized(ma_device* pDevice) +{ + if (pDevice == NULL) { + return MA_FALSE; + } + + return ma_device__get_state(pDevice) != MA_STATE_UNINITIALIZED; +} + + +#ifdef MA_WIN32 +ma_result ma_context_uninit_backend_apis__win32(ma_context* pContext) +{ + ma_CoUninitialize(pContext); + ma_dlclose(pContext, pContext->win32.hUser32DLL); + ma_dlclose(pContext, pContext->win32.hOle32DLL); + ma_dlclose(pContext, pContext->win32.hAdvapi32DLL); + + return MA_SUCCESS; +} + +ma_result ma_context_init_backend_apis__win32(ma_context* pContext) +{ +#ifdef MA_WIN32_DESKTOP + /* Ole32.dll */ + pContext->win32.hOle32DLL = ma_dlopen(pContext, "ole32.dll"); + if (pContext->win32.hOle32DLL == NULL) { + return MA_FAILED_TO_INIT_BACKEND; + } + + pContext->win32.CoInitializeEx = (ma_proc)ma_dlsym(pContext, pContext->win32.hOle32DLL, "CoInitializeEx"); + pContext->win32.CoUninitialize = (ma_proc)ma_dlsym(pContext, pContext->win32.hOle32DLL, "CoUninitialize"); + pContext->win32.CoCreateInstance = (ma_proc)ma_dlsym(pContext, pContext->win32.hOle32DLL, "CoCreateInstance"); + pContext->win32.CoTaskMemFree = (ma_proc)ma_dlsym(pContext, pContext->win32.hOle32DLL, "CoTaskMemFree"); + pContext->win32.PropVariantClear = (ma_proc)ma_dlsym(pContext, pContext->win32.hOle32DLL, "PropVariantClear"); + pContext->win32.StringFromGUID2 = (ma_proc)ma_dlsym(pContext, pContext->win32.hOle32DLL, "StringFromGUID2"); + + + /* User32.dll */ + pContext->win32.hUser32DLL = ma_dlopen(pContext, "user32.dll"); + if (pContext->win32.hUser32DLL == NULL) { + return MA_FAILED_TO_INIT_BACKEND; + } + + pContext->win32.GetForegroundWindow = (ma_proc)ma_dlsym(pContext, pContext->win32.hUser32DLL, "GetForegroundWindow"); + pContext->win32.GetDesktopWindow = (ma_proc)ma_dlsym(pContext, pContext->win32.hUser32DLL, "GetDesktopWindow"); + + + /* Advapi32.dll */ + pContext->win32.hAdvapi32DLL = ma_dlopen(pContext, "advapi32.dll"); + if (pContext->win32.hAdvapi32DLL == NULL) { + return MA_FAILED_TO_INIT_BACKEND; + } + + pContext->win32.RegOpenKeyExA = (ma_proc)ma_dlsym(pContext, pContext->win32.hAdvapi32DLL, "RegOpenKeyExA"); + pContext->win32.RegCloseKey = (ma_proc)ma_dlsym(pContext, pContext->win32.hAdvapi32DLL, "RegCloseKey"); + pContext->win32.RegQueryValueExA = (ma_proc)ma_dlsym(pContext, pContext->win32.hAdvapi32DLL, "RegQueryValueExA"); +#endif + + ma_CoInitializeEx(pContext, NULL, MA_COINIT_VALUE); + return MA_SUCCESS; +} +#else +ma_result ma_context_uninit_backend_apis__nix(ma_context* pContext) +{ +#if defined(MA_USE_RUNTIME_LINKING_FOR_PTHREAD) && !defined(MA_NO_RUNTIME_LINKING) + ma_dlclose(pContext, pContext->posix.pthreadSO); +#else + (void)pContext; +#endif + + return MA_SUCCESS; +} + +ma_result ma_context_init_backend_apis__nix(ma_context* pContext) +{ + /* pthread */ +#if defined(MA_USE_RUNTIME_LINKING_FOR_PTHREAD) && !defined(MA_NO_RUNTIME_LINKING) + const char* libpthreadFileNames[] = { + "libpthread.so", + "libpthread.so.0", + "libpthread.dylib" + }; + size_t i; + + for (i = 0; i < sizeof(libpthreadFileNames) / sizeof(libpthreadFileNames[0]); ++i) { + pContext->posix.pthreadSO = ma_dlopen(pContext, libpthreadFileNames[i]); + if (pContext->posix.pthreadSO != NULL) { + break; + } + } + + if (pContext->posix.pthreadSO == NULL) { + return MA_FAILED_TO_INIT_BACKEND; + } + + pContext->posix.pthread_create = (ma_proc)ma_dlsym(pContext, pContext->posix.pthreadSO, "pthread_create"); + pContext->posix.pthread_join = (ma_proc)ma_dlsym(pContext, pContext->posix.pthreadSO, "pthread_join"); + pContext->posix.pthread_mutex_init = (ma_proc)ma_dlsym(pContext, pContext->posix.pthreadSO, "pthread_mutex_init"); + pContext->posix.pthread_mutex_destroy = (ma_proc)ma_dlsym(pContext, pContext->posix.pthreadSO, "pthread_mutex_destroy"); + pContext->posix.pthread_mutex_lock = (ma_proc)ma_dlsym(pContext, pContext->posix.pthreadSO, "pthread_mutex_lock"); + pContext->posix.pthread_mutex_unlock = (ma_proc)ma_dlsym(pContext, pContext->posix.pthreadSO, "pthread_mutex_unlock"); + pContext->posix.pthread_cond_init = (ma_proc)ma_dlsym(pContext, pContext->posix.pthreadSO, "pthread_cond_init"); + pContext->posix.pthread_cond_destroy = (ma_proc)ma_dlsym(pContext, pContext->posix.pthreadSO, "pthread_cond_destroy"); + pContext->posix.pthread_cond_wait = (ma_proc)ma_dlsym(pContext, pContext->posix.pthreadSO, "pthread_cond_wait"); + pContext->posix.pthread_cond_signal = (ma_proc)ma_dlsym(pContext, pContext->posix.pthreadSO, "pthread_cond_signal"); + pContext->posix.pthread_attr_init = (ma_proc)ma_dlsym(pContext, pContext->posix.pthreadSO, "pthread_attr_init"); + pContext->posix.pthread_attr_destroy = (ma_proc)ma_dlsym(pContext, pContext->posix.pthreadSO, "pthread_attr_destroy"); + pContext->posix.pthread_attr_setschedpolicy = (ma_proc)ma_dlsym(pContext, pContext->posix.pthreadSO, "pthread_attr_setschedpolicy"); + pContext->posix.pthread_attr_getschedparam = (ma_proc)ma_dlsym(pContext, pContext->posix.pthreadSO, "pthread_attr_getschedparam"); + pContext->posix.pthread_attr_setschedparam = (ma_proc)ma_dlsym(pContext, pContext->posix.pthreadSO, "pthread_attr_setschedparam"); +#else + pContext->posix.pthread_create = (ma_proc)pthread_create; + pContext->posix.pthread_join = (ma_proc)pthread_join; + pContext->posix.pthread_mutex_init = (ma_proc)pthread_mutex_init; + pContext->posix.pthread_mutex_destroy = (ma_proc)pthread_mutex_destroy; + pContext->posix.pthread_mutex_lock = (ma_proc)pthread_mutex_lock; + pContext->posix.pthread_mutex_unlock = (ma_proc)pthread_mutex_unlock; + pContext->posix.pthread_cond_init = (ma_proc)pthread_cond_init; + pContext->posix.pthread_cond_destroy = (ma_proc)pthread_cond_destroy; + pContext->posix.pthread_cond_wait = (ma_proc)pthread_cond_wait; + pContext->posix.pthread_cond_signal = (ma_proc)pthread_cond_signal; + pContext->posix.pthread_attr_init = (ma_proc)pthread_attr_init; + pContext->posix.pthread_attr_destroy = (ma_proc)pthread_attr_destroy; +#if !defined(__EMSCRIPTEN__) + pContext->posix.pthread_attr_setschedpolicy = (ma_proc)pthread_attr_setschedpolicy; + pContext->posix.pthread_attr_getschedparam = (ma_proc)pthread_attr_getschedparam; + pContext->posix.pthread_attr_setschedparam = (ma_proc)pthread_attr_setschedparam; +#endif +#endif + + return MA_SUCCESS; +} +#endif + +ma_result ma_context_init_backend_apis(ma_context* pContext) +{ + ma_result result; +#ifdef MA_WIN32 + result = ma_context_init_backend_apis__win32(pContext); +#else + result = ma_context_init_backend_apis__nix(pContext); +#endif + + return result; +} + +ma_result ma_context_uninit_backend_apis(ma_context* pContext) +{ + ma_result result; +#ifdef MA_WIN32 + result = ma_context_uninit_backend_apis__win32(pContext); +#else + result = ma_context_uninit_backend_apis__nix(pContext); +#endif + + return result; +} + + +ma_bool32 ma_context_is_backend_asynchronous(ma_context* pContext) +{ + return pContext->isBackendAsynchronous; +} + +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_backend defaultBackends[ma_backend_null+1]; + ma_uint32 iBackend; + ma_backend* pBackendsToIterate; + ma_uint32 backendsToIterateCount; + + if (pContext == NULL) { + return MA_INVALID_ARGS; + } + + 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(); + } + + pContext->logCallback = config.logCallback; + pContext->threadPriority = config.threadPriority; + pContext->pUserData = config.pUserData; + + /* Backend APIs need to be initialized first. This is where external libraries will be loaded and linked. */ + result = ma_context_init_backend_apis(pContext); + if (result != MA_SUCCESS) { + return result; + } + + for (iBackend = 0; iBackend <= ma_backend_null; ++iBackend) { + defaultBackends[iBackend] = (ma_backend)iBackend; + } + + pBackendsToIterate = (ma_backend*)backends; + backendsToIterateCount = backendCount; + if (pBackendsToIterate == NULL) { + pBackendsToIterate = (ma_backend*)defaultBackends; + backendsToIterateCount = ma_countof(defaultBackends); + } + + ma_assert(pBackendsToIterate != NULL); + + for (iBackend = 0; iBackend < backendsToIterateCount; ++iBackend) { + ma_backend backend = pBackendsToIterate[iBackend]; + + result = MA_NO_BACKEND; + switch (backend) { + #ifdef MA_HAS_WASAPI + case ma_backend_wasapi: + { + result = ma_context_init__wasapi(&config, pContext); + } break; + #endif + #ifdef MA_HAS_DSOUND + case ma_backend_dsound: + { + result = ma_context_init__dsound(&config, pContext); + } 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); + } break; + #endif + #ifdef MA_HAS_COREAUDIO + case ma_backend_coreaudio: + { + result = ma_context_init__coreaudio(&config, pContext); + } break; + #endif + #ifdef MA_HAS_SNDIO + case ma_backend_sndio: + { + result = ma_context_init__sndio(&config, pContext); + } break; + #endif + #ifdef MA_HAS_AUDIO4 + case ma_backend_audio4: + { + result = ma_context_init__audio4(&config, pContext); + } break; + #endif + #ifdef MA_HAS_OSS + case ma_backend_oss: + { + result = ma_context_init__oss(&config, pContext); + } break; + #endif + #ifdef MA_HAS_AAUDIO + case ma_backend_aaudio: + { + result = ma_context_init__aaudio(&config, pContext); + } break; + #endif + #ifdef MA_HAS_OPENSL + case ma_backend_opensl: + { + result = ma_context_init__opensl(&config, pContext); + } break; + #endif + #ifdef MA_HAS_WEBAUDIO + case ma_backend_webaudio: + { + result = ma_context_init__webaudio(&config, pContext); + } break; + #endif + #ifdef MA_HAS_NULL + case ma_backend_null: + { + result = ma_context_init__null(&config, pContext); + } break; + #endif + + default: break; + } + + /* If this iteration was successful, return. */ + if (result == MA_SUCCESS) { + result = ma_mutex_init(pContext, &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.", MA_FAILED_TO_CREATE_MUTEX); + } + result = ma_mutex_init(pContext, &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.", MA_FAILED_TO_CREATE_MUTEX); + } + +#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; + } + } + + /* If we get here it means an error occurred. */ + ma_zero_object(pContext); /* Safety. */ + return MA_NO_BACKEND; +} + +ma_result ma_context_uninit(ma_context* pContext) +{ + if (pContext == NULL) { + return MA_INVALID_ARGS; + } + + pContext->onUninit(pContext); + + ma_mutex_uninit(&pContext->deviceEnumLock); + ma_mutex_uninit(&pContext->deviceInfoLock); + ma_free(pContext->pDeviceInfos); + ma_context_uninit_backend_apis(pContext); + + return MA_SUCCESS; +} + + +ma_result ma_context_enumerate_devices(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_result result; + + if (pContext == NULL || pContext->onEnumDevices == NULL || callback == NULL) { + return MA_INVALID_ARGS; + } + + ma_mutex_lock(&pContext->deviceEnumLock); + { + result = pContext->onEnumDevices(pContext, callback, pUserData); + } + ma_mutex_unlock(&pContext->deviceEnumLock); + + return result; +} + + +ma_bool32 ma_context_get_devices__enum_callback(ma_context* pContext, ma_device_type deviceType, const ma_device_info* pInfo, void* pUserData) +{ + /* + We need to insert the device info into our main internal buffer. Where it goes depends on the device type. If it's a capture device + it's just appended to the end. If it's a playback device it's inserted just before the first capture device. + */ + + /* + First make sure we have room. Since the number of devices we add to the list is usually relatively small I've decided to use a + simple fixed size increment for buffer expansion. + */ + const ma_uint32 bufferExpansionCount = 2; + const ma_uint32 totalDeviceInfoCount = pContext->playbackDeviceInfoCount + pContext->captureDeviceInfoCount; + + if (pContext->deviceInfoCapacity >= totalDeviceInfoCount) { + ma_uint32 newCapacity = totalDeviceInfoCount + bufferExpansionCount; + ma_device_info* pNewInfos = (ma_device_info*)ma_realloc(pContext->pDeviceInfos, sizeof(*pContext->pDeviceInfos)*newCapacity); + if (pNewInfos == NULL) { + return MA_FALSE; /* Out of memory. */ + } + + pContext->pDeviceInfos = pNewInfos; + pContext->deviceInfoCapacity = newCapacity; + } + + if (deviceType == ma_device_type_playback) { + /* Playback. Insert just before the first capture device. */ + + /* The first thing to do is move all of the capture devices down a slot. */ + ma_uint32 iFirstCaptureDevice = pContext->playbackDeviceInfoCount; + size_t iCaptureDevice; + for (iCaptureDevice = totalDeviceInfoCount; iCaptureDevice > iFirstCaptureDevice; --iCaptureDevice) { + pContext->pDeviceInfos[iCaptureDevice] = pContext->pDeviceInfos[iCaptureDevice-1]; + } + + /* Now just insert where the first capture device was before moving it down a slot. */ + pContext->pDeviceInfos[iFirstCaptureDevice] = *pInfo; + pContext->playbackDeviceInfoCount += 1; + } else { + /* Capture. Insert at the end. */ + pContext->pDeviceInfos[totalDeviceInfoCount] = *pInfo; + pContext->captureDeviceInfoCount += 1; + } + + (void)pUserData; + return MA_TRUE; +} + +ma_result ma_context_get_devices(ma_context* pContext, ma_device_info** ppPlaybackDeviceInfos, ma_uint32* pPlaybackDeviceCount, ma_device_info** ppCaptureDeviceInfos, ma_uint32* pCaptureDeviceCount) +{ + ma_result result; + + /* Safety. */ + if (ppPlaybackDeviceInfos != NULL) *ppPlaybackDeviceInfos = NULL; + if (pPlaybackDeviceCount != NULL) *pPlaybackDeviceCount = 0; + if (ppCaptureDeviceInfos != NULL) *ppCaptureDeviceInfos = NULL; + if (pCaptureDeviceCount != NULL) *pCaptureDeviceCount = 0; + + if (pContext == NULL || pContext->onEnumDevices == NULL) { + return MA_INVALID_ARGS; + } + + /* 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); + { + /* Reset everything first. */ + pContext->playbackDeviceInfoCount = 0; + pContext->captureDeviceInfoCount = 0; + + /* Now enumerate over available devices. */ + result = pContext->onEnumDevices(pContext, ma_context_get_devices__enum_callback, NULL); + if (result == MA_SUCCESS) { + /* Playback devices. */ + if (ppPlaybackDeviceInfos != NULL) { + *ppPlaybackDeviceInfos = pContext->pDeviceInfos; + } + if (pPlaybackDeviceCount != NULL) { + *pPlaybackDeviceCount = pContext->playbackDeviceInfoCount; + } + + /* Capture devices. */ + if (ppCaptureDeviceInfos != NULL) { + *ppCaptureDeviceInfos = pContext->pDeviceInfos + pContext->playbackDeviceInfoCount; /* Capture devices come after playback devices. */ + } + if (pCaptureDeviceCount != NULL) { + *pCaptureDeviceCount = pContext->captureDeviceInfoCount; + } + } + } + ma_mutex_unlock(&pContext->deviceEnumLock); + + return result; +} + +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_device_info deviceInfo; + + /* 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; + } + + ma_zero_object(&deviceInfo); + + /* Help the backend out by copying over the device ID if we have one. */ + if (pDeviceID != NULL) { + 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; + } + + /* Getting here means onGetDeviceInfo has not been set. */ + return MA_ERROR; +} + + +ma_result ma_device_init(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +{ + ma_result result; + ma_device_config config; + + if (pContext == NULL) { + return ma_device_init_ex(NULL, 0, NULL, pConfig, pDevice); + } + if (pDevice == NULL) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "ma_device_init() called with invalid arguments (pDevice == NULL).", MA_INVALID_ARGS); + } + if (pConfig == NULL) { + return ma_post_error(pDevice, 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; + + /* Basic config validation. */ + if (config.deviceType != ma_device_type_playback && config.deviceType != ma_device_type_capture && config.deviceType != ma_device_type_duplex) { + return ma_post_error(pDevice, 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) { + return ma_post_error(pDevice, 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)) { + return ma_post_error(pDevice, 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) { + if (config.playback.channels > MA_MAX_CHANNELS) { + return ma_post_error(pDevice, 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)) { + return ma_post_error(pDevice, 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; + + if (((ma_uintptr)pDevice % sizeof(pDevice)) != 0) { + if (pContext->logCallback) { + pContext->logCallback(pContext, pDevice, MA_LOG_LEVEL_WARNING, "WARNING: ma_device_init() called for a device that is not properly aligned. Thread safety is not supported."); + } + } + + /* + 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; + } + + 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 buffer size. */ + if (config.bufferSizeInMilliseconds == 0 && config.bufferSizeInFrames == 0) { + config.bufferSizeInMilliseconds = (config.performanceProfile == ma_performance_profile_low_latency) ? MA_BASE_BUFFER_SIZE_IN_MILLISECONDS_LOW_LATENCY : MA_BASE_BUFFER_SIZE_IN_MILLISECONDS_CONSERVATIVE; + pDevice->usingDefaultBufferSize = 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; + } + + + pDevice->type = config.deviceType; + pDevice->sampleRate = config.sampleRate; + + 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); + + + if (ma_mutex_init(pContext, &pDevice->lock) != MA_SUCCESS) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "Failed to create mutex.", MA_FAILED_TO_CREATE_MUTEX); + } + + /* + When the device is started, the worker thread is the one that does the actual startup of the backend device. We + use a semaphore to wait for the background thread to finish the work. The same applies for stopping the device. + + Each of these semaphores is released internally by the worker thread when the work is completed. The start + semaphore is also used to wake up the worker thread. + */ + if (ma_event_init(pContext, &pDevice->wakeupEvent) != MA_SUCCESS) { + ma_mutex_uninit(&pDevice->lock); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "Failed to create worker thread wakeup event.", MA_FAILED_TO_CREATE_EVENT); + } + if (ma_event_init(pContext, &pDevice->startEvent) != MA_SUCCESS) { + ma_event_uninit(&pDevice->wakeupEvent); + ma_mutex_uninit(&pDevice->lock); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "Failed to create worker thread start event.", MA_FAILED_TO_CREATE_EVENT); + } + if (ma_event_init(pContext, &pDevice->stopEvent) != MA_SUCCESS) { + ma_event_uninit(&pDevice->startEvent); + ma_event_uninit(&pDevice->wakeupEvent); + ma_mutex_uninit(&pDevice->lock); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "Failed to create worker thread stop event.", MA_FAILED_TO_CREATE_EVENT); + } + + + result = pContext->onDeviceInit(pContext, &config, pDevice); + if (result != MA_SUCCESS) { + return MA_NO_BACKEND; /* The error message will have been posted with ma_post_error() by the source of the error so don't bother calling it here. */ + } + + 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) { + 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. */ + if (ma_thread_create(pContext, &pDevice->thread, ma_worker_thread, pDevice) != MA_SUCCESS) { + ma_device_uninit(pDevice); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "Failed to create worker thread.", MA_FAILED_TO_CREATE_THREAD); + } + + /* Wait for the worker thread to put the device into it's stopped state for real. */ + ma_event_wait(&pDevice->stopEvent); + } else { + ma_device__set_state(pDevice, MA_STATE_STOPPED); + } + + +#ifdef MA_DEBUG_OUTPUT + printf("[%s]\n", ma_get_backend_name(pDevice->pContext->backend)); + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + printf(" %s (%s)\n", pDevice->capture.name, "Capture"); + printf(" Format: %s -> %s\n", ma_get_format_name(pDevice->capture.format), ma_get_format_name(pDevice->capture.internalFormat)); + printf(" Channels: %d -> %d\n", pDevice->capture.channels, pDevice->capture.internalChannels); + printf(" Sample Rate: %d -> %d\n", pDevice->sampleRate, pDevice->capture.internalSampleRate); + printf(" Buffer Size: %d/%d (%d)\n", pDevice->capture.internalBufferSizeInFrames, pDevice->capture.internalPeriods, (pDevice->capture.internalBufferSizeInFrames / pDevice->capture.internalPeriods)); + printf(" Conversion:\n"); + printf(" Pre Format Conversion: %s\n", pDevice->capture.converter.isPreFormatConversionRequired ? "YES" : "NO"); + printf(" Post Format Conversion: %s\n", pDevice->capture.converter.isPostFormatConversionRequired ? "YES" : "NO"); + printf(" Channel Routing: %s\n", pDevice->capture.converter.isChannelRoutingRequired ? "YES" : "NO"); + printf(" SRC: %s\n", pDevice->capture.converter.isSRCRequired ? "YES" : "NO"); + printf(" Channel Routing at Start: %s\n", pDevice->capture.converter.isChannelRoutingAtStart ? "YES" : "NO"); + printf(" Passthrough: %s\n", pDevice->capture.converter.isPassthrough ? "YES" : "NO"); + } + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + printf(" %s (%s)\n", pDevice->playback.name, "Playback"); + printf(" Format: %s -> %s\n", ma_get_format_name(pDevice->playback.format), ma_get_format_name(pDevice->playback.internalFormat)); + printf(" Channels: %d -> %d\n", pDevice->playback.channels, pDevice->playback.internalChannels); + printf(" Sample Rate: %d -> %d\n", pDevice->sampleRate, pDevice->playback.internalSampleRate); + printf(" Buffer Size: %d/%d (%d)\n", pDevice->playback.internalBufferSizeInFrames, pDevice->playback.internalPeriods, (pDevice->playback.internalBufferSizeInFrames / pDevice->playback.internalPeriods)); + printf(" Conversion:\n"); + printf(" Pre Format Conversion: %s\n", pDevice->playback.converter.isPreFormatConversionRequired ? "YES" : "NO"); + printf(" Post Format Conversion: %s\n", pDevice->playback.converter.isPostFormatConversionRequired ? "YES" : "NO"); + printf(" Channel Routing: %s\n", pDevice->playback.converter.isChannelRoutingRequired ? "YES" : "NO"); + printf(" SRC: %s\n", pDevice->playback.converter.isSRCRequired ? "YES" : "NO"); + printf(" Channel Routing at Start: %s\n", pDevice->playback.converter.isChannelRoutingAtStart ? "YES" : "NO"); + printf(" Passthrough: %s\n", pDevice->playback.converter.isPassthrough ? "YES" : "NO"); + } +#endif + + + ma_assert(ma_device__get_state(pDevice) == MA_STATE_STOPPED); + return MA_SUCCESS; +} + +ma_result ma_device_init_ex(const ma_backend backends[], ma_uint32 backendCount, const ma_context_config* pContextConfig, const ma_device_config* pConfig, ma_device* pDevice) +{ + ma_result result; + ma_context* pContext; + ma_backend defaultBackends[ma_backend_null+1]; + ma_uint32 iBackend; + ma_backend* pBackendsToIterate; + ma_uint32 backendsToIterateCount; + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + pContext = (ma_context*)ma_malloc(sizeof(*pContext)); + if (pContext == NULL) { + return MA_OUT_OF_MEMORY; + } + + for (iBackend = 0; iBackend <= ma_backend_null; ++iBackend) { + defaultBackends[iBackend] = (ma_backend)iBackend; + } + + pBackendsToIterate = (ma_backend*)backends; + backendsToIterateCount = backendCount; + if (pBackendsToIterate == NULL) { + pBackendsToIterate = (ma_backend*)defaultBackends; + backendsToIterateCount = ma_countof(defaultBackends); + } + + result = MA_NO_BACKEND; + + for (iBackend = 0; iBackend < backendsToIterateCount; ++iBackend) { + result = ma_context_init(&pBackendsToIterate[iBackend], 1, pContextConfig, pContext); + if (result == MA_SUCCESS) { + result = ma_device_init(pContext, pConfig, pDevice); + if (result == MA_SUCCESS) { + break; /* Success. */ + } else { + ma_context_uninit(pContext); /* Failure. */ + } + } + } + + if (result != MA_SUCCESS) { + ma_free(pContext); + return result; + } + + pDevice->isOwnerOfContext = MA_TRUE; + return result; +} + +void ma_device_uninit(ma_device* pDevice) +{ + if (!ma_device__is_initialized(pDevice)) { + return; + } + + /* Make sure the device is stopped first. The backends will probably handle this naturally, but I like to do it explicitly for my own sanity. */ + if (ma_device_is_started(pDevice)) { + ma_device_stop(pDevice); + } + + /* Putting the device into an uninitialized state will make the worker thread return. */ + ma_device__set_state(pDevice, MA_STATE_UNINITIALIZED); + + /* Wake up the worker thread and wait for it to properly terminate. */ + if (!ma_context_is_backend_asynchronous(pDevice->pContext)) { + ma_event_signal(&pDevice->wakeupEvent); + ma_thread_wait(&pDevice->thread); + } + + pDevice->pContext->onDeviceUninit(pDevice); + + ma_event_uninit(&pDevice->stopEvent); + ma_event_uninit(&pDevice->startEvent); + ma_event_uninit(&pDevice->wakeupEvent); + ma_mutex_uninit(&pDevice->lock); + + if (pDevice->isOwnerOfContext) { + ma_context_uninit(pDevice->pContext); + ma_free(pDevice->pContext); + } + + ma_zero_object(pDevice); +} + +void ma_device_set_stop_callback(ma_device* pDevice, ma_stop_proc proc) +{ + if (pDevice == NULL) { + return; + } + + ma_atomic_exchange_ptr(&pDevice->onStop, proc); +} + +ma_result ma_device_start(ma_device* pDevice) +{ + ma_result result; + + if (pDevice == NULL) { + 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) { + 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) { + 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); + { + /* 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_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 (result == MA_SUCCESS) { + ma_device__set_state(pDevice, MA_STATE_STARTED); + } + } else { + /* + Synchronous backends are started by signaling an event that's being waited on in the worker thread. We first wake up the + thread and then wait for the start event. + */ + ma_event_signal(&pDevice->wakeupEvent); + + /* + Wait for the worker thread to finish starting the device. Note that the worker thread will be the one who puts the device + into the started state. Don't call ma_device__set_state() here. + */ + ma_event_wait(&pDevice->startEvent); + result = pDevice->workResult; + } + } + ma_mutex_unlock(&pDevice->lock); + + return result; +} + +ma_result ma_device_stop(ma_device* pDevice) +{ + ma_result result; + + if (pDevice == NULL) { + 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) { + 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) { + 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); + { + /* 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_device__set_state(pDevice, MA_STATE_STOPPING); + + /* There's no need to wake up the thread like we do when starting. */ + + /* Asynchronous backends need to be handled differently. */ + if (ma_context_is_backend_asynchronous(pDevice->pContext)) { + if (pDevice->pContext->onDeviceStop) { + result = pDevice->pContext->onDeviceStop(pDevice); + } else { + result = MA_SUCCESS; + } + + ma_device__set_state(pDevice, MA_STATE_STOPPED); + } else { + /* Synchronous backends. */ + + /* + We need to wait for the worker thread to become available for work before returning. Note that the worker thread will be + the one who puts the device into the stopped state. Don't call ma_device__set_state() here. + */ + ma_event_wait(&pDevice->stopEvent); + result = MA_SUCCESS; + } + } + ma_mutex_unlock(&pDevice->lock); + + return result; +} + +ma_bool32 ma_device_is_started(ma_device* pDevice) +{ + if (pDevice == NULL) { + return MA_FALSE; + } + + return ma_device__get_state(pDevice) == MA_STATE_STARTED; +} + + +ma_context_config ma_context_config_init() +{ + ma_context_config config; + ma_zero_object(&config); + + return config; +} + +ma_device_config ma_device_config_init(ma_device_type deviceType) +{ + ma_device_config config; + ma_zero_object(&config); + config.deviceType = deviceType; + + return config; +} +#endif /* MA_NO_DEVICE_IO */ + + +void ma_get_standard_channel_map_microsoft(ma_uint32 channels, ma_channel channelMap[MA_MAX_CHANNELS]) +{ + /* Based off the speaker configurations mentioned here: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ksmedia/ns-ksmedia-ksaudio_channel_config */ + switch (channels) + { + case 1: + { + channelMap[0] = MA_CHANNEL_MONO; + } break; + + case 2: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + } break; + + case 3: /* Not defined, but best guess. */ + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_FRONT_CENTER; + } break; + + case 4: + { +#ifndef MA_USE_QUAD_MICROSOFT_CHANNEL_MAP + /* Surround. Using the Surround profile has the advantage of the 3rd channel (MA_CHANNEL_FRONT_CENTER) mapping nicely with higher channel counts. */ + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_FRONT_CENTER; + channelMap[3] = MA_CHANNEL_BACK_CENTER; +#else + /* Quad. */ + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_BACK_LEFT; + channelMap[3] = MA_CHANNEL_BACK_RIGHT; +#endif + } break; + + case 5: /* Not defined, but best guess. */ + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_FRONT_CENTER; + channelMap[3] = MA_CHANNEL_BACK_LEFT; + channelMap[4] = MA_CHANNEL_BACK_RIGHT; + } break; + + case 6: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_FRONT_CENTER; + channelMap[3] = MA_CHANNEL_LFE; + channelMap[4] = MA_CHANNEL_SIDE_LEFT; + channelMap[5] = MA_CHANNEL_SIDE_RIGHT; + } break; + + case 7: /* Not defined, but best guess. */ + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_FRONT_CENTER; + channelMap[3] = MA_CHANNEL_LFE; + channelMap[4] = MA_CHANNEL_BACK_CENTER; + channelMap[5] = MA_CHANNEL_SIDE_LEFT; + channelMap[6] = MA_CHANNEL_SIDE_RIGHT; + } break; + + case 8: + default: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_FRONT_CENTER; + channelMap[3] = MA_CHANNEL_LFE; + channelMap[4] = MA_CHANNEL_BACK_LEFT; + channelMap[5] = MA_CHANNEL_BACK_RIGHT; + channelMap[6] = MA_CHANNEL_SIDE_LEFT; + channelMap[7] = MA_CHANNEL_SIDE_RIGHT; + } break; + } + + /* Remainder. */ + if (channels > 8) { + ma_uint32 iChannel; + for (iChannel = 8; iChannel < MA_MAX_CHANNELS; ++iChannel) { + channelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-8)); + } + } +} + +void ma_get_standard_channel_map_alsa(ma_uint32 channels, ma_channel channelMap[MA_MAX_CHANNELS]) +{ + switch (channels) + { + case 1: + { + channelMap[0] = MA_CHANNEL_MONO; + } break; + + case 2: + { + channelMap[0] = MA_CHANNEL_LEFT; + channelMap[1] = MA_CHANNEL_RIGHT; + } break; + + case 3: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_FRONT_CENTER; + } break; + + case 4: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_BACK_LEFT; + channelMap[3] = MA_CHANNEL_BACK_RIGHT; + } break; + + case 5: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_BACK_LEFT; + channelMap[3] = MA_CHANNEL_BACK_RIGHT; + channelMap[4] = MA_CHANNEL_FRONT_CENTER; + } break; + + case 6: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_BACK_LEFT; + channelMap[3] = MA_CHANNEL_BACK_RIGHT; + channelMap[4] = MA_CHANNEL_FRONT_CENTER; + channelMap[5] = MA_CHANNEL_LFE; + } break; + + case 7: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_BACK_LEFT; + channelMap[3] = MA_CHANNEL_BACK_RIGHT; + channelMap[4] = MA_CHANNEL_FRONT_CENTER; + channelMap[5] = MA_CHANNEL_LFE; + channelMap[6] = MA_CHANNEL_BACK_CENTER; + } break; + + case 8: + default: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_BACK_LEFT; + channelMap[3] = MA_CHANNEL_BACK_RIGHT; + channelMap[4] = MA_CHANNEL_FRONT_CENTER; + channelMap[5] = MA_CHANNEL_LFE; + channelMap[6] = MA_CHANNEL_SIDE_LEFT; + channelMap[7] = MA_CHANNEL_SIDE_RIGHT; + } break; + } + + /* Remainder. */ + if (channels > 8) { + ma_uint32 iChannel; + for (iChannel = 8; iChannel < MA_MAX_CHANNELS; ++iChannel) { + channelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-8)); + } + } +} + +void ma_get_standard_channel_map_rfc3551(ma_uint32 channels, ma_channel channelMap[MA_MAX_CHANNELS]) +{ + switch (channels) + { + case 1: + { + channelMap[0] = MA_CHANNEL_MONO; + } break; + + case 2: + { + channelMap[0] = MA_CHANNEL_LEFT; + channelMap[1] = MA_CHANNEL_RIGHT; + } break; + + case 3: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_FRONT_CENTER; + } break; + + case 4: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_CENTER; + channelMap[2] = MA_CHANNEL_FRONT_RIGHT; + channelMap[3] = MA_CHANNEL_BACK_CENTER; + } break; + + case 5: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_FRONT_CENTER; + channelMap[3] = MA_CHANNEL_BACK_LEFT; + channelMap[4] = MA_CHANNEL_BACK_RIGHT; + } break; + + case 6: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_SIDE_LEFT; + channelMap[2] = MA_CHANNEL_FRONT_CENTER; + channelMap[3] = MA_CHANNEL_FRONT_RIGHT; + channelMap[4] = MA_CHANNEL_SIDE_RIGHT; + channelMap[5] = MA_CHANNEL_BACK_CENTER; + } break; + } + + /* Remainder. */ + if (channels > 8) { + ma_uint32 iChannel; + for (iChannel = 6; iChannel < MA_MAX_CHANNELS; ++iChannel) { + channelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-6)); + } + } +} + +void ma_get_standard_channel_map_flac(ma_uint32 channels, ma_channel channelMap[MA_MAX_CHANNELS]) +{ + switch (channels) + { + case 1: + { + channelMap[0] = MA_CHANNEL_MONO; + } break; + + case 2: + { + channelMap[0] = MA_CHANNEL_LEFT; + channelMap[1] = MA_CHANNEL_RIGHT; + } break; + + case 3: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_FRONT_CENTER; + } break; + + case 4: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_BACK_LEFT; + channelMap[3] = MA_CHANNEL_BACK_RIGHT; + } break; + + case 5: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_FRONT_CENTER; + channelMap[3] = MA_CHANNEL_BACK_LEFT; + channelMap[4] = MA_CHANNEL_BACK_RIGHT; + } break; + + case 6: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_FRONT_CENTER; + channelMap[3] = MA_CHANNEL_LFE; + channelMap[4] = MA_CHANNEL_BACK_LEFT; + channelMap[5] = MA_CHANNEL_BACK_RIGHT; + } break; + + case 7: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_FRONT_CENTER; + channelMap[3] = MA_CHANNEL_LFE; + channelMap[4] = MA_CHANNEL_BACK_CENTER; + channelMap[5] = MA_CHANNEL_SIDE_LEFT; + channelMap[6] = MA_CHANNEL_SIDE_RIGHT; + } break; + + case 8: + default: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_FRONT_CENTER; + channelMap[3] = MA_CHANNEL_LFE; + channelMap[4] = MA_CHANNEL_BACK_LEFT; + channelMap[5] = MA_CHANNEL_BACK_RIGHT; + channelMap[6] = MA_CHANNEL_SIDE_LEFT; + channelMap[7] = MA_CHANNEL_SIDE_RIGHT; + } break; + } + + /* Remainder. */ + if (channels > 8) { + ma_uint32 iChannel; + for (iChannel = 8; iChannel < MA_MAX_CHANNELS; ++iChannel) { + channelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-8)); + } + } +} + +void ma_get_standard_channel_map_vorbis(ma_uint32 channels, ma_channel channelMap[MA_MAX_CHANNELS]) +{ + /* In Vorbis' type 0 channel mapping, the first two channels are not always the standard left/right - it will have the center speaker where the right usually goes. Why?! */ + switch (channels) + { + case 1: + { + channelMap[0] = MA_CHANNEL_MONO; + } break; + + case 2: + { + channelMap[0] = MA_CHANNEL_LEFT; + channelMap[1] = MA_CHANNEL_RIGHT; + } break; + + case 3: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_CENTER; + channelMap[2] = MA_CHANNEL_FRONT_RIGHT; + } break; + + case 4: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_BACK_LEFT; + channelMap[3] = MA_CHANNEL_BACK_RIGHT; + } break; + + case 5: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_CENTER; + channelMap[2] = MA_CHANNEL_FRONT_RIGHT; + channelMap[3] = MA_CHANNEL_BACK_LEFT; + channelMap[4] = MA_CHANNEL_BACK_RIGHT; + } break; + + case 6: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_CENTER; + channelMap[2] = MA_CHANNEL_FRONT_RIGHT; + channelMap[3] = MA_CHANNEL_BACK_LEFT; + channelMap[4] = MA_CHANNEL_BACK_RIGHT; + channelMap[5] = MA_CHANNEL_LFE; + } break; + + case 7: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_CENTER; + channelMap[2] = MA_CHANNEL_FRONT_RIGHT; + channelMap[3] = MA_CHANNEL_SIDE_LEFT; + channelMap[4] = MA_CHANNEL_SIDE_RIGHT; + channelMap[5] = MA_CHANNEL_BACK_CENTER; + channelMap[6] = MA_CHANNEL_LFE; + } break; + + case 8: + default: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_CENTER; + channelMap[2] = MA_CHANNEL_FRONT_RIGHT; + channelMap[3] = MA_CHANNEL_SIDE_LEFT; + channelMap[4] = MA_CHANNEL_SIDE_RIGHT; + channelMap[5] = MA_CHANNEL_BACK_LEFT; + channelMap[6] = MA_CHANNEL_BACK_RIGHT; + channelMap[7] = MA_CHANNEL_LFE; + } break; + } + + /* Remainder. */ + if (channels > 8) { + ma_uint32 iChannel; + for (iChannel = 8; iChannel < MA_MAX_CHANNELS; ++iChannel) { + channelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-8)); + } + } +} + +void ma_get_standard_channel_map_sound4(ma_uint32 channels, ma_channel channelMap[MA_MAX_CHANNELS]) +{ + switch (channels) + { + case 1: + { + channelMap[0] = MA_CHANNEL_MONO; + } break; + + case 2: + { + channelMap[0] = MA_CHANNEL_LEFT; + channelMap[1] = MA_CHANNEL_RIGHT; + } break; + + case 3: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_BACK_CENTER; + } break; + + case 4: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_BACK_LEFT; + channelMap[3] = MA_CHANNEL_BACK_RIGHT; + } break; + + case 5: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_BACK_LEFT; + channelMap[3] = MA_CHANNEL_BACK_RIGHT; + channelMap[4] = MA_CHANNEL_FRONT_CENTER; + } break; + + case 6: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_BACK_LEFT; + channelMap[3] = MA_CHANNEL_BACK_RIGHT; + channelMap[4] = MA_CHANNEL_FRONT_CENTER; + channelMap[5] = MA_CHANNEL_LFE; + } break; + + case 7: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_BACK_LEFT; + channelMap[3] = MA_CHANNEL_BACK_RIGHT; + channelMap[4] = MA_CHANNEL_FRONT_CENTER; + channelMap[5] = MA_CHANNEL_BACK_CENTER; + channelMap[6] = MA_CHANNEL_LFE; + } break; + + case 8: + default: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_BACK_LEFT; + channelMap[3] = MA_CHANNEL_BACK_RIGHT; + channelMap[4] = MA_CHANNEL_FRONT_CENTER; + channelMap[5] = MA_CHANNEL_LFE; + channelMap[6] = MA_CHANNEL_SIDE_LEFT; + channelMap[7] = MA_CHANNEL_SIDE_RIGHT; + } break; + } + + /* Remainder. */ + if (channels > 8) { + ma_uint32 iChannel; + for (iChannel = 8; iChannel < MA_MAX_CHANNELS; ++iChannel) { + channelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-8)); + } + } +} + +void ma_get_standard_channel_map_sndio(ma_uint32 channels, ma_channel channelMap[MA_MAX_CHANNELS]) +{ + switch (channels) + { + case 1: + { + channelMap[0] = MA_CHANNEL_MONO; + } break; + + case 2: + { + channelMap[0] = MA_CHANNEL_LEFT; + channelMap[1] = MA_CHANNEL_RIGHT; + } break; + + case 3: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_FRONT_CENTER; + } break; + + case 4: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_BACK_LEFT; + channelMap[3] = MA_CHANNEL_BACK_RIGHT; + } break; + + case 5: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_BACK_LEFT; + channelMap[3] = MA_CHANNEL_BACK_RIGHT; + channelMap[4] = MA_CHANNEL_FRONT_CENTER; + } break; + + case 6: + default: + { + channelMap[0] = MA_CHANNEL_FRONT_LEFT; + channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + channelMap[2] = MA_CHANNEL_BACK_LEFT; + channelMap[3] = MA_CHANNEL_BACK_RIGHT; + channelMap[4] = MA_CHANNEL_FRONT_CENTER; + channelMap[5] = MA_CHANNEL_LFE; + } break; + } + + /* Remainder. */ + if (channels > 6) { + ma_uint32 iChannel; + for (iChannel = 6; iChannel < MA_MAX_CHANNELS; ++iChannel) { + channelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-6)); + } + } +} + +void ma_get_standard_channel_map(ma_standard_channel_map standardChannelMap, ma_uint32 channels, ma_channel channelMap[MA_MAX_CHANNELS]) +{ + switch (standardChannelMap) + { + case ma_standard_channel_map_alsa: + { + ma_get_standard_channel_map_alsa(channels, channelMap); + } break; + + case ma_standard_channel_map_rfc3551: + { + ma_get_standard_channel_map_rfc3551(channels, channelMap); + } break; + + case ma_standard_channel_map_flac: + { + ma_get_standard_channel_map_flac(channels, channelMap); + } break; + + case ma_standard_channel_map_vorbis: + { + ma_get_standard_channel_map_vorbis(channels, channelMap); + } break; + + case ma_standard_channel_map_sound4: + { + ma_get_standard_channel_map_sound4(channels, channelMap); + } break; + + case ma_standard_channel_map_sndio: + { + ma_get_standard_channel_map_sndio(channels, channelMap); + } break; + + case ma_standard_channel_map_microsoft: + default: + { + ma_get_standard_channel_map_microsoft(channels, channelMap); + } break; + } +} + +void ma_channel_map_copy(ma_channel* pOut, const ma_channel* pIn, ma_uint32 channels) +{ + if (pOut != NULL && pIn != NULL && channels > 0) { + ma_copy_memory(pOut, pIn, sizeof(*pOut) * channels); + } +} + +ma_bool32 ma_channel_map_valid(ma_uint32 channels, const ma_channel channelMap[MA_MAX_CHANNELS]) +{ + if (channelMap == NULL) { + return MA_FALSE; + } + + /* A channel count of 0 is invalid. */ + if (channels == 0) { + return MA_FALSE; + } + + /* It does not make sense to have a mono channel when there is more than 1 channel. */ + if (channels > 1) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; ++iChannel) { + if (channelMap[iChannel] == MA_CHANNEL_MONO) { + return MA_FALSE; + } + } + } + + return MA_TRUE; +} + +ma_bool32 ma_channel_map_equal(ma_uint32 channels, const ma_channel channelMapA[MA_MAX_CHANNELS], const ma_channel channelMapB[MA_MAX_CHANNELS]) +{ + ma_uint32 iChannel; + + if (channelMapA == channelMapB) { + return MA_FALSE; + } + + if (channels == 0 || channels > MA_MAX_CHANNELS) { + return MA_FALSE; + } + + for (iChannel = 0; iChannel < channels; ++iChannel) { + if (channelMapA[iChannel] != channelMapB[iChannel]) { + return MA_FALSE; + } + } + + return MA_TRUE; +} + +ma_bool32 ma_channel_map_blank(ma_uint32 channels, const ma_channel channelMap[MA_MAX_CHANNELS]) +{ + ma_uint32 iChannel; + + for (iChannel = 0; iChannel < channels; ++iChannel) { + if (channelMap[iChannel] != MA_CHANNEL_NONE) { + return MA_FALSE; + } + } + + return MA_TRUE; +} + +ma_bool32 ma_channel_map_contains_channel_position(ma_uint32 channels, const ma_channel channelMap[MA_MAX_CHANNELS], ma_channel channelPosition) +{ + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; ++iChannel) { + if (channelMap[iChannel] == channelPosition) { + return MA_TRUE; + } + } + + return MA_FALSE; +} + + + + +/************************************************************************************************************************************************************** + +Format Conversion. + +**************************************************************************************************************************************************************/ +void ma_copy_memory_64(void* dst, const void* src, ma_uint64 sizeInBytes) +{ +#if 0xFFFFFFFFFFFFFFFF <= MA_SIZE_MAX + ma_copy_memory(dst, src, (size_t)sizeInBytes); +#else + while (sizeInBytes > 0) { + ma_uint64 bytesToCopyNow = sizeInBytes; + if (bytesToCopyNow > MA_SIZE_MAX) { + bytesToCopyNow = MA_SIZE_MAX; + } + + ma_copy_memory(dst, src, (size_t)bytesToCopyNow); /* Safe cast to size_t. */ + + sizeInBytes -= bytesToCopyNow; + dst = ( void*)(( ma_uint8*)dst + bytesToCopyNow); + src = (const void*)((const ma_uint8*)src + bytesToCopyNow); + } +#endif +} + +void ma_zero_memory_64(void* dst, ma_uint64 sizeInBytes) +{ +#if 0xFFFFFFFFFFFFFFFF <= MA_SIZE_MAX + ma_zero_memory(dst, (size_t)sizeInBytes); +#else + while (sizeInBytes > 0) { + ma_uint64 bytesToZeroNow = sizeInBytes; + if (bytesToZeroNow > MA_SIZE_MAX) { + bytesToZeroNow = MA_SIZE_MAX; + } + + ma_zero_memory(dst, (size_t)bytesToZeroNow); /* Safe cast to size_t. */ + + sizeInBytes -= bytesToZeroNow; + dst = (void*)((ma_uint8*)dst + bytesToZeroNow); + } +#endif +} + + +/* u8 */ +void ma_pcm_u8_to_u8(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + (void)ditherMode; + ma_copy_memory_64(dst, src, count * sizeof(ma_uint8)); +} + + +void ma_pcm_u8_to_s16__reference(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_int16* dst_s16 = (ma_int16*)dst; + const ma_uint8* src_u8 = (const ma_uint8*)src; + + ma_uint64 i; + for (i = 0; i < count; i += 1) { + ma_int16 x = src_u8[i]; + x = x - 128; + x = x << 8; + dst_s16[i] = x; + } + + (void)ditherMode; +} + +void ma_pcm_u8_to_s16__optimized(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_u8_to_s16__reference(dst, src, count, ditherMode); +} + +#if defined(MA_SUPPORT_SSE2) +void ma_pcm_u8_to_s16__sse2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_u8_to_s16__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX2) +void ma_pcm_u8_to_s16__avx2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_u8_to_s16__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX512) +void ma_pcm_u8_to_s16__avx512(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_u8_to_s16__avx2(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_NEON) +void ma_pcm_u8_to_s16__neon(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_u8_to_s16__optimized(dst, src, count, ditherMode); +} +#endif + +void ma_pcm_u8_to_s16(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_u8_to_s16__reference(dst, src, count, ditherMode); +#else + ma_pcm_u8_to_s16__optimized(dst, src, count, ditherMode); +#endif +} + + +void ma_pcm_u8_to_s24__reference(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_uint8* dst_s24 = (ma_uint8*)dst; + const ma_uint8* src_u8 = (const ma_uint8*)src; + + ma_uint64 i; + for (i = 0; i < count; i += 1) { + ma_int16 x = src_u8[i]; + x = x - 128; + + dst_s24[i*3+0] = 0; + dst_s24[i*3+1] = 0; + dst_s24[i*3+2] = (ma_uint8)((ma_int8)x); + } + + (void)ditherMode; +} + +void ma_pcm_u8_to_s24__optimized(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_u8_to_s24__reference(dst, src, count, ditherMode); +} + +#if defined(MA_SUPPORT_SSE2) +void ma_pcm_u8_to_s24__sse2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_u8_to_s24__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX2) +void ma_pcm_u8_to_s24__avx2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_u8_to_s24__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX512) +void ma_pcm_u8_to_s24__avx512(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_u8_to_s24__avx2(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_NEON) +void ma_pcm_u8_to_s24__neon(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_u8_to_s24__optimized(dst, src, count, ditherMode); +} +#endif + +void ma_pcm_u8_to_s24(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_u8_to_s24__reference(dst, src, count, ditherMode); +#else + ma_pcm_u8_to_s24__optimized(dst, src, count, ditherMode); +#endif +} + + +void ma_pcm_u8_to_s32__reference(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_int32* dst_s32 = (ma_int32*)dst; + const ma_uint8* src_u8 = (const ma_uint8*)src; + + ma_uint64 i; + for (i = 0; i < count; i += 1) { + ma_int32 x = src_u8[i]; + x = x - 128; + x = x << 24; + dst_s32[i] = x; + } + + (void)ditherMode; +} + +void ma_pcm_u8_to_s32__optimized(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_u8_to_s32__reference(dst, src, count, ditherMode); +} + +#if defined(MA_SUPPORT_SSE2) +void ma_pcm_u8_to_s32__sse2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_u8_to_s32__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX2) +void ma_pcm_u8_to_s32__avx2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_u8_to_s32__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX512) +void ma_pcm_u8_to_s32__avx512(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_u8_to_s32__avx2(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_NEON) +void ma_pcm_u8_to_s32__neon(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_u8_to_s32__optimized(dst, src, count, ditherMode); +} +#endif + +void ma_pcm_u8_to_s32(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_u8_to_s32__reference(dst, src, count, ditherMode); +#else + ma_pcm_u8_to_s32__optimized(dst, src, count, ditherMode); +#endif +} + + +void ma_pcm_u8_to_f32__reference(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + float* dst_f32 = (float*)dst; + const ma_uint8* src_u8 = (const ma_uint8*)src; + + ma_uint64 i; + for (i = 0; i < count; i += 1) { + float x = (float)src_u8[i]; + x = x * 0.00784313725490196078f; /* 0..255 to 0..2 */ + x = x - 1; /* 0..2 to -1..1 */ + + dst_f32[i] = x; + } + + (void)ditherMode; +} + +void ma_pcm_u8_to_f32__optimized(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_u8_to_f32__reference(dst, src, count, ditherMode); +} + +#if defined(MA_SUPPORT_SSE2) +void ma_pcm_u8_to_f32__sse2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_u8_to_f32__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX2) +void ma_pcm_u8_to_f32__avx2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_u8_to_f32__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX512) +void ma_pcm_u8_to_f32__avx512(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_u8_to_f32__avx2(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_NEON) +void ma_pcm_u8_to_f32__neon(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_u8_to_f32__optimized(dst, src, count, ditherMode); +} +#endif + +void ma_pcm_u8_to_f32(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_u8_to_f32__reference(dst, src, count, ditherMode); +#else + ma_pcm_u8_to_f32__optimized(dst, src, count, ditherMode); +#endif +} + + + +void ma_pcm_interleave_u8__reference(void* dst, const void** src, ma_uint64 frameCount, ma_uint32 channels) +{ + ma_uint8* dst_u8 = (ma_uint8*)dst; + const ma_uint8** src_u8 = (const ma_uint8**)src; + + ma_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dst_u8[iFrame*channels + iChannel] = src_u8[iChannel][iFrame]; + } + } +} + +void ma_pcm_interleave_u8__optimized(void* dst, const void** src, ma_uint64 frameCount, ma_uint32 channels) +{ + ma_uint8* dst_u8 = (ma_uint8*)dst; + const ma_uint8** src_u8 = (const ma_uint8**)src; + + if (channels == 1) { + ma_copy_memory_64(dst, src[0], frameCount * sizeof(ma_uint8)); + } else if (channels == 2) { + ma_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + dst_u8[iFrame*2 + 0] = src_u8[0][iFrame]; + dst_u8[iFrame*2 + 1] = src_u8[1][iFrame]; + } + } else { + ma_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dst_u8[iFrame*channels + iChannel] = src_u8[iChannel][iFrame]; + } + } + } +} + +void ma_pcm_interleave_u8(void* dst, const void** src, ma_uint64 frameCount, ma_uint32 channels) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_interleave_u8__reference(dst, src, frameCount, channels); +#else + ma_pcm_interleave_u8__optimized(dst, src, frameCount, channels); +#endif +} + + +void ma_pcm_deinterleave_u8__reference(void** dst, const void* src, ma_uint64 frameCount, ma_uint32 channels) +{ + ma_uint8** dst_u8 = (ma_uint8**)dst; + const ma_uint8* src_u8 = (const ma_uint8*)src; + + ma_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dst_u8[iChannel][iFrame] = src_u8[iFrame*channels + iChannel]; + } + } +} + +void ma_pcm_deinterleave_u8__optimized(void** dst, const void* src, ma_uint64 frameCount, ma_uint32 channels) +{ + ma_pcm_deinterleave_u8__reference(dst, src, frameCount, channels); +} + +void ma_pcm_deinterleave_u8(void** dst, const void* src, ma_uint64 frameCount, ma_uint32 channels) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_deinterleave_u8__reference(dst, src, frameCount, channels); +#else + ma_pcm_deinterleave_u8__optimized(dst, src, frameCount, channels); +#endif +} + + +/* s16 */ +void ma_pcm_s16_to_u8__reference(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_uint8* dst_u8 = (ma_uint8*)dst; + const ma_int16* src_s16 = (const ma_int16*)src; + + if (ditherMode == ma_dither_mode_none) { + ma_uint64 i; + for (i = 0; i < count; i += 1) { + ma_int16 x = src_s16[i]; + x = x >> 8; + x = x + 128; + dst_u8[i] = (ma_uint8)x; + } + } else { + ma_uint64 i; + for (i = 0; i < count; i += 1) { + ma_int16 x = src_s16[i]; + + /* Dither. Don't overflow. */ + ma_int32 dither = ma_dither_s32(ditherMode, -0x80, 0x7F); + if ((x + dither) <= 0x7FFF) { + x = (ma_int16)(x + dither); + } else { + x = 0x7FFF; + } + + x = x >> 8; + x = x + 128; + dst_u8[i] = (ma_uint8)x; + } + } +} + +void ma_pcm_s16_to_u8__optimized(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s16_to_u8__reference(dst, src, count, ditherMode); +} + +#if defined(MA_SUPPORT_SSE2) +void ma_pcm_s16_to_u8__sse2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s16_to_u8__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX2) +void ma_pcm_s16_to_u8__avx2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s16_to_u8__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX512) +void ma_pcm_s16_to_u8__avx512(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s16_to_u8__avx2(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_NEON) +void ma_pcm_s16_to_u8__neon(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s16_to_u8__optimized(dst, src, count, ditherMode); +} +#endif + +void ma_pcm_s16_to_u8(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_s16_to_u8__reference(dst, src, count, ditherMode); +#else + ma_pcm_s16_to_u8__optimized(dst, src, count, ditherMode); +#endif +} + + +void ma_pcm_s16_to_s16(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + (void)ditherMode; + ma_copy_memory_64(dst, src, count * sizeof(ma_int16)); +} + + +void ma_pcm_s16_to_s24__reference(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_uint8* dst_s24 = (ma_uint8*)dst; + const ma_int16* src_s16 = (const ma_int16*)src; + + ma_uint64 i; + for (i = 0; i < count; i += 1) { + dst_s24[i*3+0] = 0; + dst_s24[i*3+1] = (ma_uint8)(src_s16[i] & 0xFF); + dst_s24[i*3+2] = (ma_uint8)(src_s16[i] >> 8); + } + + (void)ditherMode; +} + +void ma_pcm_s16_to_s24__optimized(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s16_to_s24__reference(dst, src, count, ditherMode); +} + +#if defined(MA_SUPPORT_SSE2) +void ma_pcm_s16_to_s24__sse2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s16_to_s24__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX2) +void ma_pcm_s16_to_s24__avx2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s16_to_s24__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX512) +void ma_pcm_s16_to_s24__avx512(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s16_to_s24__avx2(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_NEON) +void ma_pcm_s16_to_s24__neon(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s16_to_s24__optimized(dst, src, count, ditherMode); +} +#endif + +void ma_pcm_s16_to_s24(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_s16_to_s24__reference(dst, src, count, ditherMode); +#else + ma_pcm_s16_to_s24__optimized(dst, src, count, ditherMode); +#endif +} + + +void ma_pcm_s16_to_s32__reference(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_int32* dst_s32 = (ma_int32*)dst; + const ma_int16* src_s16 = (const ma_int16*)src; + + ma_uint64 i; + for (i = 0; i < count; i += 1) { + dst_s32[i] = src_s16[i] << 16; + } + + (void)ditherMode; +} + +void ma_pcm_s16_to_s32__optimized(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s16_to_s32__reference(dst, src, count, ditherMode); +} + +#if defined(MA_SUPPORT_SSE2) +void ma_pcm_s16_to_s32__sse2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s16_to_s32__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX2) +void ma_pcm_s16_to_s32__avx2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s16_to_s32__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX512) +void ma_pcm_s16_to_s32__avx512(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s16_to_s32__avx2(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_NEON) +void ma_pcm_s16_to_s32__neon(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s16_to_s32__optimized(dst, src, count, ditherMode); +} +#endif + +void ma_pcm_s16_to_s32(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_s16_to_s32__reference(dst, src, count, ditherMode); +#else + ma_pcm_s16_to_s32__optimized(dst, src, count, ditherMode); +#endif +} + + +void ma_pcm_s16_to_f32__reference(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + float* dst_f32 = (float*)dst; + const ma_int16* src_s16 = (const ma_int16*)src; + + ma_uint64 i; + for (i = 0; i < count; i += 1) { + float x = (float)src_s16[i]; + +#if 0 + /* The accurate way. */ + x = x + 32768.0f; /* -32768..32767 to 0..65535 */ + x = x * 0.00003051804379339284f; /* 0..65536 to 0..2 */ + x = x - 1; /* 0..2 to -1..1 */ +#else + /* The fast way. */ + x = x * 0.000030517578125f; /* -32768..32767 to -1..0.999969482421875 */ +#endif + + dst_f32[i] = x; + } + + (void)ditherMode; +} + +void ma_pcm_s16_to_f32__optimized(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s16_to_f32__reference(dst, src, count, ditherMode); +} + +#if defined(MA_SUPPORT_SSE2) +void ma_pcm_s16_to_f32__sse2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s16_to_f32__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX2) +void ma_pcm_s16_to_f32__avx2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s16_to_f32__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX512) +void ma_pcm_s16_to_f32__avx512(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s16_to_f32__avx2(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_NEON) +void ma_pcm_s16_to_f32__neon(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s16_to_f32__optimized(dst, src, count, ditherMode); +} +#endif + +void ma_pcm_s16_to_f32(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_s16_to_f32__reference(dst, src, count, ditherMode); +#else + ma_pcm_s16_to_f32__optimized(dst, src, count, ditherMode); +#endif +} + + +void ma_pcm_interleave_s16__reference(void* dst, const void** src, ma_uint64 frameCount, ma_uint32 channels) +{ + ma_int16* dst_s16 = (ma_int16*)dst; + const ma_int16** src_s16 = (const ma_int16**)src; + + ma_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dst_s16[iFrame*channels + iChannel] = src_s16[iChannel][iFrame]; + } + } +} + +void ma_pcm_interleave_s16__optimized(void* dst, const void** src, ma_uint64 frameCount, ma_uint32 channels) +{ + ma_pcm_interleave_s16__reference(dst, src, frameCount, channels); +} + +void ma_pcm_interleave_s16(void* dst, const void** src, ma_uint64 frameCount, ma_uint32 channels) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_interleave_s16__reference(dst, src, frameCount, channels); +#else + ma_pcm_interleave_s16__optimized(dst, src, frameCount, channels); +#endif +} + + +void ma_pcm_deinterleave_s16__reference(void** dst, const void* src, ma_uint64 frameCount, ma_uint32 channels) +{ + ma_int16** dst_s16 = (ma_int16**)dst; + const ma_int16* src_s16 = (const ma_int16*)src; + + ma_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dst_s16[iChannel][iFrame] = src_s16[iFrame*channels + iChannel]; + } + } +} + +void ma_pcm_deinterleave_s16__optimized(void** dst, const void* src, ma_uint64 frameCount, ma_uint32 channels) +{ + ma_pcm_deinterleave_s16__reference(dst, src, frameCount, channels); +} + +void ma_pcm_deinterleave_s16(void** dst, const void* src, ma_uint64 frameCount, ma_uint32 channels) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_deinterleave_s16__reference(dst, src, frameCount, channels); +#else + ma_pcm_deinterleave_s16__optimized(dst, src, frameCount, channels); +#endif +} + + +/* s24 */ +void ma_pcm_s24_to_u8__reference(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_uint8* dst_u8 = (ma_uint8*)dst; + const ma_uint8* src_s24 = (const ma_uint8*)src; + + if (ditherMode == ma_dither_mode_none) { + ma_uint64 i; + for (i = 0; i < count; i += 1) { + ma_int8 x = (ma_int8)src_s24[i*3 + 2] + 128; + dst_u8[i] = (ma_uint8)x; + } + } else { + ma_uint64 i; + for (i = 0; i < count; i += 1) { + ma_int32 x = (ma_int32)(((ma_uint32)(src_s24[i*3+0]) << 8) | ((ma_uint32)(src_s24[i*3+1]) << 16) | ((ma_uint32)(src_s24[i*3+2])) << 24); + + /* Dither. Don't overflow. */ + ma_int32 dither = ma_dither_s32(ditherMode, -0x800000, 0x7FFFFF); + if ((ma_int64)x + dither <= 0x7FFFFFFF) { + x = x + dither; + } else { + x = 0x7FFFFFFF; + } + + x = x >> 24; + x = x + 128; + dst_u8[i] = (ma_uint8)x; + } + } +} + +void ma_pcm_s24_to_u8__optimized(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s24_to_u8__reference(dst, src, count, ditherMode); +} + +#if defined(MA_SUPPORT_SSE2) +void ma_pcm_s24_to_u8__sse2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s24_to_u8__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX2) +void ma_pcm_s24_to_u8__avx2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s24_to_u8__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX512) +void ma_pcm_s24_to_u8__avx512(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s24_to_u8__avx2(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_NEON) +void ma_pcm_s24_to_u8__neon(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s24_to_u8__optimized(dst, src, count, ditherMode); +} +#endif + +void ma_pcm_s24_to_u8(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_s24_to_u8__reference(dst, src, count, ditherMode); +#else + ma_pcm_s24_to_u8__optimized(dst, src, count, ditherMode); +#endif +} + + +void ma_pcm_s24_to_s16__reference(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_int16* dst_s16 = (ma_int16*)dst; + const ma_uint8* src_s24 = (const ma_uint8*)src; + + if (ditherMode == ma_dither_mode_none) { + ma_uint64 i; + for (i = 0; i < count; i += 1) { + ma_uint16 dst_lo = ((ma_uint16)src_s24[i*3 + 1]); + ma_uint16 dst_hi = ((ma_uint16)src_s24[i*3 + 2]) << 8; + dst_s16[i] = (ma_int16)dst_lo | dst_hi; + } + } else { + ma_uint64 i; + for (i = 0; i < count; i += 1) { + ma_int32 x = (ma_int32)(((ma_uint32)(src_s24[i*3+0]) << 8) | ((ma_uint32)(src_s24[i*3+1]) << 16) | ((ma_uint32)(src_s24[i*3+2])) << 24); + + /* Dither. Don't overflow. */ + ma_int32 dither = ma_dither_s32(ditherMode, -0x8000, 0x7FFF); + if ((ma_int64)x + dither <= 0x7FFFFFFF) { + x = x + dither; + } else { + x = 0x7FFFFFFF; + } + + x = x >> 16; + dst_s16[i] = (ma_int16)x; + } + } +} + +void ma_pcm_s24_to_s16__optimized(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s24_to_s16__reference(dst, src, count, ditherMode); +} + +#if defined(MA_SUPPORT_SSE2) +void ma_pcm_s24_to_s16__sse2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s24_to_s16__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX2) +void ma_pcm_s24_to_s16__avx2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s24_to_s16__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX512) +void ma_pcm_s24_to_s16__avx512(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s24_to_s16__avx2(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_NEON) +void ma_pcm_s24_to_s16__neon(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s24_to_s16__optimized(dst, src, count, ditherMode); +} +#endif + +void ma_pcm_s24_to_s16(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_s24_to_s16__reference(dst, src, count, ditherMode); +#else + ma_pcm_s24_to_s16__optimized(dst, src, count, ditherMode); +#endif +} + + +void ma_pcm_s24_to_s24(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + (void)ditherMode; + + ma_copy_memory_64(dst, src, count * 3); +} + + +void ma_pcm_s24_to_s32__reference(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_int32* dst_s32 = (ma_int32*)dst; + const ma_uint8* src_s24 = (const ma_uint8*)src; + + ma_uint64 i; + for (i = 0; i < count; i += 1) { + dst_s32[i] = (ma_int32)(((ma_uint32)(src_s24[i*3+0]) << 8) | ((ma_uint32)(src_s24[i*3+1]) << 16) | ((ma_uint32)(src_s24[i*3+2])) << 24); + } + + (void)ditherMode; +} + +void ma_pcm_s24_to_s32__optimized(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s24_to_s32__reference(dst, src, count, ditherMode); +} + +#if defined(MA_SUPPORT_SSE2) +void ma_pcm_s24_to_s32__sse2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s24_to_s32__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX2) +void ma_pcm_s24_to_s32__avx2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s24_to_s32__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX512) +void ma_pcm_s24_to_s32__avx512(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s24_to_s32__avx2(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_NEON) +void ma_pcm_s24_to_s32__neon(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s24_to_s32__optimized(dst, src, count, ditherMode); +} +#endif + +void ma_pcm_s24_to_s32(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_s24_to_s32__reference(dst, src, count, ditherMode); +#else + ma_pcm_s24_to_s32__optimized(dst, src, count, ditherMode); +#endif +} + + +void ma_pcm_s24_to_f32__reference(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + float* dst_f32 = (float*)dst; + const ma_uint8* src_s24 = (const ma_uint8*)src; + + ma_uint64 i; + for (i = 0; i < count; i += 1) { + float x = (float)(((ma_int32)(((ma_uint32)(src_s24[i*3+0]) << 8) | ((ma_uint32)(src_s24[i*3+1]) << 16) | ((ma_uint32)(src_s24[i*3+2])) << 24)) >> 8); + +#if 0 + /* The accurate way. */ + x = x + 8388608.0f; /* -8388608..8388607 to 0..16777215 */ + x = x * 0.00000011920929665621f; /* 0..16777215 to 0..2 */ + x = x - 1; /* 0..2 to -1..1 */ +#else + /* The fast way. */ + x = x * 0.00000011920928955078125f; /* -8388608..8388607 to -1..0.999969482421875 */ +#endif + + dst_f32[i] = x; + } + + (void)ditherMode; +} + +void ma_pcm_s24_to_f32__optimized(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s24_to_f32__reference(dst, src, count, ditherMode); +} + +#if defined(MA_SUPPORT_SSE2) +void ma_pcm_s24_to_f32__sse2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s24_to_f32__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX2) +void ma_pcm_s24_to_f32__avx2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s24_to_f32__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX512) +void ma_pcm_s24_to_f32__avx512(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s24_to_f32__avx2(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_NEON) +void ma_pcm_s24_to_f32__neon(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s24_to_f32__optimized(dst, src, count, ditherMode); +} +#endif + +void ma_pcm_s24_to_f32(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_s24_to_f32__reference(dst, src, count, ditherMode); +#else + ma_pcm_s24_to_f32__optimized(dst, src, count, ditherMode); +#endif +} + + +void ma_pcm_interleave_s24__reference(void* dst, const void** src, ma_uint64 frameCount, ma_uint32 channels) +{ + ma_uint8* dst8 = (ma_uint8*)dst; + const ma_uint8** src8 = (const ma_uint8**)src; + + ma_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dst8[iFrame*3*channels + iChannel*3 + 0] = src8[iChannel][iFrame*3 + 0]; + dst8[iFrame*3*channels + iChannel*3 + 1] = src8[iChannel][iFrame*3 + 1]; + dst8[iFrame*3*channels + iChannel*3 + 2] = src8[iChannel][iFrame*3 + 2]; + } + } +} + +void ma_pcm_interleave_s24__optimized(void* dst, const void** src, ma_uint64 frameCount, ma_uint32 channels) +{ + ma_pcm_interleave_s24__reference(dst, src, frameCount, channels); +} + +void ma_pcm_interleave_s24(void* dst, const void** src, ma_uint64 frameCount, ma_uint32 channels) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_interleave_s24__reference(dst, src, frameCount, channels); +#else + ma_pcm_interleave_s24__optimized(dst, src, frameCount, channels); +#endif +} + + +void ma_pcm_deinterleave_s24__reference(void** dst, const void* src, ma_uint64 frameCount, ma_uint32 channels) +{ + ma_uint8** dst8 = (ma_uint8**)dst; + const ma_uint8* src8 = (const ma_uint8*)src; + + ma_uint32 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dst8[iChannel][iFrame*3 + 0] = src8[iFrame*3*channels + iChannel*3 + 0]; + dst8[iChannel][iFrame*3 + 1] = src8[iFrame*3*channels + iChannel*3 + 1]; + dst8[iChannel][iFrame*3 + 2] = src8[iFrame*3*channels + iChannel*3 + 2]; + } + } +} + +void ma_pcm_deinterleave_s24__optimized(void** dst, const void* src, ma_uint64 frameCount, ma_uint32 channels) +{ + ma_pcm_deinterleave_s24__reference(dst, src, frameCount, channels); +} + +void ma_pcm_deinterleave_s24(void** dst, const void* src, ma_uint64 frameCount, ma_uint32 channels) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_deinterleave_s24__reference(dst, src, frameCount, channels); +#else + ma_pcm_deinterleave_s24__optimized(dst, src, frameCount, channels); +#endif +} + + + +/* s32 */ +void ma_pcm_s32_to_u8__reference(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_uint8* dst_u8 = (ma_uint8*)dst; + const ma_int32* src_s32 = (const ma_int32*)src; + + if (ditherMode == ma_dither_mode_none) { + ma_uint64 i; + for (i = 0; i < count; i += 1) { + ma_int32 x = src_s32[i]; + x = x >> 24; + x = x + 128; + dst_u8[i] = (ma_uint8)x; + } + } else { + ma_uint64 i; + for (i = 0; i < count; i += 1) { + ma_int32 x = src_s32[i]; + + /* Dither. Don't overflow. */ + ma_int32 dither = ma_dither_s32(ditherMode, -0x800000, 0x7FFFFF); + if ((ma_int64)x + dither <= 0x7FFFFFFF) { + x = x + dither; + } else { + x = 0x7FFFFFFF; + } + + x = x >> 24; + x = x + 128; + dst_u8[i] = (ma_uint8)x; + } + } +} + +void ma_pcm_s32_to_u8__optimized(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s32_to_u8__reference(dst, src, count, ditherMode); +} + +#if defined(MA_SUPPORT_SSE2) +void ma_pcm_s32_to_u8__sse2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s32_to_u8__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX2) +void ma_pcm_s32_to_u8__avx2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s32_to_u8__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX512) +void ma_pcm_s32_to_u8__avx512(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s32_to_u8__avx2(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_NEON) +void ma_pcm_s32_to_u8__neon(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s32_to_u8__optimized(dst, src, count, ditherMode); +} +#endif + +void ma_pcm_s32_to_u8(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_s32_to_u8__reference(dst, src, count, ditherMode); +#else + ma_pcm_s32_to_u8__optimized(dst, src, count, ditherMode); +#endif +} + + +void ma_pcm_s32_to_s16__reference(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_int16* dst_s16 = (ma_int16*)dst; + const ma_int32* src_s32 = (const ma_int32*)src; + + if (ditherMode == ma_dither_mode_none) { + ma_uint64 i; + for (i = 0; i < count; i += 1) { + ma_int32 x = src_s32[i]; + x = x >> 16; + dst_s16[i] = (ma_int16)x; + } + } else { + ma_uint64 i; + for (i = 0; i < count; i += 1) { + ma_int32 x = src_s32[i]; + + /* Dither. Don't overflow. */ + ma_int32 dither = ma_dither_s32(ditherMode, -0x8000, 0x7FFF); + if ((ma_int64)x + dither <= 0x7FFFFFFF) { + x = x + dither; + } else { + x = 0x7FFFFFFF; + } + + x = x >> 16; + dst_s16[i] = (ma_int16)x; + } + } +} + +void ma_pcm_s32_to_s16__optimized(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s32_to_s16__reference(dst, src, count, ditherMode); +} + +#if defined(MA_SUPPORT_SSE2) +void ma_pcm_s32_to_s16__sse2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s32_to_s16__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX2) +void ma_pcm_s32_to_s16__avx2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s32_to_s16__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX512) +void ma_pcm_s32_to_s16__avx512(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s32_to_s16__avx2(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_NEON) +void ma_pcm_s32_to_s16__neon(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s32_to_s16__optimized(dst, src, count, ditherMode); +} +#endif + +void ma_pcm_s32_to_s16(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_s32_to_s16__reference(dst, src, count, ditherMode); +#else + ma_pcm_s32_to_s16__optimized(dst, src, count, ditherMode); +#endif +} + + +void ma_pcm_s32_to_s24__reference(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_uint8* dst_s24 = (ma_uint8*)dst; + const ma_int32* src_s32 = (const ma_int32*)src; + + ma_uint64 i; + for (i = 0; i < count; i += 1) { + ma_uint32 x = (ma_uint32)src_s32[i]; + dst_s24[i*3+0] = (ma_uint8)((x & 0x0000FF00) >> 8); + dst_s24[i*3+1] = (ma_uint8)((x & 0x00FF0000) >> 16); + dst_s24[i*3+2] = (ma_uint8)((x & 0xFF000000) >> 24); + } + + (void)ditherMode; /* No dithering for s32 -> s24. */ +} + +void ma_pcm_s32_to_s24__optimized(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s32_to_s24__reference(dst, src, count, ditherMode); +} + +#if defined(MA_SUPPORT_SSE2) +void ma_pcm_s32_to_s24__sse2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s32_to_s24__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX2) +void ma_pcm_s32_to_s24__avx2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s32_to_s24__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX512) +void ma_pcm_s32_to_s24__avx512(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s32_to_s24__avx2(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_NEON) +void ma_pcm_s32_to_s24__neon(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s32_to_s24__optimized(dst, src, count, ditherMode); +} +#endif + +void ma_pcm_s32_to_s24(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_s32_to_s24__reference(dst, src, count, ditherMode); +#else + ma_pcm_s32_to_s24__optimized(dst, src, count, ditherMode); +#endif +} + + +void ma_pcm_s32_to_s32(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + (void)ditherMode; + + ma_copy_memory_64(dst, src, count * sizeof(ma_int32)); +} + + +void ma_pcm_s32_to_f32__reference(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + float* dst_f32 = (float*)dst; + const ma_int32* src_s32 = (const ma_int32*)src; + + ma_uint64 i; + for (i = 0; i < count; i += 1) { + double x = src_s32[i]; + +#if 0 + x = x + 2147483648.0; + x = x * 0.0000000004656612873077392578125; + x = x - 1; +#else + x = x / 2147483648.0; +#endif + + dst_f32[i] = (float)x; + } + + (void)ditherMode; /* No dithering for s32 -> f32. */ +} + +void ma_pcm_s32_to_f32__optimized(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s32_to_f32__reference(dst, src, count, ditherMode); +} + +#if defined(MA_SUPPORT_SSE2) +void ma_pcm_s32_to_f32__sse2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s32_to_f32__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX2) +void ma_pcm_s32_to_f32__avx2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s32_to_f32__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX512) +void ma_pcm_s32_to_f32__avx512(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s32_to_f32__avx2(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_NEON) +void ma_pcm_s32_to_f32__neon(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_s32_to_f32__optimized(dst, src, count, ditherMode); +} +#endif + +void ma_pcm_s32_to_f32(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_s32_to_f32__reference(dst, src, count, ditherMode); +#else + ma_pcm_s32_to_f32__optimized(dst, src, count, ditherMode); +#endif +} + + +void ma_pcm_interleave_s32__reference(void* dst, const void** src, ma_uint64 frameCount, ma_uint32 channels) +{ + ma_int32* dst_s32 = (ma_int32*)dst; + const ma_int32** src_s32 = (const ma_int32**)src; + + ma_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dst_s32[iFrame*channels + iChannel] = src_s32[iChannel][iFrame]; + } + } +} + +void ma_pcm_interleave_s32__optimized(void* dst, const void** src, ma_uint64 frameCount, ma_uint32 channels) +{ + ma_pcm_interleave_s32__reference(dst, src, frameCount, channels); +} + +void ma_pcm_interleave_s32(void* dst, const void** src, ma_uint64 frameCount, ma_uint32 channels) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_interleave_s32__reference(dst, src, frameCount, channels); +#else + ma_pcm_interleave_s32__optimized(dst, src, frameCount, channels); +#endif +} + + +void ma_pcm_deinterleave_s32__reference(void** dst, const void* src, ma_uint64 frameCount, ma_uint32 channels) +{ + ma_int32** dst_s32 = (ma_int32**)dst; + const ma_int32* src_s32 = (const ma_int32*)src; + + ma_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dst_s32[iChannel][iFrame] = src_s32[iFrame*channels + iChannel]; + } + } +} + +void ma_pcm_deinterleave_s32__optimized(void** dst, const void* src, ma_uint64 frameCount, ma_uint32 channels) +{ + ma_pcm_deinterleave_s32__reference(dst, src, frameCount, channels); +} + +void ma_pcm_deinterleave_s32(void** dst, const void* src, ma_uint64 frameCount, ma_uint32 channels) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_deinterleave_s32__reference(dst, src, frameCount, channels); +#else + ma_pcm_deinterleave_s32__optimized(dst, src, frameCount, channels); +#endif +} + + +/* f32 */ +void ma_pcm_f32_to_u8__reference(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_uint64 i; + + ma_uint8* dst_u8 = (ma_uint8*)dst; + const float* src_f32 = (const float*)src; + + float ditherMin = 0; + float ditherMax = 0; + if (ditherMode != ma_dither_mode_none) { + ditherMin = 1.0f / -128; + ditherMax = 1.0f / 127; + } + + for (i = 0; i < count; i += 1) { + float x = src_f32[i]; + x = x + ma_dither_f32(ditherMode, ditherMin, ditherMax); + x = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); /* clip */ + x = x + 1; /* -1..1 to 0..2 */ + x = x * 127.5f; /* 0..2 to 0..255 */ + + dst_u8[i] = (ma_uint8)x; + } +} + +void ma_pcm_f32_to_u8__optimized(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_f32_to_u8__reference(dst, src, count, ditherMode); +} + +#if defined(MA_SUPPORT_SSE2) +void ma_pcm_f32_to_u8__sse2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_f32_to_u8__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX2) +void ma_pcm_f32_to_u8__avx2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_f32_to_u8__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX512) +void ma_pcm_f32_to_u8__avx512(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_f32_to_u8__avx2(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_NEON) +void ma_pcm_f32_to_u8__neon(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_f32_to_u8__optimized(dst, src, count, ditherMode); +} +#endif + +void ma_pcm_f32_to_u8(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_f32_to_u8__reference(dst, src, count, ditherMode); +#else + ma_pcm_f32_to_u8__optimized(dst, src, count, ditherMode); +#endif +} + + +void ma_pcm_f32_to_s16__reference(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_uint64 i; + + ma_int16* dst_s16 = (ma_int16*)dst; + const float* src_f32 = (const float*)src; + + float ditherMin = 0; + float ditherMax = 0; + if (ditherMode != ma_dither_mode_none) { + ditherMin = 1.0f / -32768; + ditherMax = 1.0f / 32767; + } + + for (i = 0; i < count; i += 1) { + float x = src_f32[i]; + x = x + ma_dither_f32(ditherMode, ditherMin, ditherMax); + x = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); /* clip */ + +#if 0 + /* The accurate way. */ + x = x + 1; /* -1..1 to 0..2 */ + x = x * 32767.5f; /* 0..2 to 0..65535 */ + x = x - 32768.0f; /* 0...65535 to -32768..32767 */ +#else + /* The fast way. */ + x = x * 32767.0f; /* -1..1 to -32767..32767 */ +#endif + + dst_s16[i] = (ma_int16)x; + } +} + +void ma_pcm_f32_to_s16__optimized(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_uint64 i; + ma_uint64 i4; + ma_uint64 count4; + + ma_int16* dst_s16 = (ma_int16*)dst; + const float* src_f32 = (const float*)src; + + float ditherMin = 0; + float ditherMax = 0; + if (ditherMode != ma_dither_mode_none) { + ditherMin = 1.0f / -32768; + ditherMax = 1.0f / 32767; + } + + /* Unrolled. */ + i = 0; + count4 = count >> 2; + for (i4 = 0; i4 < count4; i4 += 1) { + float d0 = ma_dither_f32(ditherMode, ditherMin, ditherMax); + 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]; + float x3 = src_f32[i+3]; + + x0 = x0 + d0; + x1 = x1 + d1; + x2 = x2 + d2; + x3 = x3 + d3; + + 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_s16[i+0] = (ma_int16)x0; + dst_s16[i+1] = (ma_int16)x1; + dst_s16[i+2] = (ma_int16)x2; + dst_s16[i+3] = (ma_int16)x3; + + i += 4; + } + + /* Leftover. */ + for (; i < count; i += 1) { + float x = src_f32[i]; + x = x + ma_dither_f32(ditherMode, ditherMin, ditherMax); + x = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); /* clip */ + x = x * 32767.0f; /* -1..1 to -32767..32767 */ + + dst_s16[i] = (ma_int16)x; + } +} + +#if defined(MA_SUPPORT_SSE2) +void ma_pcm_f32_to_s16__sse2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_uint64 i; + ma_uint64 i8; + ma_uint64 count8; + ma_int16* dst_s16; + const float* src_f32; + float ditherMin; + float ditherMax; + + /* Both the input and output buffers need to be aligned to 16 bytes. */ + if ((((ma_uintptr)dst & 15) != 0) || (((ma_uintptr)src & 15) != 0)) { + ma_pcm_f32_to_s16__optimized(dst, src, count, ditherMode); + return; + } + + dst_s16 = (ma_int16*)dst; + src_f32 = (const float*)src; + + ditherMin = 0; + ditherMax = 0; + if (ditherMode != ma_dither_mode_none) { + ditherMin = 1.0f / -32768; + ditherMax = 1.0f / 32767; + } + + i = 0; + + /* SSE2. SSE allows us to output 8 s16's at a time which means our loop is unrolled 8 times. */ + count8 = count >> 3; + for (i8 = 0; i8 < count8; i8 += 1) { + __m128 d0; + __m128 d1; + __m128 x0; + __m128 x1; + + if (ditherMode == ma_dither_mode_none) { + d0 = _mm_set1_ps(0); + d1 = _mm_set1_ps(0); + } else if (ditherMode == ma_dither_mode_rectangle) { + d0 = _mm_set_ps( + ma_dither_f32_rectangle(ditherMin, ditherMax), + ma_dither_f32_rectangle(ditherMin, ditherMax), + ma_dither_f32_rectangle(ditherMin, ditherMax), + ma_dither_f32_rectangle(ditherMin, ditherMax) + ); + d1 = _mm_set_ps( + ma_dither_f32_rectangle(ditherMin, ditherMax), + ma_dither_f32_rectangle(ditherMin, ditherMax), + ma_dither_f32_rectangle(ditherMin, ditherMax), + ma_dither_f32_rectangle(ditherMin, ditherMax) + ); + } else { + d0 = _mm_set_ps( + ma_dither_f32_triangle(ditherMin, ditherMax), + ma_dither_f32_triangle(ditherMin, ditherMax), + ma_dither_f32_triangle(ditherMin, ditherMax), + ma_dither_f32_triangle(ditherMin, ditherMax) + ); + d1 = _mm_set_ps( + ma_dither_f32_triangle(ditherMin, ditherMax), + ma_dither_f32_triangle(ditherMin, ditherMax), + ma_dither_f32_triangle(ditherMin, ditherMax), + ma_dither_f32_triangle(ditherMin, ditherMax) + ); + } + + x0 = *((__m128*)(src_f32 + i) + 0); + x1 = *((__m128*)(src_f32 + i) + 1); + + x0 = _mm_add_ps(x0, d0); + x1 = _mm_add_ps(x1, d1); + + x0 = _mm_mul_ps(x0, _mm_set1_ps(32767.0f)); + 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; + } + + + /* Leftover. */ + for (; i < count; i += 1) { + float x = src_f32[i]; + x = x + ma_dither_f32(ditherMode, ditherMin, ditherMax); + x = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); /* clip */ + x = x * 32767.0f; /* -1..1 to -32767..32767 */ + + dst_s16[i] = (ma_int16)x; + } +} +#endif +#if defined(MA_SUPPORT_AVX2) +void ma_pcm_f32_to_s16__avx2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_uint64 i; + ma_uint64 i16; + ma_uint64 count16; + ma_int16* dst_s16; + const float* src_f32; + float ditherMin; + float ditherMax; + + /* Both the input and output buffers need to be aligned to 32 bytes. */ + if ((((ma_uintptr)dst & 31) != 0) || (((ma_uintptr)src & 31) != 0)) { + ma_pcm_f32_to_s16__optimized(dst, src, count, ditherMode); + return; + } + + dst_s16 = (ma_int16*)dst; + src_f32 = (const float*)src; + + ditherMin = 0; + ditherMax = 0; + if (ditherMode != ma_dither_mode_none) { + ditherMin = 1.0f / -32768; + ditherMax = 1.0f / 32767; + } + + i = 0; + + /* AVX2. AVX2 allows us to output 16 s16's at a time which means our loop is unrolled 16 times. */ + count16 = count >> 4; + for (i16 = 0; i16 < count16; i16 += 1) { + __m256 d0; + __m256 d1; + __m256 x0; + __m256 x1; + __m256i i0; + __m256i i1; + __m256i p0; + __m256i p1; + __m256i r; + + if (ditherMode == ma_dither_mode_none) { + d0 = _mm256_set1_ps(0); + d1 = _mm256_set1_ps(0); + } else if (ditherMode == ma_dither_mode_rectangle) { + d0 = _mm256_set_ps( + ma_dither_f32_rectangle(ditherMin, ditherMax), + ma_dither_f32_rectangle(ditherMin, ditherMax), + ma_dither_f32_rectangle(ditherMin, ditherMax), + ma_dither_f32_rectangle(ditherMin, ditherMax), + ma_dither_f32_rectangle(ditherMin, ditherMax), + ma_dither_f32_rectangle(ditherMin, ditherMax), + ma_dither_f32_rectangle(ditherMin, ditherMax), + ma_dither_f32_rectangle(ditherMin, ditherMax) + ); + d1 = _mm256_set_ps( + ma_dither_f32_rectangle(ditherMin, ditherMax), + ma_dither_f32_rectangle(ditherMin, ditherMax), + ma_dither_f32_rectangle(ditherMin, ditherMax), + ma_dither_f32_rectangle(ditherMin, ditherMax), + ma_dither_f32_rectangle(ditherMin, ditherMax), + ma_dither_f32_rectangle(ditherMin, ditherMax), + ma_dither_f32_rectangle(ditherMin, ditherMax), + ma_dither_f32_rectangle(ditherMin, ditherMax) + ); + } else { + d0 = _mm256_set_ps( + ma_dither_f32_triangle(ditherMin, ditherMax), + ma_dither_f32_triangle(ditherMin, ditherMax), + ma_dither_f32_triangle(ditherMin, ditherMax), + ma_dither_f32_triangle(ditherMin, ditherMax), + ma_dither_f32_triangle(ditherMin, ditherMax), + ma_dither_f32_triangle(ditherMin, ditherMax), + ma_dither_f32_triangle(ditherMin, ditherMax), + ma_dither_f32_triangle(ditherMin, ditherMax) + ); + d1 = _mm256_set_ps( + ma_dither_f32_triangle(ditherMin, ditherMax), + ma_dither_f32_triangle(ditherMin, ditherMax), + ma_dither_f32_triangle(ditherMin, ditherMax), + ma_dither_f32_triangle(ditherMin, ditherMax), + ma_dither_f32_triangle(ditherMin, ditherMax), + ma_dither_f32_triangle(ditherMin, ditherMax), + ma_dither_f32_triangle(ditherMin, ditherMax), + ma_dither_f32_triangle(ditherMin, ditherMax) + ); + } + + x0 = *((__m256*)(src_f32 + i) + 0); + x1 = *((__m256*)(src_f32 + i) + 1); + + x0 = _mm256_add_ps(x0, d0); + x1 = _mm256_add_ps(x1, d1); + + x0 = _mm256_mul_ps(x0, _mm256_set1_ps(32767.0f)); + x1 = _mm256_mul_ps(x1, _mm256_set1_ps(32767.0f)); + + /* Computing the final result is a little more complicated for AVX2 than SSE2. */ + i0 = _mm256_cvttps_epi32(x0); + i1 = _mm256_cvttps_epi32(x1); + p0 = _mm256_permute2x128_si256(i0, i1, 0 | 32); + p1 = _mm256_permute2x128_si256(i0, i1, 1 | 48); + r = _mm256_packs_epi32(p0, p1); + + _mm256_stream_si256(((__m256i*)(dst_s16 + i)), r); + + i += 16; + } + + + /* Leftover. */ + for (; i < count; i += 1) { + float x = src_f32[i]; + x = x + ma_dither_f32(ditherMode, ditherMin, ditherMax); + x = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); /* clip */ + x = x * 32767.0f; /* -1..1 to -32767..32767 */ + + dst_s16[i] = (ma_int16)x; + } +} +#endif +#if defined(MA_SUPPORT_AVX512) +void ma_pcm_f32_to_s16__avx512(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + /* TODO: Convert this from AVX to AVX-512. */ + ma_pcm_f32_to_s16__avx2(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_NEON) +void ma_pcm_f32_to_s16__neon(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_uint64 i; + ma_uint64 i8; + ma_uint64 count8; + ma_int16* dst_s16; + const float* src_f32; + float ditherMin; + float ditherMax; + + /* Both the input and output buffers need to be aligned to 16 bytes. */ + if ((((ma_uintptr)dst & 15) != 0) || (((ma_uintptr)src & 15) != 0)) { + ma_pcm_f32_to_s16__optimized(dst, src, count, ditherMode); + return; + } + + dst_s16 = (ma_int16*)dst; + src_f32 = (const float*)src; + + ditherMin = 0; + ditherMax = 0; + if (ditherMode != ma_dither_mode_none) { + ditherMin = 1.0f / -32768; + ditherMax = 1.0f / 32767; + } + + i = 0; + + /* NEON. NEON allows us to output 8 s16's at a time which means our loop is unrolled 8 times. */ + count8 = count >> 3; + for (i8 = 0; i8 < count8; i8 += 1) { + float32x4_t d0; + float32x4_t d1; + float32x4_t x0; + float32x4_t x1; + int32x4_t i0; + int32x4_t i1; + + if (ditherMode == ma_dither_mode_none) { + d0 = vmovq_n_f32(0); + d1 = vmovq_n_f32(0); + } else if (ditherMode == ma_dither_mode_rectangle) { + float d0v[4]; + d0v[0] = ma_dither_f32_rectangle(ditherMin, ditherMax); + d0v[1] = ma_dither_f32_rectangle(ditherMin, ditherMax); + d0v[2] = ma_dither_f32_rectangle(ditherMin, ditherMax); + d0v[3] = ma_dither_f32_rectangle(ditherMin, ditherMax); + d0 = vld1q_f32(d0v); + + float d1v[4]; + d1v[0] = ma_dither_f32_rectangle(ditherMin, ditherMax); + d1v[1] = ma_dither_f32_rectangle(ditherMin, ditherMax); + d1v[2] = ma_dither_f32_rectangle(ditherMin, ditherMax); + d1v[3] = ma_dither_f32_rectangle(ditherMin, ditherMax); + d1 = vld1q_f32(d1v); + } else { + float d0v[4]; + d0v[0] = ma_dither_f32_triangle(ditherMin, ditherMax); + d0v[1] = ma_dither_f32_triangle(ditherMin, ditherMax); + d0v[2] = ma_dither_f32_triangle(ditherMin, ditherMax); + d0v[3] = ma_dither_f32_triangle(ditherMin, ditherMax); + d0 = vld1q_f32(d0v); + + float d1v[4]; + d1v[0] = ma_dither_f32_triangle(ditherMin, ditherMax); + d1v[1] = ma_dither_f32_triangle(ditherMin, ditherMax); + d1v[2] = ma_dither_f32_triangle(ditherMin, ditherMax); + d1v[3] = ma_dither_f32_triangle(ditherMin, ditherMax); + d1 = vld1q_f32(d1v); + } + + x0 = *((float32x4_t*)(src_f32 + i) + 0); + x1 = *((float32x4_t*)(src_f32 + i) + 1); + + x0 = vaddq_f32(x0, d0); + x1 = vaddq_f32(x1, d1); + + x0 = vmulq_n_f32(x0, 32767.0f); + x1 = vmulq_n_f32(x1, 32767.0f); + + i0 = vcvtq_s32_f32(x0); + i1 = vcvtq_s32_f32(x1); + *((int16x8_t*)(dst_s16 + i)) = vcombine_s16(vqmovn_s32(i0), vqmovn_s32(i1)); + + i += 8; + } + + + /* Leftover. */ + for (; i < count; i += 1) { + float x = src_f32[i]; + x = x + ma_dither_f32(ditherMode, ditherMin, ditherMax); + x = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); /* clip */ + x = x * 32767.0f; /* -1..1 to -32767..32767 */ + + dst_s16[i] = (ma_int16)x; + } +} +#endif + +void ma_pcm_f32_to_s16(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_f32_to_s16__reference(dst, src, count, ditherMode); +#else + ma_pcm_f32_to_s16__optimized(dst, src, count, ditherMode); +#endif +} + + +void ma_pcm_f32_to_s24__reference(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_uint8* dst_s24 = (ma_uint8*)dst; + const float* src_f32 = (const float*)src; + + ma_uint64 i; + for (i = 0; i < count; i += 1) { + ma_int32 r; + float x = src_f32[i]; + x = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); /* clip */ + +#if 0 + /* The accurate way. */ + x = x + 1; /* -1..1 to 0..2 */ + x = x * 8388607.5f; /* 0..2 to 0..16777215 */ + x = x - 8388608.0f; /* 0..16777215 to -8388608..8388607 */ +#else + /* The fast way. */ + x = x * 8388607.0f; /* -1..1 to -8388607..8388607 */ +#endif + + r = (ma_int32)x; + dst_s24[(i*3)+0] = (ma_uint8)((r & 0x0000FF) >> 0); + dst_s24[(i*3)+1] = (ma_uint8)((r & 0x00FF00) >> 8); + dst_s24[(i*3)+2] = (ma_uint8)((r & 0xFF0000) >> 16); + } + + (void)ditherMode; /* No dithering for f32 -> s24. */ +} + +void ma_pcm_f32_to_s24__optimized(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_f32_to_s24__reference(dst, src, count, ditherMode); +} + +#if defined(MA_SUPPORT_SSE2) +void ma_pcm_f32_to_s24__sse2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_f32_to_s24__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX2) +void ma_pcm_f32_to_s24__avx2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_f32_to_s24__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX512) +void ma_pcm_f32_to_s24__avx512(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_f32_to_s24__avx2(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_NEON) +void ma_pcm_f32_to_s24__neon(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_f32_to_s24__optimized(dst, src, count, ditherMode); +} +#endif + +void ma_pcm_f32_to_s24(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_f32_to_s24__reference(dst, src, count, ditherMode); +#else + ma_pcm_f32_to_s24__optimized(dst, src, count, ditherMode); +#endif +} + + +void ma_pcm_f32_to_s32__reference(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_int32* dst_s32 = (ma_int32*)dst; + const float* src_f32 = (const float*)src; + + ma_uint32 i; + for (i = 0; i < count; i += 1) { + double x = src_f32[i]; + x = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); /* clip */ + +#if 0 + /* The accurate way. */ + x = x + 1; /* -1..1 to 0..2 */ + x = x * 2147483647.5; /* 0..2 to 0..4294967295 */ + x = x - 2147483648.0; /* 0...4294967295 to -2147483648..2147483647 */ +#else + /* The fast way. */ + x = x * 2147483647.0; /* -1..1 to -2147483647..2147483647 */ +#endif + + dst_s32[i] = (ma_int32)x; + } + + (void)ditherMode; /* No dithering for f32 -> s32. */ +} + +void ma_pcm_f32_to_s32__optimized(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_f32_to_s32__reference(dst, src, count, ditherMode); +} + +#if defined(MA_SUPPORT_SSE2) +void ma_pcm_f32_to_s32__sse2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_f32_to_s32__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX2) +void ma_pcm_f32_to_s32__avx2(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_f32_to_s32__optimized(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_AVX512) +void ma_pcm_f32_to_s32__avx512(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_f32_to_s32__avx2(dst, src, count, ditherMode); +} +#endif +#if defined(MA_SUPPORT_NEON) +void ma_pcm_f32_to_s32__neon(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + ma_pcm_f32_to_s32__optimized(dst, src, count, ditherMode); +} +#endif + +void ma_pcm_f32_to_s32(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_f32_to_s32__reference(dst, src, count, ditherMode); +#else + ma_pcm_f32_to_s32__optimized(dst, src, count, ditherMode); +#endif +} + + +void ma_pcm_f32_to_f32(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) +{ + (void)ditherMode; + + ma_copy_memory_64(dst, src, count * sizeof(float)); +} + + +void ma_pcm_interleave_f32__reference(void* dst, const void** src, ma_uint64 frameCount, ma_uint32 channels) +{ + float* dst_f32 = (float*)dst; + const float** src_f32 = (const float**)src; + + ma_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dst_f32[iFrame*channels + iChannel] = src_f32[iChannel][iFrame]; + } + } +} + +void ma_pcm_interleave_f32__optimized(void* dst, const void** src, ma_uint64 frameCount, ma_uint32 channels) +{ + ma_pcm_interleave_f32__reference(dst, src, frameCount, channels); +} + +void ma_pcm_interleave_f32(void* dst, const void** src, ma_uint64 frameCount, ma_uint32 channels) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_interleave_f32__reference(dst, src, frameCount, channels); +#else + ma_pcm_interleave_f32__optimized(dst, src, frameCount, channels); +#endif +} + + +void ma_pcm_deinterleave_f32__reference(void** dst, const void* src, ma_uint64 frameCount, ma_uint32 channels) +{ + float** dst_f32 = (float**)dst; + const float* src_f32 = (const float*)src; + + ma_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dst_f32[iChannel][iFrame] = src_f32[iFrame*channels + iChannel]; + } + } +} + +void ma_pcm_deinterleave_f32__optimized(void** dst, const void* src, ma_uint64 frameCount, ma_uint32 channels) +{ + ma_pcm_deinterleave_f32__reference(dst, src, frameCount, channels); +} + +void ma_pcm_deinterleave_f32(void** dst, const void* src, ma_uint64 frameCount, ma_uint32 channels) +{ +#ifdef MA_USE_REFERENCE_CONVERSION_APIS + ma_pcm_deinterleave_f32__reference(dst, src, frameCount, channels); +#else + ma_pcm_deinterleave_f32__optimized(dst, src, frameCount, channels); +#endif +} + + +void ma_format_converter_init_callbacks__default(ma_format_converter* pConverter) +{ + ma_assert(pConverter != NULL); + + switch (pConverter->config.formatIn) + { + case ma_format_u8: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_u8_to_u8; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_u8_to_s16; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_u8_to_s24; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_u8_to_s32; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_u8_to_f32; + } + } break; + + case ma_format_s16: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_s16_to_u8; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_s16_to_s16; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_s16_to_s24; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_s16_to_s32; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_s16_to_f32; + } + } break; + + case ma_format_s24: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_s24_to_u8; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_s24_to_s16; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_s24_to_s24; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_s24_to_s32; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_s24_to_f32; + } + } break; + + case ma_format_s32: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_s32_to_u8; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_s32_to_s16; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_s32_to_s24; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_s32_to_s32; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_s32_to_f32; + } + } break; + + case ma_format_f32: + default: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_f32_to_u8; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_f32_to_s16; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_f32_to_s24; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_f32_to_s32; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_f32_to_f32; + } + } break; + } +} + +#if defined(MA_SUPPORT_SSE2) +void ma_format_converter_init_callbacks__sse2(ma_format_converter* pConverter) +{ + ma_assert(pConverter != NULL); + + switch (pConverter->config.formatIn) + { + case ma_format_u8: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_u8_to_u8; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_u8_to_s16__sse2; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_u8_to_s24__sse2; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_u8_to_s32__sse2; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_u8_to_f32__sse2; + } + } break; + + case ma_format_s16: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_s16_to_u8__sse2; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_s16_to_s16; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_s16_to_s24__sse2; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_s16_to_s32__sse2; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_s16_to_f32__sse2; + } + } break; + + case ma_format_s24: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_s24_to_u8__sse2; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_s24_to_s16__sse2; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_s24_to_s24; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_s24_to_s32__sse2; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_s24_to_f32__sse2; + } + } break; + + case ma_format_s32: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_s32_to_u8__sse2; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_s32_to_s16__sse2; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_s32_to_s24__sse2; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_s32_to_s32; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_s32_to_f32__sse2; + } + } break; + + case ma_format_f32: + default: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_f32_to_u8__sse2; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_f32_to_s16__sse2; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_f32_to_s24__sse2; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_f32_to_s32__sse2; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_f32_to_f32; + } + } break; + } +} +#endif + +#if defined(MA_SUPPORT_AVX2) +void ma_format_converter_init_callbacks__avx2(ma_format_converter* pConverter) +{ + ma_assert(pConverter != NULL); + + switch (pConverter->config.formatIn) + { + case ma_format_u8: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_u8_to_u8; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_u8_to_s16__avx2; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_u8_to_s24__avx2; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_u8_to_s32__avx2; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_u8_to_f32__avx2; + } + } break; + + case ma_format_s16: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_s16_to_u8__avx2; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_s16_to_s16; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_s16_to_s24__avx2; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_s16_to_s32__avx2; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_s16_to_f32__avx2; + } + } break; + + case ma_format_s24: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_s24_to_u8__avx2; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_s24_to_s16__avx2; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_s24_to_s24; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_s24_to_s32__avx2; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_s24_to_f32__avx2; + } + } break; + + case ma_format_s32: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_s32_to_u8__avx2; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_s32_to_s16__avx2; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_s32_to_s24__avx2; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_s32_to_s32; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_s32_to_f32__avx2; + } + } break; + + case ma_format_f32: + default: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_f32_to_u8__avx2; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_f32_to_s16__avx2; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_f32_to_s24__avx2; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_f32_to_s32__avx2; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_f32_to_f32; + } + } break; + } +} +#endif + +#if defined(MA_SUPPORT_AVX512) +void ma_format_converter_init_callbacks__avx512(ma_format_converter* pConverter) +{ + ma_assert(pConverter != NULL); + + switch (pConverter->config.formatIn) + { + case ma_format_u8: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_u8_to_u8; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_u8_to_s16__avx512; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_u8_to_s24__avx512; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_u8_to_s32__avx512; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_u8_to_f32__avx512; + } + } break; + + case ma_format_s16: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_s16_to_u8__avx512; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_s16_to_s16; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_s16_to_s24__avx512; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_s16_to_s32__avx512; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_s16_to_f32__avx512; + } + } break; + + case ma_format_s24: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_s24_to_u8__avx512; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_s24_to_s16__avx512; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_s24_to_s24; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_s24_to_s32__avx512; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_s24_to_f32__avx512; + } + } break; + + case ma_format_s32: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_s32_to_u8__avx512; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_s32_to_s16__avx512; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_s32_to_s24__avx512; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_s32_to_s32; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_s32_to_f32__avx512; + } + } break; + + case ma_format_f32: + default: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_f32_to_u8__avx512; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_f32_to_s16__avx512; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_f32_to_s24__avx512; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_f32_to_s32__avx512; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_f32_to_f32; + } + } break; + } +} +#endif + +#if defined(MA_SUPPORT_NEON) +void ma_format_converter_init_callbacks__neon(ma_format_converter* pConverter) +{ + ma_assert(pConverter != NULL); + + switch (pConverter->config.formatIn) + { + case ma_format_u8: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_u8_to_u8; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_u8_to_s16__neon; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_u8_to_s24__neon; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_u8_to_s32__neon; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_u8_to_f32__neon; + } + } break; + + case ma_format_s16: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_s16_to_u8__neon; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_s16_to_s16; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_s16_to_s24__neon; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_s16_to_s32__neon; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_s16_to_f32__neon; + } + } break; + + case ma_format_s24: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_s24_to_u8__neon; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_s24_to_s16__neon; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_s24_to_s24; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_s24_to_s32__neon; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_s24_to_f32__neon; + } + } break; + + case ma_format_s32: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_s32_to_u8__neon; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_s32_to_s16__neon; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_s32_to_s24__neon; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_s32_to_s32; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_s32_to_f32__neon; + } + } break; + + case ma_format_f32: + default: + { + if (pConverter->config.formatOut == ma_format_u8) { + pConverter->onConvertPCM = ma_pcm_f32_to_u8__neon; + } else if (pConverter->config.formatOut == ma_format_s16) { + pConverter->onConvertPCM = ma_pcm_f32_to_s16__neon; + } else if (pConverter->config.formatOut == ma_format_s24) { + pConverter->onConvertPCM = ma_pcm_f32_to_s24__neon; + } else if (pConverter->config.formatOut == ma_format_s32) { + pConverter->onConvertPCM = ma_pcm_f32_to_s32__neon; + } else if (pConverter->config.formatOut == ma_format_f32) { + pConverter->onConvertPCM = ma_pcm_f32_to_f32; + } + } break; + } +} +#endif + +ma_result ma_format_converter_init(const ma_format_converter_config* pConfig, ma_format_converter* pConverter) +{ + if (pConverter == NULL) { + return MA_INVALID_ARGS; + } + ma_zero_object(pConverter); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + pConverter->config = *pConfig; + + /* SIMD */ + pConverter->useSSE2 = ma_has_sse2() && !pConfig->noSSE2; + pConverter->useAVX2 = ma_has_avx2() && !pConfig->noAVX2; + pConverter->useAVX512 = ma_has_avx512f() && !pConfig->noAVX512; + pConverter->useNEON = ma_has_neon() && !pConfig->noNEON; + +#if defined(MA_SUPPORT_AVX512) + if (pConverter->useAVX512) { + ma_format_converter_init_callbacks__avx512(pConverter); + } else +#endif +#if defined(MA_SUPPORT_AVX2) + if (pConverter->useAVX2) { + ma_format_converter_init_callbacks__avx2(pConverter); + } else +#endif +#if defined(MA_SUPPORT_SSE2) + if (pConverter->useSSE2) { + ma_format_converter_init_callbacks__sse2(pConverter); + } else +#endif +#if defined(MA_SUPPORT_NEON) + if (pConverter->useNEON) { + ma_format_converter_init_callbacks__neon(pConverter); + } else +#endif + { + ma_format_converter_init_callbacks__default(pConverter); + } + + switch (pConfig->formatOut) + { + case ma_format_u8: + { + pConverter->onInterleavePCM = ma_pcm_interleave_u8; + pConverter->onDeinterleavePCM = ma_pcm_deinterleave_u8; + } break; + case ma_format_s16: + { + pConverter->onInterleavePCM = ma_pcm_interleave_s16; + pConverter->onDeinterleavePCM = ma_pcm_deinterleave_s16; + } break; + case ma_format_s24: + { + pConverter->onInterleavePCM = ma_pcm_interleave_s24; + pConverter->onDeinterleavePCM = ma_pcm_deinterleave_s24; + } break; + case ma_format_s32: + { + pConverter->onInterleavePCM = ma_pcm_interleave_s32; + pConverter->onDeinterleavePCM = ma_pcm_deinterleave_s32; + } break; + case ma_format_f32: + default: + { + pConverter->onInterleavePCM = ma_pcm_interleave_f32; + pConverter->onDeinterleavePCM = ma_pcm_deinterleave_f32; + } break; + } + + return MA_SUCCESS; +} + +ma_uint64 ma_format_converter_read(ma_format_converter* pConverter, ma_uint64 frameCount, void* pFramesOut, void* pUserData) +{ + ma_uint64 totalFramesRead; + ma_uint32 sampleSizeIn; + ma_uint32 sampleSizeOut; + ma_uint32 frameSizeOut; + ma_uint8* pNextFramesOut; + + if (pConverter == NULL || pFramesOut == NULL) { + return 0; + } + + totalFramesRead = 0; + sampleSizeIn = ma_get_bytes_per_sample(pConverter->config.formatIn); + sampleSizeOut = ma_get_bytes_per_sample(pConverter->config.formatOut); + /*frameSizeIn = sampleSizeIn * pConverter->config.channels;*/ + frameSizeOut = sampleSizeOut * pConverter->config.channels; + pNextFramesOut = (ma_uint8*)pFramesOut; + + if (pConverter->config.onRead != NULL) { + /* Input data is interleaved. */ + if (pConverter->config.formatIn == pConverter->config.formatOut) { + /* Pass through. */ + while (totalFramesRead < frameCount) { + ma_uint32 framesJustRead; + ma_uint64 framesRemaining = (frameCount - totalFramesRead); + ma_uint64 framesToReadRightNow = framesRemaining; + if (framesToReadRightNow > 0xFFFFFFFF) { + framesToReadRightNow = 0xFFFFFFFF; + } + + framesJustRead = (ma_uint32)pConverter->config.onRead(pConverter, (ma_uint32)framesToReadRightNow, pNextFramesOut, pUserData); + if (framesJustRead == 0) { + break; + } + + totalFramesRead += framesJustRead; + pNextFramesOut += framesJustRead * frameSizeOut; + + if (framesJustRead < framesToReadRightNow) { + break; + } + } + } else { + /* Conversion required. */ + ma_uint32 maxFramesToReadAtATime; + + MA_ALIGN(MA_SIMD_ALIGNMENT) ma_uint8 temp[MA_MAX_CHANNELS * MA_MAX_PCM_SAMPLE_SIZE_IN_BYTES * 128]; + ma_assert(sizeof(temp) <= 0xFFFFFFFF); + + maxFramesToReadAtATime = sizeof(temp) / sampleSizeIn / pConverter->config.channels; + + while (totalFramesRead < frameCount) { + ma_uint32 framesJustRead; + ma_uint64 framesRemaining = (frameCount - totalFramesRead); + ma_uint64 framesToReadRightNow = framesRemaining; + if (framesToReadRightNow > maxFramesToReadAtATime) { + framesToReadRightNow = maxFramesToReadAtATime; + } + + framesJustRead = (ma_uint32)pConverter->config.onRead(pConverter, (ma_uint32)framesToReadRightNow, temp, pUserData); + if (framesJustRead == 0) { + break; + } + + pConverter->onConvertPCM(pNextFramesOut, temp, framesJustRead*pConverter->config.channels, pConverter->config.ditherMode); + + totalFramesRead += framesJustRead; + pNextFramesOut += framesJustRead * frameSizeOut; + + if (framesJustRead < framesToReadRightNow) { + break; + } + } + } + } else { + /* Input data is deinterleaved. If a conversion is required we need to do an intermediary step. */ + void* ppTempSamplesOfOutFormat[MA_MAX_CHANNELS]; + size_t splitBufferSizeOut; + ma_uint32 maxFramesToReadAtATime; + + MA_ALIGN(MA_SIMD_ALIGNMENT) ma_uint8 tempSamplesOfOutFormat[MA_MAX_CHANNELS * MA_MAX_PCM_SAMPLE_SIZE_IN_BYTES * 128]; + ma_assert(sizeof(tempSamplesOfOutFormat) <= 0xFFFFFFFF); + + ma_split_buffer(tempSamplesOfOutFormat, sizeof(tempSamplesOfOutFormat), pConverter->config.channels, MA_SIMD_ALIGNMENT, (void**)&ppTempSamplesOfOutFormat, &splitBufferSizeOut); + + maxFramesToReadAtATime = (ma_uint32)(splitBufferSizeOut / sampleSizeIn); + + while (totalFramesRead < frameCount) { + ma_uint32 framesJustRead; + ma_uint64 framesRemaining = (frameCount - totalFramesRead); + ma_uint64 framesToReadRightNow = framesRemaining; + if (framesToReadRightNow > maxFramesToReadAtATime) { + framesToReadRightNow = maxFramesToReadAtATime; + } + + if (pConverter->config.formatIn == pConverter->config.formatOut) { + /* Only interleaving. */ + framesJustRead = (ma_uint32)pConverter->config.onReadDeinterleaved(pConverter, (ma_uint32)framesToReadRightNow, ppTempSamplesOfOutFormat, pUserData); + if (framesJustRead == 0) { + break; + } + } else { + /* Interleaving + Conversion. Convert first, then interleave. */ + void* ppTempSamplesOfInFormat[MA_MAX_CHANNELS]; + size_t splitBufferSizeIn; + ma_uint32 iChannel; + + MA_ALIGN(MA_SIMD_ALIGNMENT) ma_uint8 tempSamplesOfInFormat[MA_MAX_CHANNELS * MA_MAX_PCM_SAMPLE_SIZE_IN_BYTES * 128]; + + ma_split_buffer(tempSamplesOfInFormat, sizeof(tempSamplesOfInFormat), pConverter->config.channels, MA_SIMD_ALIGNMENT, (void**)&ppTempSamplesOfInFormat, &splitBufferSizeIn); + + if (framesToReadRightNow > (splitBufferSizeIn / sampleSizeIn)) { + framesToReadRightNow = (splitBufferSizeIn / sampleSizeIn); + } + + framesJustRead = (ma_uint32)pConverter->config.onReadDeinterleaved(pConverter, (ma_uint32)framesToReadRightNow, ppTempSamplesOfInFormat, pUserData); + if (framesJustRead == 0) { + break; + } + + for (iChannel = 0; iChannel < pConverter->config.channels; iChannel += 1) { + pConverter->onConvertPCM(ppTempSamplesOfOutFormat[iChannel], ppTempSamplesOfInFormat[iChannel], framesJustRead, pConverter->config.ditherMode); + } + } + + pConverter->onInterleavePCM(pNextFramesOut, (const void**)ppTempSamplesOfOutFormat, framesJustRead, pConverter->config.channels); + + totalFramesRead += framesJustRead; + pNextFramesOut += framesJustRead * frameSizeOut; + + if (framesJustRead < framesToReadRightNow) { + break; + } + } + } + + return totalFramesRead; +} + +ma_uint64 ma_format_converter_read_deinterleaved(ma_format_converter* pConverter, ma_uint64 frameCount, void** ppSamplesOut, void* pUserData) +{ + ma_uint64 totalFramesRead; + ma_uint32 sampleSizeIn; + ma_uint32 sampleSizeOut; + ma_uint8* ppNextSamplesOut[MA_MAX_CHANNELS]; + + if (pConverter == NULL || ppSamplesOut == NULL) { + return 0; + } + + totalFramesRead = 0; + sampleSizeIn = ma_get_bytes_per_sample(pConverter->config.formatIn); + sampleSizeOut = ma_get_bytes_per_sample(pConverter->config.formatOut); + + ma_copy_memory(ppNextSamplesOut, ppSamplesOut, sizeof(void*) * pConverter->config.channels); + + if (pConverter->config.onRead != NULL) { + /* Input data is interleaved. */ + ma_uint32 maxFramesToReadAtATime; + + MA_ALIGN(MA_SIMD_ALIGNMENT) ma_uint8 tempSamplesOfOutFormat[MA_MAX_CHANNELS * MA_MAX_PCM_SAMPLE_SIZE_IN_BYTES * 128]; + ma_assert(sizeof(tempSamplesOfOutFormat) <= 0xFFFFFFFF); + + maxFramesToReadAtATime = sizeof(tempSamplesOfOutFormat) / sampleSizeIn / pConverter->config.channels; + + while (totalFramesRead < frameCount) { + ma_uint32 iChannel; + ma_uint32 framesJustRead; + ma_uint64 framesRemaining = (frameCount - totalFramesRead); + ma_uint64 framesToReadRightNow = framesRemaining; + if (framesToReadRightNow > maxFramesToReadAtATime) { + framesToReadRightNow = maxFramesToReadAtATime; + } + + if (pConverter->config.formatIn == pConverter->config.formatOut) { + /* Only de-interleaving. */ + framesJustRead = (ma_uint32)pConverter->config.onRead(pConverter, (ma_uint32)framesToReadRightNow, tempSamplesOfOutFormat, pUserData); + if (framesJustRead == 0) { + break; + } + } else { + /* De-interleaving + Conversion. Convert first, then de-interleave. */ + MA_ALIGN(MA_SIMD_ALIGNMENT) ma_uint8 tempSamplesOfInFormat[sizeof(tempSamplesOfOutFormat)]; + + framesJustRead = (ma_uint32)pConverter->config.onRead(pConverter, (ma_uint32)framesToReadRightNow, tempSamplesOfInFormat, pUserData); + if (framesJustRead == 0) { + break; + } + + pConverter->onConvertPCM(tempSamplesOfOutFormat, tempSamplesOfInFormat, framesJustRead * pConverter->config.channels, pConverter->config.ditherMode); + } + + pConverter->onDeinterleavePCM((void**)ppNextSamplesOut, tempSamplesOfOutFormat, framesJustRead, pConverter->config.channels); + + totalFramesRead += framesJustRead; + for (iChannel = 0; iChannel < pConverter->config.channels; ++iChannel) { + ppNextSamplesOut[iChannel] += framesJustRead * sampleSizeOut; + } + + if (framesJustRead < framesToReadRightNow) { + break; + } + } + } else { + /* Input data is deinterleaved. */ + if (pConverter->config.formatIn == pConverter->config.formatOut) { + /* Pass through. */ + while (totalFramesRead < frameCount) { + ma_uint32 iChannel; + ma_uint32 framesJustRead; + ma_uint64 framesRemaining = (frameCount - totalFramesRead); + ma_uint64 framesToReadRightNow = framesRemaining; + if (framesToReadRightNow > 0xFFFFFFFF) { + framesToReadRightNow = 0xFFFFFFFF; + } + + framesJustRead = (ma_uint32)pConverter->config.onReadDeinterleaved(pConverter, (ma_uint32)framesToReadRightNow, (void**)ppNextSamplesOut, pUserData); + if (framesJustRead == 0) { + break; + } + + totalFramesRead += framesJustRead; + for (iChannel = 0; iChannel < pConverter->config.channels; ++iChannel) { + ppNextSamplesOut[iChannel] += framesJustRead * sampleSizeOut; + } + + if (framesJustRead < framesToReadRightNow) { + break; + } + } + } else { + /* Conversion required. */ + void* ppTemp[MA_MAX_CHANNELS]; + size_t splitBufferSize; + ma_uint32 maxFramesToReadAtATime; + + MA_ALIGN(MA_SIMD_ALIGNMENT) ma_uint8 temp[MA_MAX_CHANNELS][MA_MAX_PCM_SAMPLE_SIZE_IN_BYTES * 128]; + ma_assert(sizeof(temp) <= 0xFFFFFFFF); + + ma_split_buffer(temp, sizeof(temp), pConverter->config.channels, MA_SIMD_ALIGNMENT, (void**)&ppTemp, &splitBufferSize); + + maxFramesToReadAtATime = (ma_uint32)(splitBufferSize / sampleSizeIn); + + while (totalFramesRead < frameCount) { + ma_uint32 iChannel; + ma_uint32 framesJustRead; + ma_uint64 framesRemaining = (frameCount - totalFramesRead); + ma_uint64 framesToReadRightNow = framesRemaining; + if (framesToReadRightNow > maxFramesToReadAtATime) { + framesToReadRightNow = maxFramesToReadAtATime; + } + + framesJustRead = (ma_uint32)pConverter->config.onReadDeinterleaved(pConverter, (ma_uint32)framesToReadRightNow, ppTemp, pUserData); + if (framesJustRead == 0) { + break; + } + + for (iChannel = 0; iChannel < pConverter->config.channels; iChannel += 1) { + pConverter->onConvertPCM(ppNextSamplesOut[iChannel], ppTemp[iChannel], framesJustRead, pConverter->config.ditherMode); + ppNextSamplesOut[iChannel] += framesJustRead * sampleSizeOut; + } + + totalFramesRead += framesJustRead; + + if (framesJustRead < framesToReadRightNow) { + break; + } + } + } + } + + return totalFramesRead; +} + + +ma_format_converter_config ma_format_converter_config_init_new() +{ + ma_format_converter_config config; + ma_zero_object(&config); + + return config; +} + +ma_format_converter_config ma_format_converter_config_init(ma_format formatIn, ma_format formatOut, ma_uint32 channels, ma_format_converter_read_proc onRead, void* pUserData) +{ + ma_format_converter_config config = ma_format_converter_config_init_new(); + config.formatIn = formatIn; + config.formatOut = formatOut; + config.channels = channels; + config.onRead = onRead; + config.onReadDeinterleaved = NULL; + config.pUserData = pUserData; + + return config; +} + +ma_format_converter_config ma_format_converter_config_init_deinterleaved(ma_format formatIn, ma_format formatOut, ma_uint32 channels, ma_format_converter_read_deinterleaved_proc onReadDeinterleaved, void* pUserData) +{ + ma_format_converter_config config = ma_format_converter_config_init(formatIn, formatOut, channels, NULL, pUserData); + config.onReadDeinterleaved = onReadDeinterleaved; + + return config; +} + + + +/************************************************************************************************************************************************************** + +Channel Routing + +**************************************************************************************************************************************************************/ + +/* +-X = Left, +X = Right +-Y = Bottom, +Y = Top +-Z = Front, +Z = Back +*/ +typedef struct +{ + float x; + float y; + float z; +} ma_vec3; + +static MA_INLINE ma_vec3 ma_vec3f(float x, float y, float z) +{ + ma_vec3 r; + r.x = x; + r.y = y; + r.z = z; + + return r; +} + +static MA_INLINE ma_vec3 ma_vec3_add(ma_vec3 a, ma_vec3 b) +{ + return ma_vec3f( + a.x + b.x, + a.y + b.y, + a.z + b.z + ); +} + +static MA_INLINE ma_vec3 ma_vec3_sub(ma_vec3 a, ma_vec3 b) +{ + return ma_vec3f( + a.x - b.x, + a.y - b.y, + a.z - b.z + ); +} + +static MA_INLINE ma_vec3 ma_vec3_mul(ma_vec3 a, ma_vec3 b) +{ + return ma_vec3f( + a.x * b.x, + a.y * b.y, + a.z * b.z + ); +} + +static MA_INLINE ma_vec3 ma_vec3_div(ma_vec3 a, ma_vec3 b) +{ + return ma_vec3f( + a.x / b.x, + a.y / b.y, + a.z / b.z + ); +} + +static MA_INLINE float ma_vec3_dot(ma_vec3 a, ma_vec3 b) +{ + return a.x*b.x + a.y*b.y + a.z*b.z; +} + +static MA_INLINE float ma_vec3_length2(ma_vec3 a) +{ + return ma_vec3_dot(a, a); +} + +static MA_INLINE float ma_vec3_length(ma_vec3 a) +{ + return (float)sqrt(ma_vec3_length2(a)); +} + +static MA_INLINE ma_vec3 ma_vec3_normalize(ma_vec3 a) +{ + float len = 1 / ma_vec3_length(a); + + ma_vec3 r; + r.x = a.x * len; + r.y = a.y * len; + r.z = a.z * len; + + return r; +} + +static MA_INLINE float ma_vec3_distance(ma_vec3 a, ma_vec3 b) +{ + return ma_vec3_length(ma_vec3_sub(a, b)); +} + + +#define MA_PLANE_LEFT 0 +#define MA_PLANE_RIGHT 1 +#define MA_PLANE_FRONT 2 +#define MA_PLANE_BACK 3 +#define MA_PLANE_BOTTOM 4 +#define MA_PLANE_TOP 5 + +float g_maChannelPlaneRatios[MA_CHANNEL_POSITION_COUNT][6] = { + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_NONE */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_MONO */ + { 0.5f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_FRONT_LEFT */ + { 0.0f, 0.5f, 0.5f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_FRONT_RIGHT */ + { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_FRONT_CENTER */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_LFE */ + { 0.5f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f}, /* MA_CHANNEL_BACK_LEFT */ + { 0.0f, 0.5f, 0.0f, 0.5f, 0.0f, 0.0f}, /* MA_CHANNEL_BACK_RIGHT */ + { 0.25f, 0.0f, 0.75f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_FRONT_LEFT_CENTER */ + { 0.0f, 0.25f, 0.75f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_FRONT_RIGHT_CENTER */ + { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f}, /* MA_CHANNEL_BACK_CENTER */ + { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_SIDE_LEFT */ + { 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_SIDE_RIGHT */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, /* MA_CHANNEL_TOP_CENTER */ + { 0.33f, 0.0f, 0.33f, 0.0f, 0.0f, 0.34f}, /* MA_CHANNEL_TOP_FRONT_LEFT */ + { 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.5f}, /* MA_CHANNEL_TOP_FRONT_CENTER */ + { 0.0f, 0.33f, 0.33f, 0.0f, 0.0f, 0.34f}, /* MA_CHANNEL_TOP_FRONT_RIGHT */ + { 0.33f, 0.0f, 0.0f, 0.33f, 0.0f, 0.34f}, /* MA_CHANNEL_TOP_BACK_LEFT */ + { 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f}, /* MA_CHANNEL_TOP_BACK_CENTER */ + { 0.0f, 0.33f, 0.0f, 0.33f, 0.0f, 0.34f}, /* MA_CHANNEL_TOP_BACK_RIGHT */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_0 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_1 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_2 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_3 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_4 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_5 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_6 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_7 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_8 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_9 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_10 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_11 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_12 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_13 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_14 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_15 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_16 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_17 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_18 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_19 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_20 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_21 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_22 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_23 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_24 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_25 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_26 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_27 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_28 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_29 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_30 */ + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, /* MA_CHANNEL_AUX_31 */ +}; + +float ma_calculate_channel_position_planar_weight(ma_channel channelPositionA, ma_channel channelPositionB) +{ + /* + 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 = + g_maChannelPlaneRatios[channelPositionA][0] * g_maChannelPlaneRatios[channelPositionB][0] + + g_maChannelPlaneRatios[channelPositionA][1] * g_maChannelPlaneRatios[channelPositionB][1] + + g_maChannelPlaneRatios[channelPositionA][2] * g_maChannelPlaneRatios[channelPositionB][2] + + g_maChannelPlaneRatios[channelPositionA][3] * g_maChannelPlaneRatios[channelPositionB][3] + + g_maChannelPlaneRatios[channelPositionA][4] * g_maChannelPlaneRatios[channelPositionB][4] + + g_maChannelPlaneRatios[channelPositionA][5] * g_maChannelPlaneRatios[channelPositionB][5]; + + return contribution; +} + +float ma_channel_router__calculate_input_channel_planar_weight(const ma_channel_router* pRouter, ma_channel channelPositionIn, ma_channel channelPositionOut) +{ + ma_assert(pRouter != NULL); + (void)pRouter; + + return ma_calculate_channel_position_planar_weight(channelPositionIn, channelPositionOut); +} + +ma_bool32 ma_channel_router__is_spatial_channel_position(const ma_channel_router* pRouter, ma_channel channelPosition) +{ + int i; + + ma_assert(pRouter != NULL); + (void)pRouter; + + if (channelPosition == MA_CHANNEL_NONE || channelPosition == MA_CHANNEL_MONO || channelPosition == MA_CHANNEL_LFE) { + return MA_FALSE; + } + + for (i = 0; i < 6; ++i) { + if (g_maChannelPlaneRatios[channelPosition][i] != 0) { + return MA_TRUE; + } + } + + return MA_FALSE; +} + +ma_result ma_channel_router_init(const ma_channel_router_config* pConfig, ma_channel_router* pRouter) +{ + ma_uint32 iChannelIn; + ma_uint32 iChannelOut; + + if (pRouter == NULL) { + return MA_INVALID_ARGS; + } + + ma_zero_object(pRouter); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + if (pConfig->onReadDeinterleaved == NULL) { + return MA_INVALID_ARGS; + } + + if (!ma_channel_map_valid(pConfig->channelsIn, pConfig->channelMapIn)) { + return MA_INVALID_ARGS; /* Invalid input channel map. */ + } + if (!ma_channel_map_valid(pConfig->channelsOut, pConfig->channelMapOut)) { + return MA_INVALID_ARGS; /* Invalid output channel map. */ + } + + pRouter->config = *pConfig; + + /* SIMD */ + pRouter->useSSE2 = ma_has_sse2() && !pConfig->noSSE2; + pRouter->useAVX2 = ma_has_avx2() && !pConfig->noAVX2; + pRouter->useAVX512 = ma_has_avx512f() && !pConfig->noAVX512; + pRouter->useNEON = ma_has_neon() && !pConfig->noNEON; + + /* If the input and output channels and channel maps are the same we should use a passthrough. */ + if (pRouter->config.channelsIn == pRouter->config.channelsOut) { + if (ma_channel_map_equal(pRouter->config.channelsIn, pRouter->config.channelMapIn, pRouter->config.channelMapOut)) { + pRouter->isPassthrough = MA_TRUE; + } + if (ma_channel_map_blank(pRouter->config.channelsIn, pRouter->config.channelMapIn) || ma_channel_map_blank(pRouter->config.channelsOut, pRouter->config.channelMapOut)) { + pRouter->isPassthrough = MA_TRUE; + } + } + + /* + 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. + 3) Otherwise channels are blended based on spatial locality. + */ + if (!pRouter->isPassthrough) { + if (pRouter->config.channelsIn == pRouter->config.channelsOut) { + ma_bool32 areAllChannelPositionsPresent = MA_TRUE; + for (iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) { + ma_bool32 isInputChannelPositionInOutput = MA_FALSE; + for (iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) { + if (pRouter->config.channelMapIn[iChannelIn] == pRouter->config.channelMapOut[iChannelOut]) { + isInputChannelPositionInOutput = MA_TRUE; + break; + } + } + + if (!isInputChannelPositionInOutput) { + areAllChannelPositionsPresent = MA_FALSE; + break; + } + } + + if (areAllChannelPositionsPresent) { + pRouter->isSimpleShuffle = MA_TRUE; + + /* + All the router will be doing is rearranging channels which means all we need to do is use a shuffling table which is just + a mapping between the index of the input channel to the index of the output channel. + */ + for (iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) { + for (iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) { + if (pRouter->config.channelMapIn[iChannelIn] == pRouter->config.channelMapOut[iChannelOut]) { + pRouter->shuffleTable[iChannelIn] = (ma_uint8)iChannelOut; + break; + } + } + } + } + } + } + + + /* + 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! + */ + + /* In all cases we need to make sure all channels that are present in both channel maps have a 1:1 mapping. */ + for (iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) { + ma_channel channelPosIn = pRouter->config.channelMapIn[iChannelIn]; + + for (iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) { + ma_channel channelPosOut = pRouter->config.channelMapOut[iChannelOut]; + + if (channelPosIn == channelPosOut) { + pRouter->config.weights[iChannelIn][iChannelOut] = 1; + } + } + } + + /* + The mono channel is accumulated on all other channels, except LFE. Make sure in this loop we exclude output mono channels since + they were handled in the pass above. + */ + for (iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) { + ma_channel channelPosIn = pRouter->config.channelMapIn[iChannelIn]; + + if (channelPosIn == MA_CHANNEL_MONO) { + for (iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) { + ma_channel channelPosOut = pRouter->config.channelMapOut[iChannelOut]; + + if (channelPosOut != MA_CHANNEL_NONE && channelPosOut != MA_CHANNEL_MONO && channelPosOut != MA_CHANNEL_LFE) { + pRouter->config.weights[iChannelIn][iChannelOut] = 1; + } + } + } + } + + /* The output mono channel is the average of all non-none, non-mono and non-lfe input channels. */ + { + ma_uint32 len = 0; + for (iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) { + ma_channel channelPosIn = pRouter->config.channelMapIn[iChannelIn]; + + if (channelPosIn != MA_CHANNEL_NONE && channelPosIn != MA_CHANNEL_MONO && channelPosIn != MA_CHANNEL_LFE) { + len += 1; + } + } + + if (len > 0) { + float monoWeight = 1.0f / len; + + for (iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) { + ma_channel channelPosOut = pRouter->config.channelMapOut[iChannelOut]; + + if (channelPosOut == MA_CHANNEL_MONO) { + for (iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) { + ma_channel channelPosIn = pRouter->config.channelMapIn[iChannelIn]; + + if (channelPosIn != MA_CHANNEL_NONE && channelPosIn != MA_CHANNEL_MONO && channelPosIn != MA_CHANNEL_LFE) { + pRouter->config.weights[iChannelIn][iChannelOut] += monoWeight; + } + } + } + } + } + } + + + /* Input and output channels that are not present on the other side need to be blended in based on spatial locality. */ + switch (pRouter->config.mixingMode) + { + case ma_channel_mix_mode_rectangular: + { + /* Unmapped input channels. */ + for (iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) { + ma_channel channelPosIn = pRouter->config.channelMapIn[iChannelIn]; + + if (ma_channel_router__is_spatial_channel_position(pRouter, channelPosIn)) { + if (!ma_channel_map_contains_channel_position(pRouter->config.channelsOut, pRouter->config.channelMapOut, channelPosIn)) { + for (iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) { + ma_channel channelPosOut = pRouter->config.channelMapOut[iChannelOut]; + + if (ma_channel_router__is_spatial_channel_position(pRouter, channelPosOut)) { + float weight = 0; + if (pRouter->config.mixingMode == ma_channel_mix_mode_planar_blend) { + weight = ma_channel_router__calculate_input_channel_planar_weight(pRouter, channelPosIn, channelPosOut); + } + + /* Only apply the weight if we haven't already got some contribution from the respective channels. */ + if (pRouter->config.weights[iChannelIn][iChannelOut] == 0) { + pRouter->config.weights[iChannelIn][iChannelOut] = weight; + } + } + } + } + } + } + + /* Unmapped output channels. */ + for (iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) { + ma_channel channelPosOut = pRouter->config.channelMapOut[iChannelOut]; + + if (ma_channel_router__is_spatial_channel_position(pRouter, channelPosOut)) { + if (!ma_channel_map_contains_channel_position(pRouter->config.channelsIn, pRouter->config.channelMapIn, channelPosOut)) { + for (iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) { + ma_channel channelPosIn = pRouter->config.channelMapIn[iChannelIn]; + + if (ma_channel_router__is_spatial_channel_position(pRouter, channelPosIn)) { + float weight = 0; + if (pRouter->config.mixingMode == ma_channel_mix_mode_planar_blend) { + weight = ma_channel_router__calculate_input_channel_planar_weight(pRouter, channelPosIn, channelPosOut); + } + + /* Only apply the weight if we haven't already got some contribution from the respective channels. */ + if (pRouter->config.weights[iChannelIn][iChannelOut] == 0) { + pRouter->config.weights[iChannelIn][iChannelOut] = weight; + } + } + } + } + } + } + } break; + + case ma_channel_mix_mode_custom_weights: + case ma_channel_mix_mode_simple: + default: + { + /* Fallthrough. */ + } break; + } + + return MA_SUCCESS; +} + +static MA_INLINE ma_bool32 ma_channel_router__can_use_sse2(ma_channel_router* pRouter, const float* pSamplesOut, const float* pSamplesIn) +{ + return pRouter->useSSE2 && (((ma_uintptr)pSamplesOut & 15) == 0) && (((ma_uintptr)pSamplesIn & 15) == 0); +} + +static MA_INLINE ma_bool32 ma_channel_router__can_use_avx2(ma_channel_router* pRouter, const float* pSamplesOut, const float* pSamplesIn) +{ + return pRouter->useAVX2 && (((ma_uintptr)pSamplesOut & 31) == 0) && (((ma_uintptr)pSamplesIn & 31) == 0); +} + +static MA_INLINE ma_bool32 ma_channel_router__can_use_avx512(ma_channel_router* pRouter, const float* pSamplesOut, const float* pSamplesIn) +{ + return pRouter->useAVX512 && (((ma_uintptr)pSamplesOut & 63) == 0) && (((ma_uintptr)pSamplesIn & 63) == 0); +} + +static MA_INLINE ma_bool32 ma_channel_router__can_use_neon(ma_channel_router* pRouter, const float* pSamplesOut, const float* pSamplesIn) +{ + return pRouter->useNEON && (((ma_uintptr)pSamplesOut & 15) == 0) && (((ma_uintptr)pSamplesIn & 15) == 0); +} + +void ma_channel_router__do_routing(ma_channel_router* pRouter, ma_uint64 frameCount, float** ppSamplesOut, const float** ppSamplesIn) +{ + ma_uint32 iChannelIn; + ma_uint32 iChannelOut; + + ma_assert(pRouter != NULL); + ma_assert(pRouter->isPassthrough == MA_FALSE); + + if (pRouter->isSimpleShuffle) { + /* A shuffle is just a re-arrangement of channels and does not require any arithmetic. */ + ma_assert(pRouter->config.channelsIn == pRouter->config.channelsOut); + for (iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) { + iChannelOut = pRouter->shuffleTable[iChannelIn]; + ma_copy_memory_64(ppSamplesOut[iChannelOut], ppSamplesIn[iChannelIn], frameCount * sizeof(float)); + } + } else { + /* This is the more complicated case. Each of the output channels is accumulated with 0 or more input channels. */ + + /* Clear. */ + for (iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) { + ma_zero_memory_64(ppSamplesOut[iChannelOut], frameCount * sizeof(float)); + } + + /* Accumulate. */ + for (iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) { + for (iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) { + ma_uint64 iFrame = 0; +#if defined(MA_SUPPORT_NEON) + if (ma_channel_router__can_use_neon(pRouter, ppSamplesOut[iChannelOut], ppSamplesIn[iChannelIn])) { + float32x4_t weight = vmovq_n_f32(pRouter->config.weights[iChannelIn][iChannelOut]); + ma_uint64 frameCount4 = frameCount/4; + ma_uint64 iFrame4; + + for (iFrame4 = 0; iFrame4 < frameCount4; iFrame4 += 1) { + float32x4_t* pO = (float32x4_t*)ppSamplesOut[iChannelOut] + iFrame4; + float32x4_t* pI = (float32x4_t*)ppSamplesIn [iChannelIn ] + iFrame4; + *pO = vaddq_f32(*pO, vmulq_f32(*pI, weight)); + } + + iFrame += frameCount4*4; + } + else +#endif +#if defined(MA_SUPPORT_AVX512) + if (ma_channel_router__can_use_avx512(pRouter, ppSamplesOut[iChannelOut], ppSamplesIn[iChannelIn])) { + __m512 weight = _mm512_set1_ps(pRouter->config.weights[iChannelIn][iChannelOut]); + ma_uint64 frameCount16 = frameCount/16; + ma_uint64 iFrame16; + + for (iFrame16 = 0; iFrame16 < frameCount16; iFrame16 += 1) { + __m512* pO = (__m512*)ppSamplesOut[iChannelOut] + iFrame16; + __m512* pI = (__m512*)ppSamplesIn [iChannelIn ] + iFrame16; + *pO = _mm512_add_ps(*pO, _mm512_mul_ps(*pI, weight)); + } + + iFrame += frameCount16*16; + } + else +#endif +#if defined(MA_SUPPORT_AVX2) + if (ma_channel_router__can_use_avx2(pRouter, ppSamplesOut[iChannelOut], ppSamplesIn[iChannelIn])) { + __m256 weight = _mm256_set1_ps(pRouter->config.weights[iChannelIn][iChannelOut]); + ma_uint64 frameCount8 = frameCount/8; + ma_uint64 iFrame8; + + for (iFrame8 = 0; iFrame8 < frameCount8; iFrame8 += 1) { + __m256* pO = (__m256*)ppSamplesOut[iChannelOut] + iFrame8; + __m256* pI = (__m256*)ppSamplesIn [iChannelIn ] + iFrame8; + *pO = _mm256_add_ps(*pO, _mm256_mul_ps(*pI, weight)); + } + + iFrame += frameCount8*8; + } + else +#endif +#if defined(MA_SUPPORT_SSE2) + if (ma_channel_router__can_use_sse2(pRouter, ppSamplesOut[iChannelOut], ppSamplesIn[iChannelIn])) { + __m128 weight = _mm_set1_ps(pRouter->config.weights[iChannelIn][iChannelOut]); + ma_uint64 frameCount4 = frameCount/4; + ma_uint64 iFrame4; + + for (iFrame4 = 0; iFrame4 < frameCount4; iFrame4 += 1) { + __m128* pO = (__m128*)ppSamplesOut[iChannelOut] + iFrame4; + __m128* pI = (__m128*)ppSamplesIn [iChannelIn ] + iFrame4; + *pO = _mm_add_ps(*pO, _mm_mul_ps(*pI, weight)); + } + + iFrame += frameCount4*4; + } else +#endif + { /* Reference. */ + float weight0 = pRouter->config.weights[iChannelIn][iChannelOut]; + float weight1 = pRouter->config.weights[iChannelIn][iChannelOut]; + float weight2 = pRouter->config.weights[iChannelIn][iChannelOut]; + float weight3 = pRouter->config.weights[iChannelIn][iChannelOut]; + ma_uint64 frameCount4 = frameCount/4; + ma_uint64 iFrame4; + + for (iFrame4 = 0; iFrame4 < frameCount4; iFrame4 += 1) { + ppSamplesOut[iChannelOut][iFrame+0] += ppSamplesIn[iChannelIn][iFrame+0] * weight0; + ppSamplesOut[iChannelOut][iFrame+1] += ppSamplesIn[iChannelIn][iFrame+1] * weight1; + ppSamplesOut[iChannelOut][iFrame+2] += ppSamplesIn[iChannelIn][iFrame+2] * weight2; + ppSamplesOut[iChannelOut][iFrame+3] += ppSamplesIn[iChannelIn][iFrame+3] * weight3; + iFrame += 4; + } + } + + /* Leftover. */ + for (; iFrame < frameCount; ++iFrame) { + ppSamplesOut[iChannelOut][iFrame] += ppSamplesIn[iChannelIn][iFrame] * pRouter->config.weights[iChannelIn][iChannelOut]; + } + } + } + } +} + +ma_uint64 ma_channel_router_read_deinterleaved(ma_channel_router* pRouter, ma_uint64 frameCount, void** ppSamplesOut, void* pUserData) +{ + if (pRouter == NULL || ppSamplesOut == NULL) { + return 0; + } + + /* Fast path for a passthrough. */ + if (pRouter->isPassthrough) { + if (frameCount <= 0xFFFFFFFF) { + return (ma_uint32)pRouter->config.onReadDeinterleaved(pRouter, (ma_uint32)frameCount, ppSamplesOut, pUserData); + } else { + float* ppNextSamplesOut[MA_MAX_CHANNELS]; + ma_uint64 totalFramesRead; + + ma_copy_memory(ppNextSamplesOut, ppSamplesOut, sizeof(float*) * pRouter->config.channelsOut); + + totalFramesRead = 0; + while (totalFramesRead < frameCount) { + ma_uint32 iChannel; + ma_uint32 framesJustRead; + ma_uint64 framesRemaining = (frameCount - totalFramesRead); + ma_uint64 framesToReadRightNow = framesRemaining; + if (framesToReadRightNow > 0xFFFFFFFF) { + framesToReadRightNow = 0xFFFFFFFF; + } + + framesJustRead = (ma_uint32)pRouter->config.onReadDeinterleaved(pRouter, (ma_uint32)framesToReadRightNow, (void**)ppNextSamplesOut, pUserData); + if (framesJustRead == 0) { + break; + } + + totalFramesRead += framesJustRead; + + if (framesJustRead < framesToReadRightNow) { + break; + } + + for (iChannel = 0; iChannel < pRouter->config.channelsOut; ++iChannel) { + ppNextSamplesOut[iChannel] += framesJustRead; + } + } + + return totalFramesRead; + } + } + + /* Slower path for a non-passthrough. */ + { + float* ppNextSamplesOut[MA_MAX_CHANNELS]; + float* ppTemp[MA_MAX_CHANNELS]; + size_t maxBytesToReadPerFrameEachIteration; + size_t maxFramesToReadEachIteration; + ma_uint64 totalFramesRead; + MA_ALIGN(MA_SIMD_ALIGNMENT) float temp[MA_MAX_CHANNELS * 256]; + + ma_assert(sizeof(temp) <= 0xFFFFFFFF); + ma_copy_memory(ppNextSamplesOut, ppSamplesOut, sizeof(float*) * pRouter->config.channelsOut); + + + ma_split_buffer(temp, sizeof(temp), pRouter->config.channelsIn, MA_SIMD_ALIGNMENT, (void**)&ppTemp, &maxBytesToReadPerFrameEachIteration); + + maxFramesToReadEachIteration = maxBytesToReadPerFrameEachIteration/sizeof(float); + + totalFramesRead = 0; + while (totalFramesRead < frameCount) { + ma_uint32 iChannel; + ma_uint32 framesJustRead; + ma_uint64 framesRemaining = (frameCount - totalFramesRead); + ma_uint64 framesToReadRightNow = framesRemaining; + if (framesToReadRightNow > maxFramesToReadEachIteration) { + framesToReadRightNow = maxFramesToReadEachIteration; + } + + framesJustRead = pRouter->config.onReadDeinterleaved(pRouter, (ma_uint32)framesToReadRightNow, (void**)ppTemp, pUserData); + if (framesJustRead == 0) { + break; + } + + ma_channel_router__do_routing(pRouter, framesJustRead, (float**)ppNextSamplesOut, (const float**)ppTemp); /* <-- Real work is done here. */ + + totalFramesRead += framesJustRead; + if (totalFramesRead < frameCount) { + for (iChannel = 0; iChannel < pRouter->config.channelsIn; iChannel += 1) { + ppNextSamplesOut[iChannel] += framesJustRead; + } + } + + if (framesJustRead < framesToReadRightNow) { + break; + } + } + + return totalFramesRead; + } +} + +ma_channel_router_config ma_channel_router_config_init(ma_uint32 channelsIn, const ma_channel channelMapIn[MA_MAX_CHANNELS], ma_uint32 channelsOut, const ma_channel channelMapOut[MA_MAX_CHANNELS], ma_channel_mix_mode mixingMode, ma_channel_router_read_deinterleaved_proc onRead, void* pUserData) +{ + ma_channel_router_config config; + ma_uint32 iChannel; + + ma_zero_object(&config); + + config.channelsIn = channelsIn; + for (iChannel = 0; iChannel < channelsIn; ++iChannel) { + config.channelMapIn[iChannel] = channelMapIn[iChannel]; + } + + config.channelsOut = channelsOut; + for (iChannel = 0; iChannel < channelsOut; ++iChannel) { + config.channelMapOut[iChannel] = channelMapOut[iChannel]; + } + + config.mixingMode = mixingMode; + config.onReadDeinterleaved = onRead; + config.pUserData = pUserData; + + return config; +} + + + +/************************************************************************************************************************************************************** + +SRC + +**************************************************************************************************************************************************************/ +#define ma_floorf(x) ((float)floor((double)(x))) +#define ma_sinf(x) ((float)sin((double)(x))) +#define ma_cosf(x) ((float)cos((double)(x))) + +static MA_INLINE double ma_sinc(double x) +{ + if (x != 0) { + return sin(MA_PI_D*x) / (MA_PI_D*x); + } else { + return 1; + } +} + +#define ma_sincf(x) ((float)ma_sinc((double)(x))) + + +ma_uint64 ma_src_read_deinterleaved__passthrough(ma_src* pSRC, ma_uint64 frameCount, void** ppSamplesOut, void* pUserData); +ma_uint64 ma_src_read_deinterleaved__linear(ma_src* pSRC, ma_uint64 frameCount, void** ppSamplesOut, void* pUserData); +ma_uint64 ma_src_read_deinterleaved__sinc(ma_src* pSRC, ma_uint64 frameCount, void** ppSamplesOut, void* pUserData); + +void ma_src__build_sinc_table__sinc(ma_src* pSRC) +{ + ma_uint32 i; + + ma_assert(pSRC != NULL); + + pSRC->sinc.table[0] = 1.0f; + for (i = 1; i < ma_countof(pSRC->sinc.table); i += 1) { + double x = i*MA_PI_D / MA_SRC_SINC_LOOKUP_TABLE_RESOLUTION; + pSRC->sinc.table[i] = (float)(sin(x)/x); + } +} + +void ma_src__build_sinc_table__rectangular(ma_src* pSRC) +{ + /* This is the same as the base sinc table. */ + ma_src__build_sinc_table__sinc(pSRC); +} + +void ma_src__build_sinc_table__hann(ma_src* pSRC) +{ + ma_uint32 i; + + ma_src__build_sinc_table__sinc(pSRC); + + for (i = 0; i < ma_countof(pSRC->sinc.table); i += 1) { + double x = pSRC->sinc.table[i]; + double N = MA_SRC_SINC_MAX_WINDOW_WIDTH*2; + double n = ((double)(i) / MA_SRC_SINC_LOOKUP_TABLE_RESOLUTION) + MA_SRC_SINC_MAX_WINDOW_WIDTH; + double w = 0.5 * (1 - cos((2*MA_PI_D*n) / (N))); + + pSRC->sinc.table[i] = (float)(x * w); + } +} + +ma_result ma_src_init(const ma_src_config* pConfig, ma_src* pSRC) +{ + if (pSRC == NULL) { + return MA_INVALID_ARGS; + } + + ma_zero_object(pSRC); + + if (pConfig == NULL || pConfig->onReadDeinterleaved == NULL) { + return MA_INVALID_ARGS; + } + if (pConfig->channels == 0 || pConfig->channels > MA_MAX_CHANNELS) { + return MA_INVALID_ARGS; + } + + pSRC->config = *pConfig; + + /* SIMD */ + pSRC->useSSE2 = ma_has_sse2() && !pConfig->noSSE2; + pSRC->useAVX2 = ma_has_avx2() && !pConfig->noAVX2; + pSRC->useAVX512 = ma_has_avx512f() && !pConfig->noAVX512; + pSRC->useNEON = ma_has_neon() && !pConfig->noNEON; + + if (pSRC->config.algorithm == ma_src_algorithm_sinc) { + /* Make sure the window width within bounds. */ + if (pSRC->config.sinc.windowWidth == 0) { + pSRC->config.sinc.windowWidth = MA_SRC_SINC_DEFAULT_WINDOW_WIDTH; + } + if (pSRC->config.sinc.windowWidth < MA_SRC_SINC_MIN_WINDOW_WIDTH) { + pSRC->config.sinc.windowWidth = MA_SRC_SINC_MIN_WINDOW_WIDTH; + } + if (pSRC->config.sinc.windowWidth > MA_SRC_SINC_MAX_WINDOW_WIDTH) { + pSRC->config.sinc.windowWidth = MA_SRC_SINC_MAX_WINDOW_WIDTH; + } + + /* Set up the lookup table. */ + switch (pSRC->config.sinc.windowFunction) { + case ma_src_sinc_window_function_hann: ma_src__build_sinc_table__hann(pSRC); break; + case ma_src_sinc_window_function_rectangular: ma_src__build_sinc_table__rectangular(pSRC); break; + default: return MA_INVALID_ARGS; /* <-- Hitting this means the window function is unknown to miniaudio. */ + } + } + + return MA_SUCCESS; +} + +ma_result ma_src_set_sample_rate(ma_src* pSRC, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut) +{ + if (pSRC == NULL) { + return MA_INVALID_ARGS; + } + + /* Must have a sample rate of > 0. */ + if (sampleRateIn == 0 || sampleRateOut == 0) { + return MA_INVALID_ARGS; + } + + ma_atomic_exchange_32(&pSRC->config.sampleRateIn, sampleRateIn); + ma_atomic_exchange_32(&pSRC->config.sampleRateOut, sampleRateOut); + + return MA_SUCCESS; +} + +ma_uint64 ma_src_read_deinterleaved(ma_src* pSRC, ma_uint64 frameCount, void** ppSamplesOut, void* pUserData) +{ + ma_src_algorithm algorithm; + + if (pSRC == NULL || frameCount == 0 || ppSamplesOut == NULL) { + return 0; + } + + algorithm = pSRC->config.algorithm; + + /* Can use a function pointer for this. */ + switch (algorithm) { + case ma_src_algorithm_none: return ma_src_read_deinterleaved__passthrough(pSRC, frameCount, ppSamplesOut, pUserData); + case ma_src_algorithm_linear: return ma_src_read_deinterleaved__linear( pSRC, frameCount, ppSamplesOut, pUserData); + case ma_src_algorithm_sinc: return ma_src_read_deinterleaved__sinc( pSRC, frameCount, ppSamplesOut, pUserData); + default: break; + } + + /* Should never get here. */ + return 0; +} + +ma_uint64 ma_src_read_deinterleaved__passthrough(ma_src* pSRC, ma_uint64 frameCount, void** ppSamplesOut, void* pUserData) +{ + if (frameCount <= 0xFFFFFFFF) { + return pSRC->config.onReadDeinterleaved(pSRC, (ma_uint32)frameCount, ppSamplesOut, pUserData); + } else { + ma_uint32 iChannel; + ma_uint64 totalFramesRead; + float* ppNextSamplesOut[MA_MAX_CHANNELS]; + + for (iChannel = 0; iChannel < pSRC->config.channels; ++iChannel) { + ppNextSamplesOut[iChannel] = (float*)ppSamplesOut[iChannel]; + } + + totalFramesRead = 0; + while (totalFramesRead < frameCount) { + ma_uint32 framesJustRead; + ma_uint64 framesRemaining = frameCount - totalFramesRead; + ma_uint64 framesToReadRightNow = framesRemaining; + if (framesToReadRightNow > 0xFFFFFFFF) { + framesToReadRightNow = 0xFFFFFFFF; + } + + framesJustRead = (ma_uint32)pSRC->config.onReadDeinterleaved(pSRC, (ma_uint32)framesToReadRightNow, (void**)ppNextSamplesOut, pUserData); + if (framesJustRead == 0) { + break; + } + + totalFramesRead += framesJustRead; + for (iChannel = 0; iChannel < pSRC->config.channels; ++iChannel) { + ppNextSamplesOut[iChannel] += framesJustRead; + } + + if (framesJustRead < framesToReadRightNow) { + break; + } + } + + return totalFramesRead; + } +} + +ma_uint64 ma_src_read_deinterleaved__linear(ma_src* pSRC, ma_uint64 frameCount, void** ppSamplesOut, void* pUserData) +{ + float* ppNextSamplesOut[MA_MAX_CHANNELS]; + float factor; + ma_uint32 maxFrameCountPerChunkIn; + ma_uint64 totalFramesRead; + + ma_assert(pSRC != NULL); + ma_assert(frameCount > 0); + ma_assert(ppSamplesOut != NULL); + + ma_copy_memory(ppNextSamplesOut, ppSamplesOut, sizeof(void*) * pSRC->config.channels); + + factor = (float)pSRC->config.sampleRateIn / pSRC->config.sampleRateOut; + maxFrameCountPerChunkIn = ma_countof(pSRC->linear.input[0]); + + totalFramesRead = 0; + while (totalFramesRead < frameCount) { + ma_uint32 iChannel; + float tBeg; + float tEnd; + float tAvailable; + float tNext; + float* ppSamplesFromClient[MA_MAX_CHANNELS]; + ma_uint32 iNextFrame; + ma_uint32 maxOutputFramesToRead; + ma_uint32 maxOutputFramesToRead4; + ma_uint32 framesToReadFromClient; + ma_uint32 framesReadFromClient; + ma_uint64 framesRemaining = frameCount - totalFramesRead; + ma_uint64 framesToRead = framesRemaining; + if (framesToRead > 16384) { + framesToRead = 16384; /* <-- Keep this small because we're using 32-bit floats for calculating sample positions and I don't want to run out of precision with huge sample counts. */ + } + + + /* Read Input Data */ + tBeg = pSRC->linear.timeIn; + tEnd = tBeg + ((ma_int64)framesToRead*factor); /* Cast to int64 required for VC6. */ + + framesToReadFromClient = (ma_uint32)(tEnd) + 1 + 1; /* +1 to make tEnd 1-based and +1 because we always need to an extra sample for interpolation. */ + if (framesToReadFromClient >= maxFrameCountPerChunkIn) { + framesToReadFromClient = maxFrameCountPerChunkIn; + } + + for (iChannel = 0; iChannel < pSRC->config.channels; ++iChannel) { + ppSamplesFromClient[iChannel] = pSRC->linear.input[iChannel] + pSRC->linear.leftoverFrames; + } + + framesReadFromClient = 0; + if (framesToReadFromClient > pSRC->linear.leftoverFrames) { + framesReadFromClient = (ma_uint32)pSRC->config.onReadDeinterleaved(pSRC, (ma_uint32)framesToReadFromClient - pSRC->linear.leftoverFrames, (void**)ppSamplesFromClient, pUserData); + } + + framesReadFromClient += pSRC->linear.leftoverFrames; /* <-- You can sort of think of it as though we've re-read the leftover samples from the client. */ + if (framesReadFromClient < 2) { + break; + } + + for (iChannel = 0; iChannel < pSRC->config.channels; ++iChannel) { + ppSamplesFromClient[iChannel] = pSRC->linear.input[iChannel]; + } + + + /* Write Output Data */ + + /* + At this point we have a bunch of frames that the client has given to us for processing. From this we can determine the maximum number of output frames + that can be processed from this input. We want to output as many samples as possible from our input data. + */ + tAvailable = framesReadFromClient - tBeg - 1; /* Subtract 1 because the last input sample is needed for interpolation and cannot be included in the output sample count calculation. */ + + maxOutputFramesToRead = (ma_uint32)(tAvailable / factor); + if (maxOutputFramesToRead == 0) { + maxOutputFramesToRead = 1; + } + if (maxOutputFramesToRead > framesToRead) { + maxOutputFramesToRead = (ma_uint32)framesToRead; + } + + /* Output frames are always read in groups of 4 because I'm planning on using this as a reference for some SIMD-y stuff later. */ + maxOutputFramesToRead4 = maxOutputFramesToRead/4; + for (iChannel = 0; iChannel < pSRC->config.channels; ++iChannel) { + ma_uint32 iFrameOut; + float t0 = pSRC->linear.timeIn + factor*0; + float t1 = pSRC->linear.timeIn + factor*1; + float t2 = pSRC->linear.timeIn + factor*2; + float t3 = pSRC->linear.timeIn + factor*3; + float t; + + for (iFrameOut = 0; iFrameOut < maxOutputFramesToRead4; iFrameOut += 1) { + float iPrevSample0 = (float)floor(t0); + float iPrevSample1 = (float)floor(t1); + float iPrevSample2 = (float)floor(t2); + float iPrevSample3 = (float)floor(t3); + + float iNextSample0 = iPrevSample0 + 1; + float iNextSample1 = iPrevSample1 + 1; + float iNextSample2 = iPrevSample2 + 1; + float iNextSample3 = iPrevSample3 + 1; + + float alpha0 = t0 - iPrevSample0; + float alpha1 = t1 - iPrevSample1; + float alpha2 = t2 - iPrevSample2; + float alpha3 = t3 - iPrevSample3; + + float prevSample0 = ppSamplesFromClient[iChannel][(ma_uint32)iPrevSample0]; + float prevSample1 = ppSamplesFromClient[iChannel][(ma_uint32)iPrevSample1]; + float prevSample2 = ppSamplesFromClient[iChannel][(ma_uint32)iPrevSample2]; + float prevSample3 = ppSamplesFromClient[iChannel][(ma_uint32)iPrevSample3]; + + float nextSample0 = ppSamplesFromClient[iChannel][(ma_uint32)iNextSample0]; + float nextSample1 = ppSamplesFromClient[iChannel][(ma_uint32)iNextSample1]; + float nextSample2 = ppSamplesFromClient[iChannel][(ma_uint32)iNextSample2]; + float nextSample3 = ppSamplesFromClient[iChannel][(ma_uint32)iNextSample3]; + + ppNextSamplesOut[iChannel][iFrameOut*4 + 0] = ma_mix_f32_fast(prevSample0, nextSample0, alpha0); + ppNextSamplesOut[iChannel][iFrameOut*4 + 1] = ma_mix_f32_fast(prevSample1, nextSample1, alpha1); + ppNextSamplesOut[iChannel][iFrameOut*4 + 2] = ma_mix_f32_fast(prevSample2, nextSample2, alpha2); + ppNextSamplesOut[iChannel][iFrameOut*4 + 3] = ma_mix_f32_fast(prevSample3, nextSample3, alpha3); + + t0 += factor*4; + t1 += factor*4; + t2 += factor*4; + t3 += factor*4; + } + + t = pSRC->linear.timeIn + (factor*maxOutputFramesToRead4*4); + for (iFrameOut = (maxOutputFramesToRead4*4); iFrameOut < maxOutputFramesToRead; iFrameOut += 1) { + float iPrevSample = (float)floor(t); + float iNextSample = iPrevSample + 1; + float alpha = t - iPrevSample; + float prevSample; + float nextSample; + + ma_assert(iPrevSample < ma_countof(pSRC->linear.input[iChannel])); + ma_assert(iNextSample < ma_countof(pSRC->linear.input[iChannel])); + + prevSample = ppSamplesFromClient[iChannel][(ma_uint32)iPrevSample]; + nextSample = ppSamplesFromClient[iChannel][(ma_uint32)iNextSample]; + + ppNextSamplesOut[iChannel][iFrameOut] = ma_mix_f32_fast(prevSample, nextSample, alpha); + + t += factor; + } + + ppNextSamplesOut[iChannel] += maxOutputFramesToRead; + } + + totalFramesRead += maxOutputFramesToRead; + + + /* Residual */ + tNext = pSRC->linear.timeIn + (maxOutputFramesToRead*factor); + + pSRC->linear.timeIn = tNext; + ma_assert(tNext <= framesReadFromClient+1); + + iNextFrame = (ma_uint32)floor(tNext); + pSRC->linear.leftoverFrames = framesReadFromClient - iNextFrame; + pSRC->linear.timeIn = tNext - iNextFrame; + + for (iChannel = 0; iChannel < pSRC->config.channels; ++iChannel) { + ma_uint32 iFrame; + for (iFrame = 0; iFrame < pSRC->linear.leftoverFrames; ++iFrame) { + float sample = ppSamplesFromClient[iChannel][framesReadFromClient-pSRC->linear.leftoverFrames + iFrame]; + ppSamplesFromClient[iChannel][iFrame] = sample; + } + } + + + /* Exit the loop if we've found everything from the client. */ + if (framesReadFromClient < framesToReadFromClient) { + break; + } + } + + return totalFramesRead; +} + + +ma_src_config ma_src_config_init_new() +{ + ma_src_config config; + ma_zero_object(&config); + + return config; +} + +ma_src_config ma_src_config_init(ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_uint32 channels, ma_src_read_deinterleaved_proc onReadDeinterleaved, void* pUserData) +{ + ma_src_config config = ma_src_config_init_new(); + config.sampleRateIn = sampleRateIn; + config.sampleRateOut = sampleRateOut; + config.channels = channels; + config.onReadDeinterleaved = onReadDeinterleaved; + config.pUserData = pUserData; + + return config; +} + + +/************************************************************************************************************************************************************** + +Sinc Sample Rate Conversion +=========================== + +The sinc SRC algorithm uses a windowed sinc to perform interpolation of samples. Currently, miniaudio's implementation supports rectangular and Hann window +methods. + +Whenever an output sample is being computed, it looks at a sub-section of the input samples. I've called this sub-section in the code below the "window", +which I realize is a bit ambigous with the mathematical "window", but it works for me when I need to conceptualize things in my head. The window is made up +of two halves. The first half contains past input samples (initialized to zero), and the second half contains future input samples. As time moves forward +and input samples are consumed, the window moves forward. The larger the window, the better the quality at the expense of slower processing. The window is +limited the range [MA_SRC_SINC_MIN_WINDOW_WIDTH, MA_SRC_SINC_MAX_WINDOW_WIDTH] and defaults to MA_SRC_SINC_DEFAULT_WINDOW_WIDTH. + +Input samples are cached for efficiency (to prevent frequently requesting tiny numbers of samples from the client). When the window gets to the end of the +cache, it's moved back to the start, and more samples are read from the client. If the client has no more data to give, the cache is filled with zeros and +the last of the input samples will be consumed. Once the last of the input samples have been consumed, no more samples will be output. + + +When reading output samples, we always first read whatever is already in the input cache. Only when the cache has been fully consumed do we read more data +from the client. + +To access samples in the input buffer you do so relative to the window. When the window itself is at position 0, the first item in the buffer is accessed +with "windowPos + windowWidth". Generally, to access any sample relative to the window you do "windowPos + windowWidth + sampleIndexRelativeToWindow". + +**************************************************************************************************************************************************************/ + +/* Comment this to disable interpolation of table lookups. Less accurate, but faster. */ +#define MA_USE_SINC_TABLE_INTERPOLATION + +/* Retrieves a sample from the input buffer's window. Values >= 0 retrieve future samples. Negative values return past samples. */ +static MA_INLINE float ma_src_sinc__get_input_sample_from_window(const ma_src* pSRC, ma_uint32 channel, ma_uint32 windowPosInSamples, ma_int32 sampleIndex) +{ + ma_assert(pSRC != NULL); + ma_assert(channel < pSRC->config.channels); + ma_assert(sampleIndex >= -(ma_int32)pSRC->config.sinc.windowWidth); + ma_assert(sampleIndex < (ma_int32)pSRC->config.sinc.windowWidth); + + /* The window should always be contained within the input cache. */ + ma_assert(windowPosInSamples < ma_countof(pSRC->sinc.input[0]) - pSRC->config.sinc.windowWidth); + + return pSRC->sinc.input[channel][windowPosInSamples + pSRC->config.sinc.windowWidth + sampleIndex]; +} + +static MA_INLINE float ma_src_sinc__interpolation_factor(const ma_src* pSRC, float x) +{ + float xabs; + ma_int32 ixabs; + + ma_assert(pSRC != NULL); + + xabs = (float)fabs(x); + xabs = xabs * MA_SRC_SINC_LOOKUP_TABLE_RESOLUTION; + ixabs = (ma_int32)xabs; + +#if defined(MA_USE_SINC_TABLE_INTERPOLATION) + { + float a = xabs - ixabs; + return ma_mix_f32_fast(pSRC->sinc.table[ixabs], pSRC->sinc.table[ixabs+1], a); + } +#else + return pSRC->sinc.table[ixabs]; +#endif +} + +#if defined(MA_SUPPORT_SSE2) +static MA_INLINE __m128 ma_fabsf_sse2(__m128 x) +{ + return _mm_and_ps(_mm_castsi128_ps(_mm_set1_epi32(0x7FFFFFFF)), x); +} + +static MA_INLINE __m128 ma_truncf_sse2(__m128 x) +{ + return _mm_cvtepi32_ps(_mm_cvttps_epi32(x)); +} + +static MA_INLINE __m128 ma_src_sinc__interpolation_factor__sse2(const ma_src* pSRC, __m128 x) +{ + __m128 resolution128; + __m128 xabs; + __m128i ixabs; + __m128 lo; + __m128 hi; + __m128 a; + __m128 r; + int* ixabsv; + + resolution128 = _mm_set1_ps(MA_SRC_SINC_LOOKUP_TABLE_RESOLUTION); + xabs = ma_fabsf_sse2(x); + xabs = _mm_mul_ps(xabs, resolution128); + ixabs = _mm_cvttps_epi32(xabs); + + ixabsv = (int*)&ixabs; + + lo = _mm_set_ps( + pSRC->sinc.table[ixabsv[3]], + pSRC->sinc.table[ixabsv[2]], + pSRC->sinc.table[ixabsv[1]], + pSRC->sinc.table[ixabsv[0]] + ); + + hi = _mm_set_ps( + pSRC->sinc.table[ixabsv[3]+1], + pSRC->sinc.table[ixabsv[2]+1], + pSRC->sinc.table[ixabsv[1]+1], + pSRC->sinc.table[ixabsv[0]+1] + ); + + a = _mm_sub_ps(xabs, _mm_cvtepi32_ps(ixabs)); + r = ma_mix_f32_fast__sse2(lo, hi, a); + + return r; +} +#endif + +#if defined(MA_SUPPORT_AVX2) +static MA_INLINE __m256 ma_fabsf_avx2(__m256 x) +{ + return _mm256_and_ps(_mm256_castsi256_ps(_mm256_set1_epi32(0x7FFFFFFF)), x); +} + +#if 0 +static MA_INLINE __m256 ma_src_sinc__interpolation_factor__avx2(const ma_src* pSRC, __m256 x) +{ + __m256 resolution256 = _mm256_set1_ps(MA_SRC_SINC_LOOKUP_TABLE_RESOLUTION); + __m256 xabs = ma_fabsf_avx2(x); + + xabs = _mm256_mul_ps(xabs, resolution256); + + __m256i ixabs = _mm256_cvttps_epi32(xabs); + __m256 a = _mm256_sub_ps(xabs, _mm256_cvtepi32_ps(ixabs)); + + + int* ixabsv = (int*)&ixabs; + + __m256 lo = _mm256_set_ps( + pSRC->sinc.table[ixabsv[7]], + pSRC->sinc.table[ixabsv[6]], + pSRC->sinc.table[ixabsv[5]], + pSRC->sinc.table[ixabsv[4]], + pSRC->sinc.table[ixabsv[3]], + pSRC->sinc.table[ixabsv[2]], + pSRC->sinc.table[ixabsv[1]], + pSRC->sinc.table[ixabsv[0]] + ); + + __m256 hi = _mm256_set_ps( + pSRC->sinc.table[ixabsv[7]+1], + pSRC->sinc.table[ixabsv[6]+1], + pSRC->sinc.table[ixabsv[5]+1], + pSRC->sinc.table[ixabsv[4]+1], + pSRC->sinc.table[ixabsv[3]+1], + pSRC->sinc.table[ixabsv[2]+1], + pSRC->sinc.table[ixabsv[1]+1], + pSRC->sinc.table[ixabsv[0]+1] + ); + + __m256 r = ma_mix_f32_fast__avx2(lo, hi, a); + + return r; +} +#endif + +#endif + +#if defined(MA_SUPPORT_NEON) +static MA_INLINE float32x4_t ma_fabsf_neon(float32x4_t x) +{ + return vabdq_f32(vmovq_n_f32(0), x); +} + +static MA_INLINE float32x4_t ma_src_sinc__interpolation_factor__neon(const ma_src* pSRC, float32x4_t x) +{ + float32x4_t xabs; + int32x4_t ixabs; + float32x4_t a; + float32x4_t r; + int* ixabsv; + float lo[4]; + float hi[4]; + + xabs = ma_fabsf_neon(x); + xabs = vmulq_n_f32(xabs, MA_SRC_SINC_LOOKUP_TABLE_RESOLUTION); + ixabs = vcvtq_s32_f32(xabs); + + ixabsv = (int*)&ixabs; + + lo[0] = pSRC->sinc.table[ixabsv[0]]; + lo[1] = pSRC->sinc.table[ixabsv[1]]; + lo[2] = pSRC->sinc.table[ixabsv[2]]; + lo[3] = pSRC->sinc.table[ixabsv[3]]; + + hi[0] = pSRC->sinc.table[ixabsv[0]+1]; + hi[1] = pSRC->sinc.table[ixabsv[1]+1]; + hi[2] = pSRC->sinc.table[ixabsv[2]+1]; + hi[3] = pSRC->sinc.table[ixabsv[3]+1]; + + a = vsubq_f32(xabs, vcvtq_f32_s32(ixabs)); + r = ma_mix_f32_fast__neon(vld1q_f32(lo), vld1q_f32(hi), a); + + return r; +} +#endif + +ma_uint64 ma_src_read_deinterleaved__sinc(ma_src* pSRC, ma_uint64 frameCount, void** ppSamplesOut, void* pUserData) +{ + float factor; + float inverseFactor; + ma_int32 windowWidth; + ma_int32 windowWidth2; + ma_int32 windowWidthSIMD; + ma_int32 windowWidthSIMD2; + float* ppNextSamplesOut[MA_MAX_CHANNELS]; + float _windowSamplesUnaligned[MA_SRC_SINC_MAX_WINDOW_WIDTH*2 + MA_SIMD_ALIGNMENT]; + float* windowSamples; + float _iWindowFUnaligned[MA_SRC_SINC_MAX_WINDOW_WIDTH*2 + MA_SIMD_ALIGNMENT]; + float* iWindowF; + ma_int32 i; + ma_uint64 totalOutputFramesRead; + + ma_assert(pSRC != NULL); + ma_assert(frameCount > 0); + ma_assert(ppSamplesOut != NULL); + + factor = (float)pSRC->config.sampleRateIn / pSRC->config.sampleRateOut; + inverseFactor = 1/factor; + + windowWidth = (ma_int32)pSRC->config.sinc.windowWidth; + windowWidth2 = windowWidth*2; + + /* + There are cases where it's actually more efficient to increase the window width so that it's aligned with the respective + SIMD pipeline being used. + */ + windowWidthSIMD = windowWidth; + if (pSRC->useNEON) { + windowWidthSIMD = (windowWidthSIMD + 1) & ~(1); + } else if (pSRC->useAVX512) { + windowWidthSIMD = (windowWidthSIMD + 7) & ~(7); + } else if (pSRC->useAVX2) { + windowWidthSIMD = (windowWidthSIMD + 3) & ~(3); + } else if (pSRC->useSSE2) { + windowWidthSIMD = (windowWidthSIMD + 1) & ~(1); + } + + windowWidthSIMD2 = windowWidthSIMD*2; + (void)windowWidthSIMD2; /* <-- Silence a warning when SIMD is disabled. */ + + ma_copy_memory(ppNextSamplesOut, ppSamplesOut, sizeof(void*) * pSRC->config.channels); + + windowSamples = (float*)(((ma_uintptr)_windowSamplesUnaligned + MA_SIMD_ALIGNMENT-1) & ~(MA_SIMD_ALIGNMENT-1)); + ma_zero_memory(windowSamples, MA_SRC_SINC_MAX_WINDOW_WIDTH*2 * sizeof(float)); + + iWindowF = (float*)(((ma_uintptr)_iWindowFUnaligned + MA_SIMD_ALIGNMENT-1) & ~(MA_SIMD_ALIGNMENT-1)); + ma_zero_memory(iWindowF, MA_SRC_SINC_MAX_WINDOW_WIDTH*2 * sizeof(float)); + + for (i = 0; i < windowWidth2; ++i) { + iWindowF[i] = (float)(i - windowWidth); + } + + totalOutputFramesRead = 0; + while (totalOutputFramesRead < frameCount) { + ma_uint32 maxInputSamplesAvailableInCache; + float timeInBeg; + float timeInEnd; + ma_uint64 maxOutputFramesToRead; + ma_uint64 outputFramesRemaining; + ma_uint64 outputFramesToRead; + ma_uint32 iChannel; + ma_uint32 prevWindowPosInSamples; + ma_uint32 availableOutputFrames; + + /* + The maximum number of frames we can read this iteration depends on how many input samples we have available to us. This is the number + of input samples between the end of the window and the end of the cache. + */ + maxInputSamplesAvailableInCache = ma_countof(pSRC->sinc.input[0]) - (pSRC->config.sinc.windowWidth*2) - pSRC->sinc.windowPosInSamples; + if (maxInputSamplesAvailableInCache > pSRC->sinc.inputFrameCount) { + maxInputSamplesAvailableInCache = pSRC->sinc.inputFrameCount; + } + + /* Never consume the tail end of the input data if requested. */ + if (pSRC->config.neverConsumeEndOfInput) { + if (maxInputSamplesAvailableInCache >= pSRC->config.sinc.windowWidth) { + maxInputSamplesAvailableInCache -= pSRC->config.sinc.windowWidth; + } else { + maxInputSamplesAvailableInCache = 0; + } + } + + timeInBeg = pSRC->sinc.timeIn; + timeInEnd = (float)(pSRC->sinc.windowPosInSamples + maxInputSamplesAvailableInCache); + + ma_assert(timeInBeg >= 0); + ma_assert(timeInBeg <= timeInEnd); + + maxOutputFramesToRead = (ma_uint64)(((timeInEnd - timeInBeg) * inverseFactor)); + + outputFramesRemaining = frameCount - totalOutputFramesRead; + outputFramesToRead = outputFramesRemaining; + if (outputFramesToRead > maxOutputFramesToRead) { + outputFramesToRead = maxOutputFramesToRead; + } + + for (iChannel = 0; iChannel < pSRC->config.channels; iChannel += 1) { + /* Do SRC. */ + float timeIn = timeInBeg; + ma_uint32 iSample; + for (iSample = 0; iSample < outputFramesToRead; iSample += 1) { + float sampleOut = 0; + float iTimeInF = ma_floorf(timeIn); + ma_uint32 iTimeIn = (ma_uint32)iTimeInF; + ma_int32 iWindow = 0; + float tScalar; + + /* Pre-load the window samples into an aligned buffer to begin with. Need to put these into an aligned buffer to make SIMD easier. */ + windowSamples[0] = 0; /* <-- The first sample is always zero. */ + for (i = 1; i < windowWidth2; ++i) { + windowSamples[i] = pSRC->sinc.input[iChannel][iTimeIn + i]; + } + +#if defined(MA_SUPPORT_AVX2) || defined(MA_SUPPORT_AVX512) + if (pSRC->useAVX2 || pSRC->useAVX512) { + __m256i ixabs[MA_SRC_SINC_MAX_WINDOW_WIDTH*2/8]; + __m256 a[MA_SRC_SINC_MAX_WINDOW_WIDTH*2/8]; + __m256 resolution256; + __m256 t; + __m256 r; + ma_int32 windowWidth8; + ma_int32 iWindow8; + + resolution256 = _mm256_set1_ps(MA_SRC_SINC_LOOKUP_TABLE_RESOLUTION); + + t = _mm256_set1_ps((timeIn - iTimeInF)); + r = _mm256_set1_ps(0); + + windowWidth8 = windowWidthSIMD2 >> 3; + for (iWindow8 = 0; iWindow8 < windowWidth8; iWindow8 += 1) { + __m256 w = *((__m256*)iWindowF + iWindow8); + + __m256 xabs = _mm256_sub_ps(t, w); + xabs = ma_fabsf_avx2(xabs); + xabs = _mm256_mul_ps(xabs, resolution256); + + ixabs[iWindow8] = _mm256_cvttps_epi32(xabs); + a[iWindow8] = _mm256_sub_ps(xabs, _mm256_cvtepi32_ps(ixabs[iWindow8])); + } + + for (iWindow8 = 0; iWindow8 < windowWidth8; iWindow8 += 1) { + int* ixabsv = (int*)&ixabs[iWindow8]; + + __m256 lo = _mm256_set_ps( + pSRC->sinc.table[ixabsv[7]], + pSRC->sinc.table[ixabsv[6]], + pSRC->sinc.table[ixabsv[5]], + pSRC->sinc.table[ixabsv[4]], + pSRC->sinc.table[ixabsv[3]], + pSRC->sinc.table[ixabsv[2]], + pSRC->sinc.table[ixabsv[1]], + pSRC->sinc.table[ixabsv[0]] + ); + + __m256 hi = _mm256_set_ps( + pSRC->sinc.table[ixabsv[7]+1], + pSRC->sinc.table[ixabsv[6]+1], + pSRC->sinc.table[ixabsv[5]+1], + pSRC->sinc.table[ixabsv[4]+1], + pSRC->sinc.table[ixabsv[3]+1], + pSRC->sinc.table[ixabsv[2]+1], + pSRC->sinc.table[ixabsv[1]+1], + pSRC->sinc.table[ixabsv[0]+1] + ); + + __m256 s = *((__m256*)windowSamples + iWindow8); + r = _mm256_add_ps(r, _mm256_mul_ps(s, ma_mix_f32_fast__avx2(lo, hi, a[iWindow8]))); + } + + /* Horizontal add. */ + __m256 x = _mm256_hadd_ps(r, _mm256_permute2f128_ps(r, r, 1)); + x = _mm256_hadd_ps(x, x); + x = _mm256_hadd_ps(x, x); + sampleOut += _mm_cvtss_f32(_mm256_castps256_ps128(x)); + + iWindow += windowWidth8 * 8; + } + else +#endif +#if defined(MA_SUPPORT_SSE2) + if (pSRC->useSSE2) { + __m128 t = _mm_set1_ps((timeIn - iTimeInF)); + __m128 r = _mm_set1_ps(0); + + ma_int32 windowWidth4 = windowWidthSIMD2 >> 2; + ma_int32 iWindow4; + for (iWindow4 = 0; iWindow4 < windowWidth4; iWindow4 += 1) { + __m128* s = (__m128*)windowSamples + iWindow4; + __m128* w = (__m128*)iWindowF + iWindow4; + + __m128 a = ma_src_sinc__interpolation_factor__sse2(pSRC, _mm_sub_ps(t, *w)); + r = _mm_add_ps(r, _mm_mul_ps(*s, a)); + } + + sampleOut += ((float*)(&r))[0]; + sampleOut += ((float*)(&r))[1]; + sampleOut += ((float*)(&r))[2]; + sampleOut += ((float*)(&r))[3]; + + iWindow += windowWidth4 * 4; + } + else +#endif +#if defined(MA_SUPPORT_NEON) + if (pSRC->useNEON) { + float32x4_t t = vmovq_n_f32((timeIn - iTimeInF)); + float32x4_t r = vmovq_n_f32(0); + + ma_int32 windowWidth4 = windowWidthSIMD2 >> 2; + ma_int32 iWindow4; + for (iWindow4 = 0; iWindow4 < windowWidth4; iWindow4 += 1) { + float32x4_t* s = (float32x4_t*)windowSamples + iWindow4; + float32x4_t* w = (float32x4_t*)iWindowF + iWindow4; + + float32x4_t a = ma_src_sinc__interpolation_factor__neon(pSRC, vsubq_f32(t, *w)); + r = vaddq_f32(r, vmulq_f32(*s, a)); + } + + sampleOut += ((float*)(&r))[0]; + sampleOut += ((float*)(&r))[1]; + sampleOut += ((float*)(&r))[2]; + sampleOut += ((float*)(&r))[3]; + + iWindow += windowWidth4 * 4; + } + else +#endif + { + iWindow += 1; /* The first one is a dummy for SIMD alignment purposes. Skip it. */ + } + + /* Non-SIMD/Reference implementation. */ + tScalar = (timeIn - iTimeIn); + for (; iWindow < windowWidth2; iWindow += 1) { + float s = windowSamples[iWindow]; + float w = iWindowF[iWindow]; + + float a = ma_src_sinc__interpolation_factor(pSRC, (tScalar - w)); + float r = s * a; + + sampleOut += r; + } + + ppNextSamplesOut[iChannel][iSample] = (float)sampleOut; + + timeIn += factor; + } + + ppNextSamplesOut[iChannel] += outputFramesToRead; + } + + totalOutputFramesRead += outputFramesToRead; + + prevWindowPosInSamples = pSRC->sinc.windowPosInSamples; + + pSRC->sinc.timeIn += ((ma_int64)outputFramesToRead * factor); /* Cast to int64 required for VC6. */ + pSRC->sinc.windowPosInSamples = (ma_uint32)pSRC->sinc.timeIn; + pSRC->sinc.inputFrameCount -= pSRC->sinc.windowPosInSamples - prevWindowPosInSamples; + + /* If the window has reached a point where we cannot read a whole output sample it needs to be moved back to the start. */ + availableOutputFrames = (ma_uint32)((timeInEnd - pSRC->sinc.timeIn) * inverseFactor); + + if (availableOutputFrames == 0) { + size_t samplesToMove = ma_countof(pSRC->sinc.input[0]) - pSRC->sinc.windowPosInSamples; + + pSRC->sinc.timeIn -= ma_floorf(pSRC->sinc.timeIn); + pSRC->sinc.windowPosInSamples = 0; + + /* Move everything from the end of the cache up to the front. */ + for (iChannel = 0; iChannel < pSRC->config.channels; iChannel += 1) { + memmove(pSRC->sinc.input[iChannel], pSRC->sinc.input[iChannel] + ma_countof(pSRC->sinc.input[iChannel]) - samplesToMove, samplesToMove * sizeof(*pSRC->sinc.input[iChannel])); + } + } + + /* Read more data from the client if required. */ + if (pSRC->isEndOfInputLoaded) { + pSRC->isEndOfInputLoaded = MA_FALSE; + break; + } + + /* + Everything beyond this point is reloading. If we're at the end of the input data we do _not_ want to try reading any more in this function call. If the + caller wants to keep trying, they can reload their internal data sources and call this function again. We should never be + */ + ma_assert(pSRC->isEndOfInputLoaded == MA_FALSE); + + if (pSRC->sinc.inputFrameCount <= pSRC->config.sinc.windowWidth || availableOutputFrames == 0) { + float* ppInputDst[MA_MAX_CHANNELS] = {0}; + ma_uint32 framesToReadFromClient; + ma_uint32 framesReadFromClient; + ma_uint32 leftoverFrames; + + for (iChannel = 0; iChannel < pSRC->config.channels; iChannel += 1) { + ppInputDst[iChannel] = pSRC->sinc.input[iChannel] + pSRC->config.sinc.windowWidth + pSRC->sinc.inputFrameCount; + } + + /* Now read data from the client. */ + framesToReadFromClient = ma_countof(pSRC->sinc.input[0]) - (pSRC->config.sinc.windowWidth + pSRC->sinc.inputFrameCount); + + framesReadFromClient = 0; + if (framesToReadFromClient > 0) { + framesReadFromClient = pSRC->config.onReadDeinterleaved(pSRC, framesToReadFromClient, (void**)ppInputDst, pUserData); + } + + if (framesReadFromClient != framesToReadFromClient) { + pSRC->isEndOfInputLoaded = MA_TRUE; + } else { + pSRC->isEndOfInputLoaded = MA_FALSE; + } + + if (framesReadFromClient != 0) { + pSRC->sinc.inputFrameCount += framesReadFromClient; + } else { + /* We couldn't get anything more from the client. If no more output samples can be computed from the available input samples we need to return. */ + if (pSRC->config.neverConsumeEndOfInput) { + if ((pSRC->sinc.inputFrameCount * inverseFactor) <= pSRC->config.sinc.windowWidth) { + break; + } + } else { + if ((pSRC->sinc.inputFrameCount * inverseFactor) < 1) { + break; + } + } + } + + /* Anything left over in the cache must be set to zero. */ + leftoverFrames = ma_countof(pSRC->sinc.input[0]) - (pSRC->config.sinc.windowWidth + pSRC->sinc.inputFrameCount); + if (leftoverFrames > 0) { + for (iChannel = 0; iChannel < pSRC->config.channels; iChannel += 1) { + ma_zero_memory(pSRC->sinc.input[iChannel] + pSRC->config.sinc.windowWidth + pSRC->sinc.inputFrameCount, leftoverFrames * sizeof(float)); + } + } + } + } + + return totalOutputFramesRead; +} + + + +/************************************************************************************************************************************************************** + +Format Conversion + +**************************************************************************************************************************************************************/ +void ma_pcm_convert(void* pOut, ma_format formatOut, const void* pIn, ma_format formatIn, ma_uint64 sampleCount, ma_dither_mode ditherMode) +{ + if (formatOut == formatIn) { + ma_copy_memory_64(pOut, pIn, sampleCount * ma_get_bytes_per_sample(formatOut)); + return; + } + + switch (formatIn) + { + case ma_format_u8: + { + switch (formatOut) + { + case ma_format_s16: ma_pcm_u8_to_s16(pOut, pIn, sampleCount, ditherMode); return; + case ma_format_s24: ma_pcm_u8_to_s24(pOut, pIn, sampleCount, ditherMode); return; + case ma_format_s32: ma_pcm_u8_to_s32(pOut, pIn, sampleCount, ditherMode); return; + case ma_format_f32: ma_pcm_u8_to_f32(pOut, pIn, sampleCount, ditherMode); return; + default: break; + } + } break; + + case ma_format_s16: + { + switch (formatOut) + { + case ma_format_u8: ma_pcm_s16_to_u8( pOut, pIn, sampleCount, ditherMode); return; + case ma_format_s24: ma_pcm_s16_to_s24(pOut, pIn, sampleCount, ditherMode); return; + case ma_format_s32: ma_pcm_s16_to_s32(pOut, pIn, sampleCount, ditherMode); return; + case ma_format_f32: ma_pcm_s16_to_f32(pOut, pIn, sampleCount, ditherMode); return; + default: break; + } + } break; + + case ma_format_s24: + { + switch (formatOut) + { + case ma_format_u8: ma_pcm_s24_to_u8( pOut, pIn, sampleCount, ditherMode); return; + case ma_format_s16: ma_pcm_s24_to_s16(pOut, pIn, sampleCount, ditherMode); return; + case ma_format_s32: ma_pcm_s24_to_s32(pOut, pIn, sampleCount, ditherMode); return; + case ma_format_f32: ma_pcm_s24_to_f32(pOut, pIn, sampleCount, ditherMode); return; + default: break; + } + } break; + + case ma_format_s32: + { + switch (formatOut) + { + case ma_format_u8: ma_pcm_s32_to_u8( pOut, pIn, sampleCount, ditherMode); return; + case ma_format_s16: ma_pcm_s32_to_s16(pOut, pIn, sampleCount, ditherMode); return; + case ma_format_s24: ma_pcm_s32_to_s24(pOut, pIn, sampleCount, ditherMode); return; + case ma_format_f32: ma_pcm_s32_to_f32(pOut, pIn, sampleCount, ditherMode); return; + default: break; + } + } break; + + case ma_format_f32: + { + switch (formatOut) + { + case ma_format_u8: ma_pcm_f32_to_u8( pOut, pIn, sampleCount, ditherMode); return; + case ma_format_s16: ma_pcm_f32_to_s16(pOut, pIn, sampleCount, ditherMode); return; + case ma_format_s24: ma_pcm_f32_to_s24(pOut, pIn, sampleCount, ditherMode); return; + case ma_format_s32: ma_pcm_f32_to_s32(pOut, pIn, sampleCount, ditherMode); return; + default: break; + } + } break; + + default: break; + } +} + +void ma_deinterleave_pcm_frames(ma_format format, ma_uint32 channels, ma_uint64 frameCount, const void* pInterleavedPCMFrames, void** ppDeinterleavedPCMFrames) +{ + if (pInterleavedPCMFrames == NULL || ppDeinterleavedPCMFrames == NULL) { + return; /* Invalid args. */ + } + + /* For efficiency we do this per format. */ + switch (format) { + case ma_format_s16: + { + const ma_int16* pSrcS16 = (const ma_int16*)pInterleavedPCMFrames; + ma_uint64 iPCMFrame; + for (iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; ++iChannel) { + ma_int16* pDstS16 = (ma_int16*)ppDeinterleavedPCMFrames[iChannel]; + pDstS16[iPCMFrame] = pSrcS16[iPCMFrame*channels+iChannel]; + } + } + } break; + + case ma_format_f32: + { + const float* pSrcF32 = (const float*)pInterleavedPCMFrames; + ma_uint64 iPCMFrame; + for (iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; ++iChannel) { + float* pDstF32 = (float*)ppDeinterleavedPCMFrames[iChannel]; + pDstF32[iPCMFrame] = pSrcF32[iPCMFrame*channels+iChannel]; + } + } + } break; + + default: + { + ma_uint32 sampleSizeInBytes = ma_get_bytes_per_sample(format); + ma_uint64 iPCMFrame; + for (iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; ++iChannel) { + void* pDst = ma_offset_ptr(ppDeinterleavedPCMFrames[iChannel], iPCMFrame*sampleSizeInBytes); + const void* pSrc = ma_offset_ptr(pInterleavedPCMFrames, (iPCMFrame*channels+iChannel)*sampleSizeInBytes); + memcpy(pDst, pSrc, sampleSizeInBytes); + } + } + } break; + } +} + +void ma_interleave_pcm_frames(ma_format format, ma_uint32 channels, ma_uint64 frameCount, const void** ppDeinterleavedPCMFrames, void* pInterleavedPCMFrames) +{ + switch (format) + { + case ma_format_s16: + { + ma_int16* pDstS16 = (ma_int16*)pInterleavedPCMFrames; + ma_uint64 iPCMFrame; + for (iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; ++iChannel) { + const ma_int16* pSrcS16 = (const ma_int16*)ppDeinterleavedPCMFrames[iChannel]; + pDstS16[iPCMFrame*channels+iChannel] = pSrcS16[iPCMFrame]; + } + } + } break; + + case ma_format_f32: + { + float* pDstF32 = (float*)pInterleavedPCMFrames; + ma_uint64 iPCMFrame; + for (iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; ++iChannel) { + const float* pSrcF32 = (const float*)ppDeinterleavedPCMFrames[iChannel]; + pDstF32[iPCMFrame*channels+iChannel] = pSrcF32[iPCMFrame]; + } + } + } break; + + default: + { + ma_uint32 sampleSizeInBytes = ma_get_bytes_per_sample(format); + ma_uint64 iPCMFrame; + for (iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; ++iChannel) { + void* pDst = ma_offset_ptr(pInterleavedPCMFrames, (iPCMFrame*channels+iChannel)*sampleSizeInBytes); + const void* pSrc = ma_offset_ptr(ppDeinterleavedPCMFrames[iChannel], iPCMFrame*sampleSizeInBytes); + memcpy(pDst, pSrc, sampleSizeInBytes); + } + } + } break; + } +} + + + +typedef struct +{ + ma_pcm_converter* pDSP; + void* pUserDataForClient; +} ma_pcm_converter_callback_data; + +ma_uint32 ma_pcm_converter__pre_format_converter_on_read(ma_format_converter* pConverter, ma_uint32 frameCount, void* pFramesOut, void* pUserData) +{ + ma_pcm_converter_callback_data* pData; + ma_pcm_converter* pDSP; + + (void)pConverter; + + pData = (ma_pcm_converter_callback_data*)pUserData; + ma_assert(pData != NULL); + + pDSP = pData->pDSP; + ma_assert(pDSP != NULL); + + return pDSP->onRead(pDSP, pFramesOut, frameCount, pData->pUserDataForClient); +} + +ma_uint32 ma_pcm_converter__post_format_converter_on_read(ma_format_converter* pConverter, ma_uint32 frameCount, void* pFramesOut, void* pUserData) +{ + ma_pcm_converter_callback_data* pData; + ma_pcm_converter* pDSP; + + (void)pConverter; + + pData = (ma_pcm_converter_callback_data*)pUserData; + ma_assert(pData != NULL); + + pDSP = pData->pDSP; + ma_assert(pDSP != NULL); + + /* When this version of this callback is used it means we're reading directly from the client. */ + ma_assert(pDSP->isPreFormatConversionRequired == MA_FALSE); + ma_assert(pDSP->isChannelRoutingRequired == MA_FALSE); + ma_assert(pDSP->isSRCRequired == MA_FALSE); + + return pDSP->onRead(pDSP, pFramesOut, frameCount, pData->pUserDataForClient); +} + +ma_uint32 ma_pcm_converter__post_format_converter_on_read_deinterleaved(ma_format_converter* pConverter, ma_uint32 frameCount, void** ppSamplesOut, void* pUserData) +{ + ma_pcm_converter_callback_data* pData; + ma_pcm_converter* pDSP; + + (void)pConverter; + + pData = (ma_pcm_converter_callback_data*)pUserData; + ma_assert(pData != NULL); + + pDSP = pData->pDSP; + ma_assert(pDSP != NULL); + + if (!pDSP->isChannelRoutingAtStart) { + return (ma_uint32)ma_channel_router_read_deinterleaved(&pDSP->channelRouter, frameCount, ppSamplesOut, pUserData); + } else { + if (pDSP->isSRCRequired) { + return (ma_uint32)ma_src_read_deinterleaved(&pDSP->src, frameCount, ppSamplesOut, pUserData); + } else { + return (ma_uint32)ma_channel_router_read_deinterleaved(&pDSP->channelRouter, frameCount, ppSamplesOut, pUserData); + } + } +} + +ma_uint32 ma_pcm_converter__src_on_read_deinterleaved(ma_src* pSRC, ma_uint32 frameCount, void** ppSamplesOut, void* pUserData) +{ + ma_pcm_converter_callback_data* pData; + ma_pcm_converter* pDSP; + + (void)pSRC; + + pData = (ma_pcm_converter_callback_data*)pUserData; + ma_assert(pData != NULL); + + pDSP = pData->pDSP; + ma_assert(pDSP != NULL); + + /* If the channel routing stage is at the front we need to read from that. Otherwise we read from the pre format converter. */ + if (pDSP->isChannelRoutingAtStart) { + return (ma_uint32)ma_channel_router_read_deinterleaved(&pDSP->channelRouter, frameCount, ppSamplesOut, pUserData); + } else { + return (ma_uint32)ma_format_converter_read_deinterleaved(&pDSP->formatConverterIn, frameCount, ppSamplesOut, pUserData); + } +} + +ma_uint32 ma_pcm_converter__channel_router_on_read_deinterleaved(ma_channel_router* pRouter, ma_uint32 frameCount, void** ppSamplesOut, void* pUserData) +{ + ma_pcm_converter_callback_data* pData; + ma_pcm_converter* pDSP; + + (void)pRouter; + + pData = (ma_pcm_converter_callback_data*)pUserData; + ma_assert(pData != NULL); + + pDSP = pData->pDSP; + ma_assert(pDSP != NULL); + + /* If the channel routing stage is at the front of the pipeline we read from the pre format converter. Otherwise we read from the sample rate converter. */ + if (pDSP->isChannelRoutingAtStart) { + return (ma_uint32)ma_format_converter_read_deinterleaved(&pDSP->formatConverterIn, frameCount, ppSamplesOut, pUserData); + } else { + if (pDSP->isSRCRequired) { + return (ma_uint32)ma_src_read_deinterleaved(&pDSP->src, frameCount, ppSamplesOut, pUserData); + } else { + return (ma_uint32)ma_format_converter_read_deinterleaved(&pDSP->formatConverterIn, frameCount, ppSamplesOut, pUserData); + } + } +} + +ma_result ma_pcm_converter_init(const ma_pcm_converter_config* pConfig, ma_pcm_converter* pDSP) +{ + ma_result result; + + if (pDSP == NULL) { + return MA_INVALID_ARGS; + } + + ma_zero_object(pDSP); + pDSP->onRead = pConfig->onRead; + pDSP->pUserData = pConfig->pUserData; + pDSP->isDynamicSampleRateAllowed = pConfig->allowDynamicSampleRate; + + /* + In general, this is the pipeline used for data conversion. Note that this can actually change which is explained later. + + Pre Format Conversion -> Sample Rate Conversion -> Channel Routing -> Post Format Conversion + + Pre Format Conversion + --------------------- + This is where the sample data is converted to a format that's usable by the later stages in the pipeline. Input data + is converted to deinterleaved floating-point. + + Channel Routing + --------------- + Channel routing is where stereo is converted to 5.1, mono is converted to stereo, etc. This stage depends on the + pre format conversion stage. + + Sample Rate Conversion + ---------------------- + Sample rate conversion depends on the pre format conversion stage and as the name implies performs sample rate conversion. + + Post Format Conversion + ---------------------- + This stage is where our deinterleaved floating-point data from the previous stages are converted to the requested output + format. + + + Optimizations + ------------- + Sometimes the conversion pipeline is rearranged for efficiency. The first obvious optimization is to eliminate unnecessary + stages in the pipeline. When no channel routing nor sample rate conversion is necessary, the entire pipeline is optimized + down to just this: + + Post Format Conversion + + When sample rate conversion is not unnecessary: + + Pre Format Conversion -> Channel Routing -> Post Format Conversion + + When channel routing is unnecessary: + + Pre Format Conversion -> Sample Rate Conversion -> Post Format Conversion + + A slightly less obvious optimization is used depending on whether or not we are increasing or decreasing the number of + channels. Because everything in the pipeline works on a per-channel basis, the efficiency of the pipeline is directly + proportionate to the number of channels that need to be processed. Therefore, it's can be more efficient to move the + channel conversion stage to an earlier or later stage. When the channel count is being reduced, we move the channel + conversion stage to the start of the pipeline so that later stages can work on a smaller number of channels at a time. + Otherwise, we move the channel conversion stage to the end of the pipeline. When reducing the channel count, the pipeline + will look like this: + + Pre Format Conversion -> Channel Routing -> Sample Rate Conversion -> Post Format Conversion + + Notice how the Channel Routing and Sample Rate Conversion stages are swapped so that the SRC stage has less data to process. + */ + + /* First we need to determine what's required and what's not. */ + if (pConfig->sampleRateIn != pConfig->sampleRateOut || pConfig->allowDynamicSampleRate) { + pDSP->isSRCRequired = MA_TRUE; + } + if (pConfig->channelsIn != pConfig->channelsOut || !ma_channel_map_equal(pConfig->channelsIn, pConfig->channelMapIn, pConfig->channelMapOut)) { + pDSP->isChannelRoutingRequired = MA_TRUE; + } + + /* If neither a sample rate conversion nor channel conversion is necessary we can skip the pre format conversion. */ + if (!pDSP->isSRCRequired && !pDSP->isChannelRoutingRequired) { + /* We don't need a pre format conversion stage, but we may still need a post format conversion stage. */ + if (pConfig->formatIn != pConfig->formatOut) { + pDSP->isPostFormatConversionRequired = MA_TRUE; + } + } else { + pDSP->isPreFormatConversionRequired = MA_TRUE; + pDSP->isPostFormatConversionRequired = MA_TRUE; + } + + /* Use a passthrough if none of the stages are being used. */ + if (!pDSP->isPreFormatConversionRequired && !pDSP->isPostFormatConversionRequired && !pDSP->isChannelRoutingRequired && !pDSP->isSRCRequired) { + pDSP->isPassthrough = MA_TRUE; + } + + /* Move the channel conversion stage to the start of the pipeline if we are reducing the channel count. */ + if (pConfig->channelsOut < pConfig->channelsIn) { + pDSP->isChannelRoutingAtStart = MA_TRUE; + } + + + /* + We always initialize every stage of the pipeline regardless of whether or not the stage is used because it simplifies + a few things when it comes to dynamically changing properties post-initialization. + */ + result = MA_SUCCESS; + + /* Pre format conversion. */ + { + ma_format_converter_config preFormatConverterConfig = ma_format_converter_config_init( + pConfig->formatIn, + ma_format_f32, + pConfig->channelsIn, + ma_pcm_converter__pre_format_converter_on_read, + pDSP + ); + preFormatConverterConfig.ditherMode = pConfig->ditherMode; + preFormatConverterConfig.noSSE2 = pConfig->noSSE2; + preFormatConverterConfig.noAVX2 = pConfig->noAVX2; + preFormatConverterConfig.noAVX512 = pConfig->noAVX512; + preFormatConverterConfig.noNEON = pConfig->noNEON; + + result = ma_format_converter_init(&preFormatConverterConfig, &pDSP->formatConverterIn); + if (result != MA_SUCCESS) { + return result; + } + } + + /* + Post format conversion. The exact configuration for this depends on whether or not we are reading data directly from the client + or from an earlier stage in the pipeline. + */ + { + ma_format_converter_config postFormatConverterConfig = ma_format_converter_config_init_new(); + postFormatConverterConfig.formatIn = pConfig->formatIn; + postFormatConverterConfig.formatOut = pConfig->formatOut; + postFormatConverterConfig.channels = pConfig->channelsOut; + postFormatConverterConfig.ditherMode = pConfig->ditherMode; + postFormatConverterConfig.noSSE2 = pConfig->noSSE2; + postFormatConverterConfig.noAVX2 = pConfig->noAVX2; + postFormatConverterConfig.noAVX512 = pConfig->noAVX512; + postFormatConverterConfig.noNEON = pConfig->noNEON; + if (pDSP->isPreFormatConversionRequired) { + postFormatConverterConfig.onReadDeinterleaved = ma_pcm_converter__post_format_converter_on_read_deinterleaved; + postFormatConverterConfig.formatIn = ma_format_f32; + } else { + postFormatConverterConfig.onRead = ma_pcm_converter__post_format_converter_on_read; + } + + result = ma_format_converter_init(&postFormatConverterConfig, &pDSP->formatConverterOut); + if (result != MA_SUCCESS) { + return result; + } + } + + /* SRC */ + { + ma_src_config srcConfig = ma_src_config_init( + pConfig->sampleRateIn, + pConfig->sampleRateOut, + ((pConfig->channelsIn < pConfig->channelsOut) ? pConfig->channelsIn : pConfig->channelsOut), + ma_pcm_converter__src_on_read_deinterleaved, + pDSP + ); + srcConfig.algorithm = pConfig->srcAlgorithm; + srcConfig.neverConsumeEndOfInput = pConfig->neverConsumeEndOfInput; + srcConfig.noSSE2 = pConfig->noSSE2; + srcConfig.noAVX2 = pConfig->noAVX2; + srcConfig.noAVX512 = pConfig->noAVX512; + srcConfig.noNEON = pConfig->noNEON; + ma_copy_memory(&srcConfig.sinc, &pConfig->sinc, sizeof(pConfig->sinc)); + + result = ma_src_init(&srcConfig, &pDSP->src); + if (result != MA_SUCCESS) { + return result; + } + } + + /* Channel conversion */ + { + ma_channel_router_config routerConfig = ma_channel_router_config_init( + pConfig->channelsIn, + pConfig->channelMapIn, + pConfig->channelsOut, + pConfig->channelMapOut, + pConfig->channelMixMode, + ma_pcm_converter__channel_router_on_read_deinterleaved, + pDSP); + routerConfig.noSSE2 = pConfig->noSSE2; + routerConfig.noAVX2 = pConfig->noAVX2; + routerConfig.noAVX512 = pConfig->noAVX512; + routerConfig.noNEON = pConfig->noNEON; + + result = ma_channel_router_init(&routerConfig, &pDSP->channelRouter); + if (result != MA_SUCCESS) { + return result; + } + } + + return MA_SUCCESS; +} + + +ma_result ma_pcm_converter_refresh_sample_rate(ma_pcm_converter* pDSP) +{ + /* The SRC stage will already have been initialized so we can just set it there. */ + ma_src_set_sample_rate(&pDSP->src, pDSP->src.config.sampleRateIn, pDSP->src.config.sampleRateOut); + return MA_SUCCESS; +} + +ma_result ma_pcm_converter_set_input_sample_rate(ma_pcm_converter* pDSP, ma_uint32 sampleRateIn) +{ + if (pDSP == NULL) { + return MA_INVALID_ARGS; + } + + /* Must have a sample rate of > 0. */ + if (sampleRateIn == 0) { + return MA_INVALID_ARGS; + } + + /* Must have been initialized with allowDynamicSampleRate. */ + if (!pDSP->isDynamicSampleRateAllowed) { + return MA_INVALID_OPERATION; + } + + ma_atomic_exchange_32(&pDSP->src.config.sampleRateIn, sampleRateIn); + return ma_pcm_converter_refresh_sample_rate(pDSP); +} + +ma_result ma_pcm_converter_set_output_sample_rate(ma_pcm_converter* pDSP, ma_uint32 sampleRateOut) +{ + if (pDSP == NULL) { + return MA_INVALID_ARGS; + } + + /* Must have a sample rate of > 0. */ + if (sampleRateOut == 0) { + return MA_INVALID_ARGS; + } + + /* Must have been initialized with allowDynamicSampleRate. */ + if (!pDSP->isDynamicSampleRateAllowed) { + return MA_INVALID_OPERATION; + } + + ma_atomic_exchange_32(&pDSP->src.config.sampleRateOut, sampleRateOut); + return ma_pcm_converter_refresh_sample_rate(pDSP); +} + +ma_result ma_pcm_converter_set_sample_rate(ma_pcm_converter* pDSP, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut) +{ + if (pDSP == NULL) { + return MA_INVALID_ARGS; + } + + /* Must have a sample rate of > 0. */ + if (sampleRateIn == 0 || sampleRateOut == 0) { + return MA_INVALID_ARGS; + } + + /* Must have been initialized with allowDynamicSampleRate. */ + if (!pDSP->isDynamicSampleRateAllowed) { + return MA_INVALID_OPERATION; + } + + ma_atomic_exchange_32(&pDSP->src.config.sampleRateIn, sampleRateIn); + ma_atomic_exchange_32(&pDSP->src.config.sampleRateOut, sampleRateOut); + + return ma_pcm_converter_refresh_sample_rate(pDSP); +} + +ma_uint64 ma_pcm_converter_read(ma_pcm_converter* pDSP, void* pFramesOut, ma_uint64 frameCount) +{ + ma_pcm_converter_callback_data data; + + if (pDSP == NULL || pFramesOut == NULL) { + return 0; + } + + /* Fast path. */ + if (pDSP->isPassthrough) { + if (frameCount <= 0xFFFFFFFF) { + return (ma_uint32)pDSP->onRead(pDSP, pFramesOut, (ma_uint32)frameCount, pDSP->pUserData); + } else { + ma_uint8* pNextFramesOut = (ma_uint8*)pFramesOut; + + ma_uint64 totalFramesRead = 0; + while (totalFramesRead < frameCount) { + ma_uint32 framesRead; + ma_uint64 framesRemaining = (frameCount - totalFramesRead); + ma_uint64 framesToReadRightNow = framesRemaining; + if (framesToReadRightNow > 0xFFFFFFFF) { + framesToReadRightNow = 0xFFFFFFFF; + } + + framesRead = pDSP->onRead(pDSP, pNextFramesOut, (ma_uint32)framesToReadRightNow, pDSP->pUserData); + if (framesRead == 0) { + break; + } + + pNextFramesOut += framesRead * pDSP->channelRouter.config.channelsOut * ma_get_bytes_per_sample(pDSP->formatConverterOut.config.formatOut); + totalFramesRead += framesRead; + } + + return totalFramesRead; + } + } + + /* Slower path. The real work is done here. To do this all we need to do is read from the last stage in the pipeline. */ + ma_assert(pDSP->isPostFormatConversionRequired == MA_TRUE); + + data.pDSP = pDSP; + data.pUserDataForClient = pDSP->pUserData; + return ma_format_converter_read(&pDSP->formatConverterOut, frameCount, pFramesOut, &data); +} + + +typedef struct +{ + const void* pDataIn; + ma_format formatIn; + ma_uint32 channelsIn; + ma_uint64 totalFrameCount; + ma_uint64 iNextFrame; + ma_bool32 isFeedingZeros; /* When set to true, feeds the DSP zero samples. */ +} ma_convert_frames__data; + +ma_uint32 ma_convert_frames__on_read(ma_pcm_converter* pDSP, void* pFramesOut, ma_uint32 frameCount, void* pUserData) +{ + ma_convert_frames__data* pData; + ma_uint32 framesToRead; + ma_uint64 framesRemaining; + ma_uint32 frameSizeInBytes; + + (void)pDSP; + + pData = (ma_convert_frames__data*)pUserData; + ma_assert(pData != NULL); + ma_assert(pData->totalFrameCount >= pData->iNextFrame); + + framesToRead = frameCount; + framesRemaining = (pData->totalFrameCount - pData->iNextFrame); + if (framesToRead > framesRemaining) { + framesToRead = (ma_uint32)framesRemaining; + } + + frameSizeInBytes = ma_get_bytes_per_frame(pData->formatIn, pData->channelsIn); + + if (!pData->isFeedingZeros) { + ma_copy_memory(pFramesOut, (const ma_uint8*)pData->pDataIn + (frameSizeInBytes * pData->iNextFrame), frameSizeInBytes * framesToRead); + } else { + ma_zero_memory(pFramesOut, frameSizeInBytes * framesToRead); + } + + pData->iNextFrame += framesToRead; + return framesToRead; +} + +ma_pcm_converter_config ma_pcm_converter_config_init_new() +{ + ma_pcm_converter_config config; + ma_zero_object(&config); + + return config; +} + +ma_pcm_converter_config ma_pcm_converter_config_init(ma_format formatIn, ma_uint32 channelsIn, ma_uint32 sampleRateIn, ma_format formatOut, ma_uint32 channelsOut, ma_uint32 sampleRateOut, ma_pcm_converter_read_proc onRead, void* pUserData) +{ + return ma_pcm_converter_config_init_ex(formatIn, channelsIn, sampleRateIn, NULL, formatOut, channelsOut, sampleRateOut, NULL, onRead, pUserData); +} + +ma_pcm_converter_config ma_pcm_converter_config_init_ex(ma_format formatIn, ma_uint32 channelsIn, ma_uint32 sampleRateIn, ma_channel channelMapIn[MA_MAX_CHANNELS], ma_format formatOut, ma_uint32 channelsOut, ma_uint32 sampleRateOut, ma_channel channelMapOut[MA_MAX_CHANNELS], ma_pcm_converter_read_proc onRead, void* pUserData) +{ + ma_pcm_converter_config config; + ma_zero_object(&config); + config.formatIn = formatIn; + config.channelsIn = channelsIn; + config.sampleRateIn = sampleRateIn; + config.formatOut = formatOut; + config.channelsOut = channelsOut; + config.sampleRateOut = sampleRateOut; + if (channelMapIn != NULL) { + ma_copy_memory(config.channelMapIn, channelMapIn, sizeof(config.channelMapIn)); + } + if (channelMapOut != NULL) { + ma_copy_memory(config.channelMapOut, channelMapOut, sizeof(config.channelMapOut)); + } + config.onRead = onRead; + config.pUserData = pUserData; + + return config; +} + + + +ma_uint64 ma_convert_frames(void* pOut, ma_format formatOut, ma_uint32 channelsOut, ma_uint32 sampleRateOut, const void* pIn, ma_format formatIn, ma_uint32 channelsIn, ma_uint32 sampleRateIn, ma_uint64 frameCount) +{ + ma_channel channelMapOut[MA_MAX_CHANNELS]; + ma_channel channelMapIn[MA_MAX_CHANNELS]; + + ma_get_standard_channel_map(ma_standard_channel_map_default, channelsOut, channelMapOut); + ma_get_standard_channel_map(ma_standard_channel_map_default, channelsIn, channelMapIn); + + return ma_convert_frames_ex(pOut, formatOut, channelsOut, sampleRateOut, channelMapOut, pIn, formatIn, channelsIn, sampleRateIn, channelMapIn, frameCount); +} + +ma_uint64 ma_convert_frames_ex(void* pOut, ma_format formatOut, ma_uint32 channelsOut, ma_uint32 sampleRateOut, ma_channel channelMapOut[MA_MAX_CHANNELS], const void* pIn, ma_format formatIn, ma_uint32 channelsIn, ma_uint32 sampleRateIn, ma_channel channelMapIn[MA_MAX_CHANNELS], ma_uint64 frameCount) +{ + ma_uint64 frameCountOut; + ma_convert_frames__data data; + ma_pcm_converter_config converterConfig; + ma_pcm_converter converter; + ma_uint64 totalFramesRead; + + if (frameCount == 0) { + return 0; + } + + frameCountOut = ma_calculate_frame_count_after_src(sampleRateOut, sampleRateIn, frameCount); + if (pOut == NULL) { + return frameCountOut; + } + + data.pDataIn = pIn; + data.formatIn = formatIn; + data.channelsIn = channelsIn; + data.totalFrameCount = frameCount; + data.iNextFrame = 0; + data.isFeedingZeros = MA_FALSE; + + ma_zero_object(&converterConfig); + + converterConfig.formatIn = formatIn; + converterConfig.channelsIn = channelsIn; + converterConfig.sampleRateIn = sampleRateIn; + if (channelMapIn != NULL) { + ma_channel_map_copy(converterConfig.channelMapIn, channelMapIn, channelsIn); + } else { + ma_get_standard_channel_map(ma_standard_channel_map_default, converterConfig.channelsIn, converterConfig.channelMapIn); + } + + converterConfig.formatOut = formatOut; + converterConfig.channelsOut = channelsOut; + converterConfig.sampleRateOut = sampleRateOut; + if (channelMapOut != NULL) { + ma_channel_map_copy(converterConfig.channelMapOut, channelMapOut, channelsOut); + } else { + ma_get_standard_channel_map(ma_standard_channel_map_default, converterConfig.channelsOut, converterConfig.channelMapOut); + } + + converterConfig.onRead = ma_convert_frames__on_read; + converterConfig.pUserData = &data; + + if (ma_pcm_converter_init(&converterConfig, &converter) != MA_SUCCESS) { + return 0; + } + + /* + Always output our computed frame count. There is a chance the sample rate conversion routine may not output the last sample + due to precision issues with 32-bit floats, in which case we should feed the DSP zero samples so it can generate that last + frame. + */ + totalFramesRead = ma_pcm_converter_read(&converter, pOut, frameCountOut); + if (totalFramesRead < frameCountOut) { + ma_uint32 bpfOut = ma_get_bytes_per_frame(formatOut, channelsOut); + + data.isFeedingZeros = MA_TRUE; + data.totalFrameCount = ((ma_uint64)0xFFFFFFFF << 32) | 0xFFFFFFFF; /* C89 does not support 64-bit constants so need to instead construct it like this. Annoying... */ /*data.totalFrameCount = 0xFFFFFFFFFFFFFFFF;*/ + data.pDataIn = NULL; + + while (totalFramesRead < frameCountOut) { + ma_uint64 framesToRead; + ma_uint64 framesJustRead; + + framesToRead = (frameCountOut - totalFramesRead); + ma_assert(framesToRead > 0); + + framesJustRead = ma_pcm_converter_read(&converter, ma_offset_ptr(pOut, totalFramesRead * bpfOut), framesToRead); + totalFramesRead += framesJustRead; + + if (framesJustRead < framesToRead) { + break; + } + } + + /* At this point we should have output every sample, but just to be super duper sure, just fill the rest with zeros. */ + if (totalFramesRead < frameCountOut) { + ma_zero_memory_64(ma_offset_ptr(pOut, totalFramesRead * bpfOut), ((frameCountOut - totalFramesRead) * bpfOut)); + totalFramesRead = frameCountOut; + } + } + + ma_assert(totalFramesRead == frameCountOut); + return totalFramesRead; +} + + +/************************************************************************************************************************************************************** + +Ring Buffer + +**************************************************************************************************************************************************************/ +MA_INLINE ma_uint32 ma_rb__extract_offset_in_bytes(ma_uint32 encodedOffset) +{ + return encodedOffset & 0x7FFFFFFF; +} + +MA_INLINE ma_uint32 ma_rb__extract_offset_loop_flag(ma_uint32 encodedOffset) +{ + return encodedOffset & 0x80000000; +} + +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)); +} + +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)); +} + +MA_INLINE ma_uint32 ma_rb__construct_offset(ma_uint32 offsetInBytes, ma_uint32 offsetLoopFlag) +{ + return offsetLoopFlag | offsetInBytes; +} + +MA_INLINE void ma_rb__deconstruct_offset(ma_uint32 encodedOffset, ma_uint32* pOffsetInBytes, ma_uint32* pOffsetLoopFlag) +{ + ma_assert(pOffsetInBytes != NULL); + ma_assert(pOffsetLoopFlag != NULL); + + *pOffsetInBytes = ma_rb__extract_offset_in_bytes(encodedOffset); + *pOffsetLoopFlag = ma_rb__extract_offset_loop_flag(encodedOffset); +} + + +ma_result ma_rb_init_ex(size_t subbufferSizeInBytes, size_t subbufferCount, size_t subbufferStrideInBytes, void* pOptionalPreallocatedBuffer, ma_rb* pRB) +{ + const ma_uint32 maxSubBufferSize = 0x7FFFFFFF - (MA_SIMD_ALIGNMENT-1); + + if (pRB == NULL) { + return MA_INVALID_ARGS; + } + + if (subbufferSizeInBytes == 0 || subbufferCount == 0) { + return MA_INVALID_ARGS; + } + + if (subbufferSizeInBytes > maxSubBufferSize) { + return MA_INVALID_ARGS; /* Maximum buffer size is ~2GB. The most significant bit is a flag for use internally. */ + } + + + ma_zero_object(pRB); + pRB->subbufferSizeInBytes = (ma_uint32)subbufferSizeInBytes; + pRB->subbufferCount = (ma_uint32)subbufferCount; + + if (pOptionalPreallocatedBuffer != NULL) { + pRB->subbufferStrideInBytes = (ma_uint32)subbufferStrideInBytes; + pRB->pBuffer = pOptionalPreallocatedBuffer; + } else { + size_t bufferSizeInBytes; + + /* + Here is where we allocate our own buffer. We always want to align this to MA_SIMD_ALIGNMENT for future SIMD optimization opportunity. To do this + we need to make sure the stride is a multiple of MA_SIMD_ALIGNMENT. + */ + pRB->subbufferStrideInBytes = (pRB->subbufferSizeInBytes + (MA_SIMD_ALIGNMENT-1)) & ~MA_SIMD_ALIGNMENT; + + bufferSizeInBytes = (size_t)pRB->subbufferCount*pRB->subbufferStrideInBytes; + pRB->pBuffer = ma_aligned_malloc(bufferSizeInBytes, MA_SIMD_ALIGNMENT); + if (pRB->pBuffer == NULL) { + return MA_OUT_OF_MEMORY; + } + + ma_zero_memory(pRB->pBuffer, bufferSizeInBytes); + pRB->ownsBuffer = MA_TRUE; + } + + return MA_SUCCESS; +} + +ma_result ma_rb_init(size_t bufferSizeInBytes, void* pOptionalPreallocatedBuffer, ma_rb* pRB) +{ + return ma_rb_init_ex(bufferSizeInBytes, 1, 0, pOptionalPreallocatedBuffer, pRB); +} + +void ma_rb_uninit(ma_rb* pRB) +{ + if (pRB == NULL) { + return; + } + + if (pRB->ownsBuffer) { + ma_aligned_free(pRB->pBuffer); + } +} + +ma_result ma_rb_acquire_read(ma_rb* pRB, size_t* pSizeInBytes, void** ppBufferOut) +{ + ma_uint32 writeOffset; + ma_uint32 writeOffsetInBytes; + ma_uint32 writeOffsetLoopFlag; + ma_uint32 readOffset; + ma_uint32 readOffsetInBytes; + ma_uint32 readOffsetLoopFlag; + size_t bytesAvailable; + size_t bytesRequested; + + if (pRB == NULL || pSizeInBytes == NULL || ppBufferOut == NULL) { + return MA_INVALID_ARGS; + } + + /* The returned buffer should never move ahead of the write pointer. */ + writeOffset = pRB->encodedWriteOffset; + ma_rb__deconstruct_offset(writeOffset, &writeOffsetInBytes, &writeOffsetLoopFlag); + + readOffset = pRB->encodedReadOffset; + ma_rb__deconstruct_offset(readOffset, &readOffsetInBytes, &readOffsetLoopFlag); + + /* + The number of bytes available depends on whether or not the read and write pointers are on the same loop iteration. If so, we + can only read up to the write pointer. If not, we can only read up to the end of the buffer. + */ + if (readOffsetLoopFlag == writeOffsetLoopFlag) { + bytesAvailable = writeOffsetInBytes - readOffsetInBytes; + } else { + bytesAvailable = pRB->subbufferSizeInBytes - readOffsetInBytes; + } + + bytesRequested = *pSizeInBytes; + if (bytesRequested > bytesAvailable) { + bytesRequested = bytesAvailable; + } + + *pSizeInBytes = bytesRequested; + (*ppBufferOut) = ma_rb__get_read_ptr(pRB); + + return MA_SUCCESS; +} + +ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes, void* pBufferOut) +{ + ma_uint32 readOffset; + ma_uint32 readOffsetInBytes; + ma_uint32 readOffsetLoopFlag; + ma_uint32 newReadOffsetInBytes; + ma_uint32 newReadOffsetLoopFlag; + + if (pRB == NULL) { + return MA_INVALID_ARGS; + } + + /* Validate the buffer. */ + if (pBufferOut != ma_rb__get_read_ptr(pRB)) { + return MA_INVALID_ARGS; + } + + readOffset = pRB->encodedReadOffset; + ma_rb__deconstruct_offset(readOffset, &readOffsetInBytes, &readOffsetLoopFlag); + + /* Check that sizeInBytes is correct. It should never go beyond the end of the buffer. */ + newReadOffsetInBytes = (ma_uint32)(readOffsetInBytes + sizeInBytes); + if (newReadOffsetInBytes > pRB->subbufferSizeInBytes) { + return MA_INVALID_ARGS; /* <-- sizeInBytes will cause the read offset to overflow. */ + } + + /* Move the read pointer back to the start if necessary. */ + newReadOffsetLoopFlag = readOffsetLoopFlag; + if (newReadOffsetInBytes == pRB->subbufferSizeInBytes) { + newReadOffsetInBytes = 0; + newReadOffsetLoopFlag ^= 0x80000000; + } + + ma_atomic_exchange_32(&pRB->encodedReadOffset, ma_rb__construct_offset(newReadOffsetLoopFlag, newReadOffsetInBytes)); + return MA_SUCCESS; +} + +ma_result ma_rb_acquire_write(ma_rb* pRB, size_t* pSizeInBytes, void** ppBufferOut) +{ + ma_uint32 readOffset; + ma_uint32 readOffsetInBytes; + ma_uint32 readOffsetLoopFlag; + ma_uint32 writeOffset; + ma_uint32 writeOffsetInBytes; + ma_uint32 writeOffsetLoopFlag; + size_t bytesAvailable; + size_t bytesRequested; + + if (pRB == NULL || pSizeInBytes == NULL || ppBufferOut == NULL) { + return MA_INVALID_ARGS; + } + + /* The returned buffer should never overtake the read buffer. */ + readOffset = pRB->encodedReadOffset; + ma_rb__deconstruct_offset(readOffset, &readOffsetInBytes, &readOffsetLoopFlag); + + writeOffset = pRB->encodedWriteOffset; + ma_rb__deconstruct_offset(writeOffset, &writeOffsetInBytes, &writeOffsetLoopFlag); + + /* + In the case of writing, if the write pointer and the read pointer are on the same loop iteration we can only + write up to the end of the buffer. Otherwise we can only write up to the read pointer. The write pointer should + never overtake the read pointer. + */ + if (writeOffsetLoopFlag == readOffsetLoopFlag) { + bytesAvailable = pRB->subbufferSizeInBytes - writeOffsetInBytes; + } else { + bytesAvailable = readOffsetInBytes - writeOffsetInBytes; + } + + bytesRequested = *pSizeInBytes; + if (bytesRequested > bytesAvailable) { + bytesRequested = bytesAvailable; + } + + *pSizeInBytes = bytesRequested; + *ppBufferOut = ma_rb__get_write_ptr(pRB); + + /* Clear the buffer if desired. */ + if (pRB->clearOnWriteAcquire) { + ma_zero_memory(*ppBufferOut, *pSizeInBytes); + } + + return MA_SUCCESS; +} + +ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes, void* pBufferOut) +{ + ma_uint32 writeOffset; + ma_uint32 writeOffsetInBytes; + ma_uint32 writeOffsetLoopFlag; + ma_uint32 newWriteOffsetInBytes; + ma_uint32 newWriteOffsetLoopFlag; + + if (pRB == NULL) { + return MA_INVALID_ARGS; + } + + /* Validate the buffer. */ + if (pBufferOut != ma_rb__get_write_ptr(pRB)) { + return MA_INVALID_ARGS; + } + + writeOffset = pRB->encodedWriteOffset; + ma_rb__deconstruct_offset(writeOffset, &writeOffsetInBytes, &writeOffsetLoopFlag); + + /* Check that sizeInBytes is correct. It should never go beyond the end of the buffer. */ + newWriteOffsetInBytes = (ma_uint32)(writeOffsetInBytes + sizeInBytes); + if (newWriteOffsetInBytes > pRB->subbufferSizeInBytes) { + return MA_INVALID_ARGS; /* <-- sizeInBytes will cause the read offset to overflow. */ + } + + /* Move the read pointer back to the start if necessary. */ + newWriteOffsetLoopFlag = writeOffsetLoopFlag; + if (newWriteOffsetInBytes == pRB->subbufferSizeInBytes) { + newWriteOffsetInBytes = 0; + newWriteOffsetLoopFlag ^= 0x80000000; + } + + ma_atomic_exchange_32(&pRB->encodedWriteOffset, ma_rb__construct_offset(newWriteOffsetLoopFlag, newWriteOffsetInBytes)); + return MA_SUCCESS; +} + +ma_result ma_rb_seek_read(ma_rb* pRB, size_t offsetInBytes) +{ + ma_uint32 readOffset; + ma_uint32 readOffsetInBytes; + ma_uint32 readOffsetLoopFlag; + ma_uint32 writeOffset; + ma_uint32 writeOffsetInBytes; + ma_uint32 writeOffsetLoopFlag; + ma_uint32 newReadOffsetInBytes; + ma_uint32 newReadOffsetLoopFlag; + + if (pRB == NULL || offsetInBytes > pRB->subbufferSizeInBytes) { + return MA_INVALID_ARGS; + } + + readOffset = pRB->encodedReadOffset; + ma_rb__deconstruct_offset(readOffset, &readOffsetInBytes, &readOffsetLoopFlag); + + writeOffset = pRB->encodedWriteOffset; + ma_rb__deconstruct_offset(writeOffset, &writeOffsetInBytes, &writeOffsetLoopFlag); + + newReadOffsetInBytes = readOffsetInBytes; + newReadOffsetLoopFlag = readOffsetLoopFlag; + + /* We cannot go past the write buffer. */ + if (readOffsetLoopFlag == writeOffsetLoopFlag) { + if ((readOffsetInBytes + offsetInBytes) > writeOffsetInBytes) { + newReadOffsetInBytes = writeOffsetInBytes; + } else { + newReadOffsetInBytes = (ma_uint32)(readOffsetInBytes + offsetInBytes); + } + } else { + /* May end up looping. */ + if ((readOffsetInBytes + offsetInBytes) >= pRB->subbufferSizeInBytes) { + newReadOffsetInBytes = (ma_uint32)(readOffsetInBytes + offsetInBytes) - pRB->subbufferSizeInBytes; + newReadOffsetLoopFlag ^= 0x80000000; /* <-- Looped. */ + } else { + newReadOffsetInBytes = (ma_uint32)(readOffsetInBytes + offsetInBytes); + } + } + + ma_atomic_exchange_32(&pRB->encodedReadOffset, ma_rb__construct_offset(newReadOffsetInBytes, newReadOffsetLoopFlag)); + return MA_SUCCESS; +} + +ma_result ma_rb_seek_write(ma_rb* pRB, size_t offsetInBytes) +{ + ma_uint32 readOffset; + ma_uint32 readOffsetInBytes; + ma_uint32 readOffsetLoopFlag; + ma_uint32 writeOffset; + ma_uint32 writeOffsetInBytes; + ma_uint32 writeOffsetLoopFlag; + ma_uint32 newWriteOffsetInBytes; + ma_uint32 newWriteOffsetLoopFlag; + + if (pRB == NULL) { + return MA_INVALID_ARGS; + } + + readOffset = pRB->encodedReadOffset; + ma_rb__deconstruct_offset(readOffset, &readOffsetInBytes, &readOffsetLoopFlag); + + writeOffset = pRB->encodedWriteOffset; + ma_rb__deconstruct_offset(writeOffset, &writeOffsetInBytes, &writeOffsetLoopFlag); + + newWriteOffsetInBytes = writeOffsetInBytes; + newWriteOffsetLoopFlag = writeOffsetLoopFlag; + + /* We cannot go past the write buffer. */ + if (readOffsetLoopFlag == writeOffsetLoopFlag) { + /* May end up looping. */ + if ((writeOffsetInBytes + offsetInBytes) >= pRB->subbufferSizeInBytes) { + newWriteOffsetInBytes = (ma_uint32)(writeOffsetInBytes + offsetInBytes) - pRB->subbufferSizeInBytes; + newWriteOffsetLoopFlag ^= 0x80000000; /* <-- Looped. */ + } else { + newWriteOffsetInBytes = (ma_uint32)(writeOffsetInBytes + offsetInBytes); + } + } else { + if ((writeOffsetInBytes + offsetInBytes) > readOffsetInBytes) { + newWriteOffsetInBytes = readOffsetInBytes; + } else { + newWriteOffsetInBytes = (ma_uint32)(writeOffsetInBytes + offsetInBytes); + } + } + + ma_atomic_exchange_32(&pRB->encodedWriteOffset, ma_rb__construct_offset(newWriteOffsetInBytes, newWriteOffsetLoopFlag)); + return MA_SUCCESS; +} + +ma_int32 ma_rb_pointer_distance(ma_rb* pRB) +{ + ma_uint32 readOffset; + ma_uint32 readOffsetInBytes; + ma_uint32 readOffsetLoopFlag; + ma_uint32 writeOffset; + ma_uint32 writeOffsetInBytes; + ma_uint32 writeOffsetLoopFlag; + + if (pRB == NULL) { + return 0; + } + + readOffset = pRB->encodedReadOffset; + ma_rb__deconstruct_offset(readOffset, &readOffsetInBytes, &readOffsetLoopFlag); + + writeOffset = pRB->encodedWriteOffset; + ma_rb__deconstruct_offset(writeOffset, &writeOffsetInBytes, &writeOffsetLoopFlag); + + if (readOffsetLoopFlag == writeOffsetLoopFlag) { + return writeOffsetInBytes - readOffsetInBytes; + } else { + return writeOffsetInBytes + (pRB->subbufferSizeInBytes - readOffsetInBytes); + } +} + +size_t ma_rb_get_subbuffer_size(ma_rb* pRB) +{ + if (pRB == NULL) { + return 0; + } + + return pRB->subbufferSizeInBytes; +} + +size_t ma_rb_get_subbuffer_stride(ma_rb* pRB) +{ + if (pRB == NULL) { + return 0; + } + + if (pRB->subbufferStrideInBytes == 0) { + return (size_t)pRB->subbufferSizeInBytes; + } + + return (size_t)pRB->subbufferStrideInBytes; +} + +size_t ma_rb_get_subbuffer_offset(ma_rb* pRB, size_t subbufferIndex) +{ + if (pRB == NULL) { + return 0; + } + + return subbufferIndex * ma_rb_get_subbuffer_stride(pRB); +} + +void* ma_rb_get_subbuffer_ptr(ma_rb* pRB, size_t subbufferIndex, void* pBuffer) +{ + if (pRB == NULL) { + return NULL; + } + + return ma_offset_ptr(pBuffer, ma_rb_get_subbuffer_offset(pRB, subbufferIndex)); +} + + +static MA_INLINE ma_uint32 ma_pcm_rb_get_bpf(ma_pcm_rb* pRB) +{ + ma_assert(pRB != NULL); + + return ma_get_bytes_per_frame(pRB->format, pRB->channels); +} + +ma_result ma_pcm_rb_init_ex(ma_format format, ma_uint32 channels, ma_uint32 subbufferSizeInFrames, ma_uint32 subbufferCount, ma_uint32 subbufferStrideInFrames, void* pOptionalPreallocatedBuffer, ma_pcm_rb* pRB) +{ + ma_uint32 bpf; + ma_result result; + + if (pRB == NULL) { + return MA_INVALID_ARGS; + } + + ma_zero_object(pRB); + + bpf = ma_get_bytes_per_frame(format, channels); + if (bpf == 0) { + return MA_INVALID_ARGS; + } + + result = ma_rb_init_ex(subbufferSizeInFrames*bpf, subbufferCount, subbufferStrideInFrames*bpf, pOptionalPreallocatedBuffer, &pRB->rb); + if (result != MA_SUCCESS) { + return result; + } + + pRB->format = format; + pRB->channels = channels; + + return MA_SUCCESS; +} + +ma_result ma_pcm_rb_init(ma_format format, ma_uint32 channels, ma_uint32 bufferSizeInFrames, void* pOptionalPreallocatedBuffer, ma_pcm_rb* pRB) +{ + return ma_pcm_rb_init_ex(format, channels, bufferSizeInFrames, 1, 0, pOptionalPreallocatedBuffer, pRB); +} + +void ma_pcm_rb_uninit(ma_pcm_rb* pRB) +{ + if (pRB == NULL) { + return; + } + + ma_rb_uninit(&pRB->rb); +} + +ma_result ma_pcm_rb_acquire_read(ma_pcm_rb* pRB, ma_uint32* pSizeInFrames, void** ppBufferOut) +{ + size_t sizeInBytes; + ma_result result; + + if (pRB == NULL || pSizeInFrames == NULL) { + return MA_INVALID_ARGS; + } + + sizeInBytes = *pSizeInFrames * ma_pcm_rb_get_bpf(pRB); + + result = ma_rb_acquire_read(&pRB->rb, &sizeInBytes, ppBufferOut); + if (result != MA_SUCCESS) { + return result; + } + + *pSizeInFrames = (ma_uint32)(sizeInBytes / (size_t)ma_pcm_rb_get_bpf(pRB)); + return MA_SUCCESS; +} + +ma_result ma_pcm_rb_commit_read(ma_pcm_rb* pRB, ma_uint32 sizeInFrames, void* pBufferOut) +{ + if (pRB == NULL) { + return MA_INVALID_ARGS; + } + + return ma_rb_commit_read(&pRB->rb, sizeInFrames * ma_pcm_rb_get_bpf(pRB), pBufferOut); +} + +ma_result ma_pcm_rb_acquire_write(ma_pcm_rb* pRB, ma_uint32* pSizeInFrames, void** ppBufferOut) +{ + size_t sizeInBytes; + ma_result result; + + if (pRB == NULL) { + return MA_INVALID_ARGS; + } + + sizeInBytes = *pSizeInFrames * ma_pcm_rb_get_bpf(pRB); + + result = ma_rb_acquire_write(&pRB->rb, &sizeInBytes, ppBufferOut); + if (result != MA_SUCCESS) { + return result; + } + + *pSizeInFrames = (ma_uint32)(sizeInBytes / ma_pcm_rb_get_bpf(pRB)); + return MA_SUCCESS; +} + +ma_result ma_pcm_rb_commit_write(ma_pcm_rb* pRB, ma_uint32 sizeInFrames, void* pBufferOut) +{ + if (pRB == NULL) { + return MA_INVALID_ARGS; + } + + return ma_rb_commit_write(&pRB->rb, sizeInFrames * ma_pcm_rb_get_bpf(pRB), pBufferOut); +} + +ma_result ma_pcm_rb_seek_read(ma_pcm_rb* pRB, ma_uint32 offsetInFrames) +{ + if (pRB == NULL) { + return MA_INVALID_ARGS; + } + + return ma_rb_seek_read(&pRB->rb, offsetInFrames * ma_pcm_rb_get_bpf(pRB)); +} + +ma_result ma_pcm_rb_seek_write(ma_pcm_rb* pRB, ma_uint32 offsetInFrames) +{ + if (pRB == NULL) { + return MA_INVALID_ARGS; + } + + return ma_rb_seek_write(&pRB->rb, offsetInFrames * ma_pcm_rb_get_bpf(pRB)); +} + +ma_int32 ma_pcm_rb_pointer_disance(ma_pcm_rb* pRB) +{ + if (pRB == NULL) { + return MA_INVALID_ARGS; + } + + return ma_rb_pointer_distance(&pRB->rb) / ma_pcm_rb_get_bpf(pRB); +} + +ma_uint32 ma_pcm_rb_get_subbuffer_size(ma_pcm_rb* pRB) +{ + if (pRB == NULL) { + return 0; + } + + return (ma_uint32)(ma_rb_get_subbuffer_size(&pRB->rb) / ma_pcm_rb_get_bpf(pRB)); +} + +ma_uint32 ma_pcm_rb_get_subbuffer_stride(ma_pcm_rb* pRB) +{ + if (pRB == NULL) { + return 0; + } + + return (ma_uint32)(ma_rb_get_subbuffer_stride(&pRB->rb) / ma_pcm_rb_get_bpf(pRB)); +} + +ma_uint32 ma_pcm_rb_get_subbuffer_offset(ma_pcm_rb* pRB, ma_uint32 subbufferIndex) +{ + if (pRB == NULL) { + return 0; + } + + return (ma_uint32)(ma_rb_get_subbuffer_offset(&pRB->rb, subbufferIndex) / ma_pcm_rb_get_bpf(pRB)); +} + +void* ma_pcm_rb_get_subbuffer_ptr(ma_pcm_rb* pRB, ma_uint32 subbufferIndex, void* pBuffer) +{ + if (pRB == NULL) { + return NULL; + } + + return ma_rb_get_subbuffer_ptr(&pRB->rb, subbufferIndex, pBuffer); +} + + + +/************************************************************************************************************************************************************** + +Miscellaneous Helpers + +**************************************************************************************************************************************************************/ +void* ma_malloc(size_t sz) +{ + return MA_MALLOC(sz); +} + +void* ma_realloc(void* p, size_t sz) +{ + return MA_REALLOC(p, sz); +} + +void ma_free(void* p) +{ + MA_FREE(p); +} + +void* ma_aligned_malloc(size_t sz, size_t alignment) +{ + size_t extraBytes; + void* pUnaligned; + void* pAligned; + + if (alignment == 0) { + return 0; + } + + extraBytes = alignment-1 + sizeof(void*); + + pUnaligned = ma_malloc(sz + extraBytes); + if (pUnaligned == NULL) { + return NULL; + } + + pAligned = (void*)(((ma_uintptr)pUnaligned + extraBytes) & ~((ma_uintptr)(alignment-1))); + ((void**)pAligned)[-1] = pUnaligned; + + return pAligned; +} + +void ma_aligned_free(void* p) +{ + ma_free(((void**)p)[-1]); +} + +const char* ma_get_format_name(ma_format format) +{ + switch (format) + { + case ma_format_unknown: return "Unknown"; + case ma_format_u8: return "8-bit Unsigned Integer"; + case ma_format_s16: return "16-bit Signed Integer"; + case ma_format_s24: return "24-bit Signed Integer (Tightly Packed)"; + case ma_format_s32: return "32-bit Signed Integer"; + case ma_format_f32: return "32-bit IEEE Floating Point"; + default: return "Invalid"; + } +} + +void ma_blend_f32(float* pOut, float* pInA, float* pInB, float factor, ma_uint32 channels) +{ + ma_uint32 i; + for (i = 0; i < channels; ++i) { + pOut[i] = ma_mix_f32(pInA[i], pInB[i], factor); + } +} + + +ma_uint32 ma_get_bytes_per_sample(ma_format format) +{ + ma_uint32 sizes[] = { + 0, /* unknown */ + 1, /* u8 */ + 2, /* s16 */ + 3, /* s24 */ + 4, /* s32 */ + 4, /* f32 */ + }; + return sizes[format]; +} + + +/************************************************************************************************************************************************************** + +Decoding + +**************************************************************************************************************************************************************/ +#ifndef MA_NO_DECODING + +size_t ma_decoder_read_bytes(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead) +{ + size_t bytesRead; + + ma_assert(pDecoder != NULL); + ma_assert(pBufferOut != NULL); + + bytesRead = pDecoder->onRead(pDecoder, pBufferOut, bytesToRead); + pDecoder->readPointer += bytesRead; + + return bytesRead; +} + +ma_bool32 ma_decoder_seek_bytes(ma_decoder* pDecoder, int byteOffset, ma_seek_origin origin) +{ + ma_bool32 wasSuccessful; + + ma_assert(pDecoder != NULL); + + wasSuccessful = pDecoder->onSeek(pDecoder, byteOffset, origin); + if (wasSuccessful) { + if (origin == ma_seek_origin_start) { + pDecoder->readPointer = (ma_uint64)byteOffset; + } else { + pDecoder->readPointer += byteOffset; + } + } + + return wasSuccessful; +} + +ma_bool32 ma_decoder_seek_bytes_64(ma_decoder* pDecoder, ma_uint64 byteOffset, ma_seek_origin origin) +{ + ma_assert(pDecoder != NULL); + + if (origin == ma_seek_origin_start) { + ma_uint64 bytesToSeekThisIteration = 0x7FFFFFFF; + if (bytesToSeekThisIteration > byteOffset) { + bytesToSeekThisIteration = byteOffset; + } + + if (!ma_decoder_seek_bytes(pDecoder, (int)bytesToSeekThisIteration, ma_seek_origin_start)) { + return MA_FALSE; + } + + byteOffset -= bytesToSeekThisIteration; + } + + /* Getting here means we need to seek relative to the current position. */ + while (byteOffset > 0) { + ma_uint64 bytesToSeekThisIteration = 0x7FFFFFFF; + if (bytesToSeekThisIteration > byteOffset) { + bytesToSeekThisIteration = byteOffset; + } + + if (!ma_decoder_seek_bytes(pDecoder, (int)bytesToSeekThisIteration, ma_seek_origin_current)) { + return MA_FALSE; + } + + byteOffset -= bytesToSeekThisIteration; + } + + return MA_TRUE; +} + + +ma_decoder_config ma_decoder_config_init(ma_format outputFormat, ma_uint32 outputChannels, ma_uint32 outputSampleRate) +{ + ma_decoder_config config; + ma_zero_object(&config); + config.format = outputFormat; + config.channels = outputChannels; + config.sampleRate = outputSampleRate; + ma_get_standard_channel_map(ma_standard_channel_map_default, config.channels, config.channelMap); + + return config; +} + +ma_decoder_config ma_decoder_config_init_copy(const ma_decoder_config* pConfig) +{ + ma_decoder_config config; + if (pConfig != NULL) { + config = *pConfig; + } else { + ma_zero_object(&config); + } + + return config; +} + +ma_result ma_decoder__init_dsp(ma_decoder* pDecoder, const ma_decoder_config* pConfig, ma_pcm_converter_read_proc onRead) +{ + ma_pcm_converter_config dspConfig; + + ma_assert(pDecoder != NULL); + + /* Output format. */ + if (pConfig->format == ma_format_unknown) { + pDecoder->outputFormat = pDecoder->internalFormat; + } else { + pDecoder->outputFormat = pConfig->format; + } + + if (pConfig->channels == 0) { + pDecoder->outputChannels = pDecoder->internalChannels; + } else { + pDecoder->outputChannels = pConfig->channels; + } + + if (pConfig->sampleRate == 0) { + pDecoder->outputSampleRate = pDecoder->internalSampleRate; + } else { + pDecoder->outputSampleRate = pConfig->sampleRate; + } + + if (ma_channel_map_blank(pDecoder->outputChannels, pConfig->channelMap)) { + ma_get_standard_channel_map(ma_standard_channel_map_default, pDecoder->outputChannels, pDecoder->outputChannelMap); + } else { + ma_copy_memory(pDecoder->outputChannelMap, pConfig->channelMap, sizeof(pConfig->channelMap)); + } + + + /* DSP. */ + dspConfig = ma_pcm_converter_config_init_ex( + pDecoder->internalFormat, pDecoder->internalChannels, pDecoder->internalSampleRate, pDecoder->internalChannelMap, + pDecoder->outputFormat, pDecoder->outputChannels, pDecoder->outputSampleRate, pDecoder->outputChannelMap, + onRead, pDecoder); + dspConfig.channelMixMode = pConfig->channelMixMode; + dspConfig.ditherMode = pConfig->ditherMode; + dspConfig.srcAlgorithm = pConfig->srcAlgorithm; + dspConfig.sinc = pConfig->src.sinc; + + return ma_pcm_converter_init(&dspConfig, &pDecoder->dsp); +} + +/* WAV */ +#ifdef dr_wav_h +#define MA_HAS_WAV + +size_t ma_decoder_internal_on_read__wav(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + ma_decoder* pDecoder = (ma_decoder*)pUserData; + ma_assert(pDecoder != NULL); + + return ma_decoder_read_bytes(pDecoder, pBufferOut, bytesToRead); +} + +drwav_bool32 ma_decoder_internal_on_seek__wav(void* pUserData, int offset, drwav_seek_origin origin) +{ + ma_decoder* pDecoder = (ma_decoder*)pUserData; + ma_assert(pDecoder != NULL); + + return ma_decoder_seek_bytes(pDecoder, offset, (origin == drwav_seek_origin_start) ? ma_seek_origin_start : ma_seek_origin_current); +} + +ma_uint32 ma_decoder_internal_on_read_pcm_frames__wav(ma_pcm_converter* pDSP, void* pSamplesOut, ma_uint32 frameCount, void* pUserData) +{ + ma_decoder* pDecoder; + drwav* pWav; + + (void)pDSP; + + pDecoder = (ma_decoder*)pUserData; + ma_assert(pDecoder != NULL); + + pWav = (drwav*)pDecoder->pInternalDecoder; + ma_assert(pWav != NULL); + + switch (pDecoder->internalFormat) { + case ma_format_s16: return (ma_uint32)drwav_read_pcm_frames_s16(pWav, frameCount, (drwav_int16*)pSamplesOut); + case ma_format_s32: return (ma_uint32)drwav_read_pcm_frames_s32(pWav, frameCount, (drwav_int32*)pSamplesOut); + case ma_format_f32: return (ma_uint32)drwav_read_pcm_frames_f32(pWav, frameCount, (float*)pSamplesOut); + default: break; + } + + /* Should never get here. If we do, it means the internal format was not set correctly at initialization time. */ + ma_assert(MA_FALSE); + return 0; +} + +ma_result ma_decoder_internal_on_seek_to_pcm_frame__wav(ma_decoder* pDecoder, ma_uint64 frameIndex) +{ + drwav* pWav; + drwav_bool32 result; + + pWav = (drwav*)pDecoder->pInternalDecoder; + ma_assert(pWav != NULL); + + result = drwav_seek_to_pcm_frame(pWav, frameIndex); + if (result) { + return MA_SUCCESS; + } else { + return MA_ERROR; + } +} + +ma_result ma_decoder_internal_on_uninit__wav(ma_decoder* pDecoder) +{ + drwav_close((drwav*)pDecoder->pInternalDecoder); + return MA_SUCCESS; +} + +ma_uint64 ma_decoder_internal_on_get_length_in_pcm_frames__wav(ma_decoder* pDecoder) +{ + return ((drwav*)pDecoder->pInternalDecoder)->totalPCMFrameCount; +} + +ma_result ma_decoder_init_wav__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + drwav* pWav; + ma_result result; + + ma_assert(pConfig != NULL); + ma_assert(pDecoder != NULL); + + /* Try opening the decoder first. */ + pWav = drwav_open(ma_decoder_internal_on_read__wav, ma_decoder_internal_on_seek__wav, pDecoder); + if (pWav == NULL) { + return MA_ERROR; + } + + /* If we get here it means we successfully initialized the WAV decoder. We can now initialize the rest of the ma_decoder. */ + pDecoder->onSeekToPCMFrame = ma_decoder_internal_on_seek_to_pcm_frame__wav; + pDecoder->onUninit = ma_decoder_internal_on_uninit__wav; + pDecoder->onGetLengthInPCMFrames = ma_decoder_internal_on_get_length_in_pcm_frames__wav; + pDecoder->pInternalDecoder = pWav; + + /* Try to be as optimal as possible for the internal format. If miniaudio does not support a format we will fall back to f32. */ + pDecoder->internalFormat = ma_format_unknown; + switch (pWav->translatedFormatTag) { + case DR_WAVE_FORMAT_PCM: + { + if (pWav->bitsPerSample == 8) { + pDecoder->internalFormat = ma_format_s16; + } else if (pWav->bitsPerSample == 16) { + pDecoder->internalFormat = ma_format_s16; + } else if (pWav->bitsPerSample == 32) { + pDecoder->internalFormat = ma_format_s32; + } + } break; + + case DR_WAVE_FORMAT_IEEE_FLOAT: + { + if (pWav->bitsPerSample == 32) { + pDecoder->internalFormat = ma_format_f32; + } + } break; + + case DR_WAVE_FORMAT_ALAW: + case DR_WAVE_FORMAT_MULAW: + case DR_WAVE_FORMAT_ADPCM: + case DR_WAVE_FORMAT_DVI_ADPCM: + { + pDecoder->internalFormat = ma_format_s16; + } break; + } + + if (pDecoder->internalFormat == ma_format_unknown) { + pDecoder->internalFormat = ma_format_f32; + } + + pDecoder->internalChannels = pWav->channels; + pDecoder->internalSampleRate = pWav->sampleRate; + ma_get_standard_channel_map(ma_standard_channel_map_microsoft, pDecoder->internalChannels, pDecoder->internalChannelMap); + + result = ma_decoder__init_dsp(pDecoder, pConfig, ma_decoder_internal_on_read_pcm_frames__wav); + if (result != MA_SUCCESS) { + drwav_close(pWav); + return result; + } + + return MA_SUCCESS; +} +#endif + +/* FLAC */ +#ifdef dr_flac_h +#define MA_HAS_FLAC + +size_t ma_decoder_internal_on_read__flac(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + ma_decoder* pDecoder = (ma_decoder*)pUserData; + ma_assert(pDecoder != NULL); + + return ma_decoder_read_bytes(pDecoder, pBufferOut, bytesToRead); +} + +drflac_bool32 ma_decoder_internal_on_seek__flac(void* pUserData, int offset, drflac_seek_origin origin) +{ + ma_decoder* pDecoder = (ma_decoder*)pUserData; + ma_assert(pDecoder != NULL); + + return ma_decoder_seek_bytes(pDecoder, offset, (origin == drflac_seek_origin_start) ? ma_seek_origin_start : ma_seek_origin_current); +} + +ma_uint32 ma_decoder_internal_on_read_pcm_frames__flac(ma_pcm_converter* pDSP, void* pSamplesOut, ma_uint32 frameCount, void* pUserData) +{ + ma_decoder* pDecoder; + drflac* pFlac; + + (void)pDSP; + + pDecoder = (ma_decoder*)pUserData; + ma_assert(pDecoder != NULL); + + pFlac = (drflac*)pDecoder->pInternalDecoder; + ma_assert(pFlac != NULL); + + switch (pDecoder->internalFormat) { + case ma_format_s16: return (ma_uint32)drflac_read_pcm_frames_s16(pFlac, frameCount, (drflac_int16*)pSamplesOut); + case ma_format_s32: return (ma_uint32)drflac_read_pcm_frames_s32(pFlac, frameCount, (drflac_int32*)pSamplesOut); + case ma_format_f32: return (ma_uint32)drflac_read_pcm_frames_f32(pFlac, frameCount, (float*)pSamplesOut); + default: break; + } + + /* Should never get here. If we do, it means the internal format was not set correctly at initialization time. */ + ma_assert(MA_FALSE); + return 0; +} + +ma_result ma_decoder_internal_on_seek_to_pcm_frame__flac(ma_decoder* pDecoder, ma_uint64 frameIndex) +{ + drflac* pFlac; + drflac_bool32 result; + + pFlac = (drflac*)pDecoder->pInternalDecoder; + ma_assert(pFlac != NULL); + + result = drflac_seek_to_pcm_frame(pFlac, frameIndex); + if (result) { + return MA_SUCCESS; + } else { + return MA_ERROR; + } +} + +ma_result ma_decoder_internal_on_uninit__flac(ma_decoder* pDecoder) +{ + drflac_close((drflac*)pDecoder->pInternalDecoder); + return MA_SUCCESS; +} + +ma_uint64 ma_decoder_internal_on_get_length_in_pcm_frames__flac(ma_decoder* pDecoder) +{ + return ((drflac*)pDecoder->pInternalDecoder)->totalPCMFrameCount; +} + +ma_result ma_decoder_init_flac__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + drflac* pFlac; + ma_result result; + + ma_assert(pConfig != NULL); + ma_assert(pDecoder != NULL); + + /* Try opening the decoder first. */ + pFlac = drflac_open(ma_decoder_internal_on_read__flac, ma_decoder_internal_on_seek__flac, pDecoder); + if (pFlac == NULL) { + return MA_ERROR; + } + + /* If we get here it means we successfully initialized the FLAC decoder. We can now initialize the rest of the ma_decoder. */ + pDecoder->onSeekToPCMFrame = ma_decoder_internal_on_seek_to_pcm_frame__flac; + pDecoder->onUninit = ma_decoder_internal_on_uninit__flac; + pDecoder->onGetLengthInPCMFrames = ma_decoder_internal_on_get_length_in_pcm_frames__flac; + pDecoder->pInternalDecoder = pFlac; + + /* + dr_flac supports reading as s32, s16 and f32. Try to do a one-to-one mapping if possible, but fall back to s32 if not. s32 is the "native" FLAC format + since it's the only one that's truly lossless. + */ + pDecoder->internalFormat = ma_format_s32; + if (pConfig->format == ma_format_s16) { + pDecoder->internalFormat = ma_format_s16; + } else if (pConfig->format == ma_format_f32) { + pDecoder->internalFormat = ma_format_f32; + } + + pDecoder->internalChannels = pFlac->channels; + pDecoder->internalSampleRate = pFlac->sampleRate; + ma_get_standard_channel_map(ma_standard_channel_map_flac, pDecoder->internalChannels, pDecoder->internalChannelMap); + + result = ma_decoder__init_dsp(pDecoder, pConfig, ma_decoder_internal_on_read_pcm_frames__flac); + if (result != MA_SUCCESS) { + drflac_close(pFlac); + return result; + } + + return MA_SUCCESS; +} +#endif + +/* Vorbis */ +#ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H +#define MA_HAS_VORBIS + +/* The size in bytes of each chunk of data to read from the Vorbis stream. */ +#define MA_VORBIS_DATA_CHUNK_SIZE 4096 + +typedef struct +{ + stb_vorbis* pInternalVorbis; + ma_uint8* pData; + size_t dataSize; + size_t dataCapacity; + ma_uint32 framesConsumed; /* The number of frames consumed in ppPacketData. */ + ma_uint32 framesRemaining; /* The number of frames remaining in ppPacketData. */ + float** ppPacketData; +} ma_vorbis_decoder; + +ma_uint32 ma_vorbis_decoder_read_pcm_frames(ma_vorbis_decoder* pVorbis, ma_decoder* pDecoder, void* pSamplesOut, ma_uint32 frameCount) +{ + float* pSamplesOutF; + ma_uint32 totalFramesRead; + + ma_assert(pVorbis != NULL); + ma_assert(pDecoder != NULL); + + pSamplesOutF = (float*)pSamplesOut; + + totalFramesRead = 0; + while (frameCount > 0) { + /* Read from the in-memory buffer first. */ + while (pVorbis->framesRemaining > 0 && frameCount > 0) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < pDecoder->internalChannels; ++iChannel) { + pSamplesOutF[0] = pVorbis->ppPacketData[iChannel][pVorbis->framesConsumed]; + pSamplesOutF += 1; + } + + pVorbis->framesConsumed += 1; + pVorbis->framesRemaining -= 1; + frameCount -= 1; + totalFramesRead += 1; + } + + if (frameCount == 0) { + break; + } + + ma_assert(pVorbis->framesRemaining == 0); + + /* We've run out of cached frames, so decode the next packet and continue iteration. */ + do + { + int samplesRead; + int consumedDataSize; + + if (pVorbis->dataSize > INT_MAX) { + break; /* Too big. */ + } + + samplesRead = 0; + consumedDataSize = stb_vorbis_decode_frame_pushdata(pVorbis->pInternalVorbis, pVorbis->pData, (int)pVorbis->dataSize, NULL, (float***)&pVorbis->ppPacketData, &samplesRead); + if (consumedDataSize != 0) { + size_t leftoverDataSize = (pVorbis->dataSize - (size_t)consumedDataSize); + size_t i; + for (i = 0; i < leftoverDataSize; ++i) { + pVorbis->pData[i] = pVorbis->pData[i + consumedDataSize]; + } + + pVorbis->dataSize = leftoverDataSize; + pVorbis->framesConsumed = 0; + pVorbis->framesRemaining = samplesRead; + break; + } else { + /* Need more data. If there's any room in the existing buffer allocation fill that first. Otherwise expand. */ + size_t bytesRead; + if (pVorbis->dataCapacity == pVorbis->dataSize) { + /* No room. Expand. */ + size_t newCap = pVorbis->dataCapacity + MA_VORBIS_DATA_CHUNK_SIZE; + ma_uint8* pNewData; + + pNewData = (ma_uint8*)ma_realloc(pVorbis->pData, newCap); + if (pNewData == NULL) { + return totalFramesRead; /* Out of memory. */ + } + + pVorbis->pData = pNewData; + pVorbis->dataCapacity = newCap; + } + + /* Fill in a chunk. */ + bytesRead = ma_decoder_read_bytes(pDecoder, pVorbis->pData + pVorbis->dataSize, (pVorbis->dataCapacity - pVorbis->dataSize)); + if (bytesRead == 0) { + return totalFramesRead; /* Error reading more data. */ + } + + pVorbis->dataSize += bytesRead; + } + } while (MA_TRUE); + } + + return totalFramesRead; +} + +ma_result ma_vorbis_decoder_seek_to_pcm_frame(ma_vorbis_decoder* pVorbis, ma_decoder* pDecoder, ma_uint64 frameIndex) +{ + float buffer[4096]; + + ma_assert(pVorbis != NULL); + ma_assert(pDecoder != NULL); + + /* + This is terribly inefficient because stb_vorbis does not have a good seeking solution with it's push API. Currently this just performs + a full decode right from the start of the stream. Later on I'll need to write a layer that goes through all of the Ogg pages until we + find the one containing the sample we need. Then we know exactly where to seek for stb_vorbis. + */ + if (!ma_decoder_seek_bytes(pDecoder, 0, ma_seek_origin_start)) { + return MA_ERROR; + } + + stb_vorbis_flush_pushdata(pVorbis->pInternalVorbis); + pVorbis->framesConsumed = 0; + pVorbis->framesRemaining = 0; + pVorbis->dataSize = 0; + + while (frameIndex > 0) { + ma_uint32 framesRead; + ma_uint32 framesToRead = ma_countof(buffer)/pDecoder->internalChannels; + if (framesToRead > frameIndex) { + framesToRead = (ma_uint32)frameIndex; + } + + framesRead = ma_vorbis_decoder_read_pcm_frames(pVorbis, pDecoder, buffer, framesToRead); + if (framesRead == 0) { + return MA_ERROR; + } + + frameIndex -= framesRead; + } + + return MA_SUCCESS; +} + + +ma_result ma_decoder_internal_on_seek_to_pcm_frame__vorbis(ma_decoder* pDecoder, ma_uint64 frameIndex) +{ + ma_vorbis_decoder* pVorbis = (ma_vorbis_decoder*)pDecoder->pInternalDecoder; + ma_assert(pVorbis != NULL); + + return ma_vorbis_decoder_seek_to_pcm_frame(pVorbis, pDecoder, frameIndex); +} + +ma_result ma_decoder_internal_on_uninit__vorbis(ma_decoder* pDecoder) +{ + ma_vorbis_decoder* pVorbis = (ma_vorbis_decoder*)pDecoder->pInternalDecoder; + ma_assert(pVorbis != NULL); + + stb_vorbis_close(pVorbis->pInternalVorbis); + ma_free(pVorbis->pData); + ma_free(pVorbis); + + return MA_SUCCESS; +} + +ma_uint32 ma_decoder_internal_on_read_pcm_frames__vorbis(ma_pcm_converter* pDSP, void* pSamplesOut, ma_uint32 frameCount, void* pUserData) +{ + ma_decoder* pDecoder; + ma_vorbis_decoder* pVorbis; + + (void)pDSP; + + pDecoder = (ma_decoder*)pUserData; + ma_assert(pDecoder != NULL); + ma_assert(pDecoder->internalFormat == ma_format_f32); + + pVorbis = (ma_vorbis_decoder*)pDecoder->pInternalDecoder; + ma_assert(pVorbis != NULL); + + return ma_vorbis_decoder_read_pcm_frames(pVorbis, pDecoder, pSamplesOut, frameCount); +} + +ma_uint64 ma_decoder_internal_on_get_length_in_pcm_frames__vorbis(ma_decoder* pDecoder) +{ + /* No good way to do this with Vorbis. */ + (void)pDecoder; + return 0; +} + +ma_result ma_decoder_init_vorbis__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result; + stb_vorbis* pInternalVorbis = NULL; + size_t dataSize = 0; + size_t dataCapacity = 0; + ma_uint8* pData = NULL; + stb_vorbis_info vorbisInfo; + size_t vorbisDataSize; + ma_vorbis_decoder* pVorbis; + + ma_assert(pConfig != NULL); + ma_assert(pDecoder != NULL); + + /* We grow the buffer in chunks. */ + do + { + /* Allocate memory for a new chunk. */ + ma_uint8* pNewData; + size_t bytesRead; + int vorbisError = 0; + int consumedDataSize = 0; + + dataCapacity += MA_VORBIS_DATA_CHUNK_SIZE; + pNewData = (ma_uint8*)ma_realloc(pData, dataCapacity); + if (pNewData == NULL) { + ma_free(pData); + return MA_OUT_OF_MEMORY; + } + + pData = pNewData; + + /* Fill in a chunk. */ + bytesRead = ma_decoder_read_bytes(pDecoder, pData + dataSize, (dataCapacity - dataSize)); + if (bytesRead == 0) { + return MA_ERROR; + } + + dataSize += bytesRead; + if (dataSize > INT_MAX) { + return MA_ERROR; /* Too big. */ + } + + pInternalVorbis = stb_vorbis_open_pushdata(pData, (int)dataSize, &consumedDataSize, &vorbisError, NULL); + if (pInternalVorbis != NULL) { + /* + If we get here it means we were able to open the stb_vorbis decoder. There may be some leftover bytes in our buffer, so + we need to move those bytes down to the front of the buffer since they'll be needed for future decoding. + */ + size_t leftoverDataSize = (dataSize - (size_t)consumedDataSize); + size_t i; + for (i = 0; i < leftoverDataSize; ++i) { + pData[i] = pData[i + consumedDataSize]; + } + + dataSize = leftoverDataSize; + break; /* Success. */ + } else { + if (vorbisError == VORBIS_need_more_data) { + continue; + } else { + return MA_ERROR; /* Failed to open the stb_vorbis decoder. */ + } + } + } while (MA_TRUE); + + + /* If we get here it means we successfully opened the Vorbis decoder. */ + vorbisInfo = stb_vorbis_get_info(pInternalVorbis); + + /* Don't allow more than MA_MAX_CHANNELS channels. */ + if (vorbisInfo.channels > MA_MAX_CHANNELS) { + stb_vorbis_close(pInternalVorbis); + ma_free(pData); + return MA_ERROR; /* Too many channels. */ + } + + vorbisDataSize = sizeof(ma_vorbis_decoder) + sizeof(float)*vorbisInfo.max_frame_size; + pVorbis = (ma_vorbis_decoder*)ma_malloc(vorbisDataSize); + if (pVorbis == NULL) { + stb_vorbis_close(pInternalVorbis); + ma_free(pData); + return MA_OUT_OF_MEMORY; + } + + ma_zero_memory(pVorbis, vorbisDataSize); + pVorbis->pInternalVorbis = pInternalVorbis; + pVorbis->pData = pData; + pVorbis->dataSize = dataSize; + pVorbis->dataCapacity = dataCapacity; + + pDecoder->onSeekToPCMFrame = ma_decoder_internal_on_seek_to_pcm_frame__vorbis; + pDecoder->onUninit = ma_decoder_internal_on_uninit__vorbis; + pDecoder->onGetLengthInPCMFrames = ma_decoder_internal_on_get_length_in_pcm_frames__vorbis; + pDecoder->pInternalDecoder = pVorbis; + + /* The internal format is always f32. */ + pDecoder->internalFormat = ma_format_f32; + pDecoder->internalChannels = vorbisInfo.channels; + pDecoder->internalSampleRate = vorbisInfo.sample_rate; + ma_get_standard_channel_map(ma_standard_channel_map_vorbis, pDecoder->internalChannels, pDecoder->internalChannelMap); + + result = ma_decoder__init_dsp(pDecoder, pConfig, ma_decoder_internal_on_read_pcm_frames__vorbis); + if (result != MA_SUCCESS) { + stb_vorbis_close(pVorbis->pInternalVorbis); + ma_free(pVorbis->pData); + ma_free(pVorbis); + return result; + } + + return MA_SUCCESS; +} +#endif + +/* MP3 */ +#ifdef dr_mp3_h +#define MA_HAS_MP3 + +size_t ma_decoder_internal_on_read__mp3(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + ma_decoder* pDecoder = (ma_decoder*)pUserData; + ma_assert(pDecoder != NULL); + + return ma_decoder_read_bytes(pDecoder, pBufferOut, bytesToRead); +} + +drmp3_bool32 ma_decoder_internal_on_seek__mp3(void* pUserData, int offset, drmp3_seek_origin origin) +{ + ma_decoder* pDecoder = (ma_decoder*)pUserData; + ma_assert(pDecoder != NULL); + + return ma_decoder_seek_bytes(pDecoder, offset, (origin == drmp3_seek_origin_start) ? ma_seek_origin_start : ma_seek_origin_current); +} + +ma_uint32 ma_decoder_internal_on_read_pcm_frames__mp3(ma_pcm_converter* pDSP, void* pSamplesOut, ma_uint32 frameCount, void* pUserData) +{ + ma_decoder* pDecoder; + drmp3* pMP3; + + (void)pDSP; + + pDecoder = (ma_decoder*)pUserData; + ma_assert(pDecoder != NULL); + ma_assert(pDecoder->internalFormat == ma_format_f32); + + pMP3 = (drmp3*)pDecoder->pInternalDecoder; + ma_assert(pMP3 != NULL); + + return (ma_uint32)drmp3_read_pcm_frames_f32(pMP3, frameCount, (float*)pSamplesOut); +} + +ma_result ma_decoder_internal_on_seek_to_pcm_frame__mp3(ma_decoder* pDecoder, ma_uint64 frameIndex) +{ + drmp3* pMP3; + drmp3_bool32 result; + + pMP3 = (drmp3*)pDecoder->pInternalDecoder; + ma_assert(pMP3 != NULL); + + result = drmp3_seek_to_pcm_frame(pMP3, frameIndex); + if (result) { + return MA_SUCCESS; + } else { + return MA_ERROR; + } +} + +ma_result ma_decoder_internal_on_uninit__mp3(ma_decoder* pDecoder) +{ + drmp3_uninit((drmp3*)pDecoder->pInternalDecoder); + ma_free(pDecoder->pInternalDecoder); + return MA_SUCCESS; +} + +ma_uint64 ma_decoder_internal_on_get_length_in_pcm_frames__mp3(ma_decoder* pDecoder) +{ + return drmp3_get_pcm_frame_count((drmp3*)pDecoder->pInternalDecoder); +} + +ma_result ma_decoder_init_mp3__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + drmp3* pMP3; + drmp3_config mp3Config; + ma_result result; + + ma_assert(pConfig != NULL); + ma_assert(pDecoder != NULL); + + pMP3 = (drmp3*)ma_malloc(sizeof(*pMP3)); + if (pMP3 == NULL) { + return MA_OUT_OF_MEMORY; + } + + /* + Try opening the decoder first. MP3 can have variable sample rates (it's per frame/packet). We therefore need + to use some smarts to determine the most appropriate internal sample rate. These are the rules we're going + to use: + + Sample Rates + 1) If an output sample rate is specified in pConfig we just use that. Otherwise; + 2) Fall back to 44100. + + The internal channel count is always stereo, and the internal format is always f32. + */ + ma_zero_object(&mp3Config); + mp3Config.outputChannels = 2; + mp3Config.outputSampleRate = (pConfig->sampleRate != 0) ? pConfig->sampleRate : 44100; + if (!drmp3_init(pMP3, ma_decoder_internal_on_read__mp3, ma_decoder_internal_on_seek__mp3, pDecoder, &mp3Config)) { + return MA_ERROR; + } + + /* If we get here it means we successfully initialized the MP3 decoder. We can now initialize the rest of the ma_decoder. */ + pDecoder->onSeekToPCMFrame = ma_decoder_internal_on_seek_to_pcm_frame__mp3; + pDecoder->onUninit = ma_decoder_internal_on_uninit__mp3; + pDecoder->onGetLengthInPCMFrames = ma_decoder_internal_on_get_length_in_pcm_frames__mp3; + pDecoder->pInternalDecoder = pMP3; + + /* Internal format. */ + pDecoder->internalFormat = ma_format_f32; + pDecoder->internalChannels = pMP3->channels; + pDecoder->internalSampleRate = pMP3->sampleRate; + ma_get_standard_channel_map(ma_standard_channel_map_default, pDecoder->internalChannels, pDecoder->internalChannelMap); + + result = ma_decoder__init_dsp(pDecoder, pConfig, ma_decoder_internal_on_read_pcm_frames__mp3); + if (result != MA_SUCCESS) { + ma_free(pMP3); + return result; + } + + return MA_SUCCESS; +} +#endif + +/* Raw */ +ma_uint32 ma_decoder_internal_on_read_pcm_frames__raw(ma_pcm_converter* pDSP, void* pSamplesOut, ma_uint32 frameCount, void* pUserData) +{ + ma_decoder* pDecoder; + ma_uint32 bpf; + + (void)pDSP; + + pDecoder = (ma_decoder*)pUserData; + ma_assert(pDecoder != NULL); + + /* For raw decoding we just read directly from the decoder's callbacks. */ + bpf = ma_get_bytes_per_frame(pDecoder->internalFormat, pDecoder->internalChannels); + return (ma_uint32)ma_decoder_read_bytes(pDecoder, pSamplesOut, frameCount * bpf) / bpf; +} + +ma_result ma_decoder_internal_on_seek_to_pcm_frame__raw(ma_decoder* pDecoder, ma_uint64 frameIndex) +{ + ma_bool32 result = MA_FALSE; + ma_uint64 totalBytesToSeek; + + ma_assert(pDecoder != NULL); + + if (pDecoder->onSeek == NULL) { + return MA_ERROR; + } + + /* The callback uses a 32 bit integer whereas we use a 64 bit unsigned integer. We just need to continuously seek until we're at the correct position. */ + totalBytesToSeek = frameIndex * ma_get_bytes_per_frame(pDecoder->internalFormat, pDecoder->internalChannels); + if (totalBytesToSeek < 0x7FFFFFFF) { + /* Simple case. */ + result = ma_decoder_seek_bytes(pDecoder, (int)(frameIndex * ma_get_bytes_per_frame(pDecoder->internalFormat, pDecoder->internalChannels)), ma_seek_origin_start); + } else { + /* Complex case. Start by doing a seek relative to the start. Then keep looping using offset seeking. */ + result = ma_decoder_seek_bytes(pDecoder, 0x7FFFFFFF, ma_seek_origin_start); + if (result == MA_TRUE) { + totalBytesToSeek -= 0x7FFFFFFF; + + while (totalBytesToSeek > 0) { + ma_uint64 bytesToSeekThisIteration = totalBytesToSeek; + if (bytesToSeekThisIteration > 0x7FFFFFFF) { + bytesToSeekThisIteration = 0x7FFFFFFF; + } + + result = ma_decoder_seek_bytes(pDecoder, (int)bytesToSeekThisIteration, ma_seek_origin_current); + if (result != MA_TRUE) { + break; + } + + totalBytesToSeek -= bytesToSeekThisIteration; + } + } + } + + if (result) { + return MA_SUCCESS; + } else { + return MA_ERROR; + } +} + +ma_result ma_decoder_internal_on_uninit__raw(ma_decoder* pDecoder) +{ + (void)pDecoder; + return MA_SUCCESS; +} + +ma_uint64 ma_decoder_internal_on_get_length_in_pcm_frames__raw(ma_decoder* pDecoder) +{ + (void)pDecoder; + return 0; +} + +ma_result ma_decoder_init_raw__internal(const ma_decoder_config* pConfigIn, const ma_decoder_config* pConfigOut, ma_decoder* pDecoder) +{ + ma_result result; + + ma_assert(pConfigIn != NULL); + ma_assert(pConfigOut != NULL); + ma_assert(pDecoder != NULL); + + pDecoder->onSeekToPCMFrame = ma_decoder_internal_on_seek_to_pcm_frame__raw; + pDecoder->onUninit = ma_decoder_internal_on_uninit__raw; + pDecoder->onGetLengthInPCMFrames = ma_decoder_internal_on_get_length_in_pcm_frames__raw; + + /* Internal format. */ + pDecoder->internalFormat = pConfigIn->format; + pDecoder->internalChannels = pConfigIn->channels; + pDecoder->internalSampleRate = pConfigIn->sampleRate; + ma_channel_map_copy(pDecoder->internalChannelMap, pConfigIn->channelMap, pConfigIn->channels); + + result = ma_decoder__init_dsp(pDecoder, pConfigOut, ma_decoder_internal_on_read_pcm_frames__raw); + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + +ma_result ma_decoder__preinit(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_assert(pConfig != NULL); + + if (pDecoder == NULL) { + return MA_INVALID_ARGS; + } + + ma_zero_object(pDecoder); + + if (onRead == NULL || onSeek == NULL) { + return MA_INVALID_ARGS; + } + + pDecoder->onRead = onRead; + pDecoder->onSeek = onSeek; + pDecoder->pUserData = pUserData; + + (void)pConfig; + return MA_SUCCESS; +} + +ma_result ma_decoder_init_wav(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_decoder_config config; + ma_result result; + + config = ma_decoder_config_init_copy(pConfig); + + result = ma_decoder__preinit(onRead, onSeek, pUserData, &config, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + +#ifdef MA_HAS_WAV + return ma_decoder_init_wav__internal(&config, pDecoder); +#else + return MA_NO_BACKEND; +#endif +} + +ma_result ma_decoder_init_flac(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_decoder_config config; + ma_result result; + + config = ma_decoder_config_init_copy(pConfig); + + result = ma_decoder__preinit(onRead, onSeek, pUserData, &config, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + +#ifdef MA_HAS_FLAC + return ma_decoder_init_flac__internal(&config, pDecoder); +#else + return MA_NO_BACKEND; +#endif +} + +ma_result ma_decoder_init_vorbis(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_decoder_config config; + ma_result result; + + config = ma_decoder_config_init_copy(pConfig); + + result = ma_decoder__preinit(onRead, onSeek, pUserData, &config, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + +#ifdef MA_HAS_VORBIS + return ma_decoder_init_vorbis__internal(&config, pDecoder); +#else + return MA_NO_BACKEND; +#endif +} + +ma_result ma_decoder_init_mp3(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_decoder_config config; + ma_result result; + + config = ma_decoder_config_init_copy(pConfig); + + result = ma_decoder__preinit(onRead, onSeek, pUserData, &config, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + +#ifdef MA_HAS_MP3 + return ma_decoder_init_mp3__internal(&config, pDecoder); +#else + return MA_NO_BACKEND; +#endif +} + +ma_result ma_decoder_init_raw(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfigIn, const ma_decoder_config* pConfigOut, ma_decoder* pDecoder) +{ + ma_decoder_config config; + ma_result result; + + config = ma_decoder_config_init_copy(pConfigOut); + + result = ma_decoder__preinit(onRead, onSeek, pUserData, &config, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + + return ma_decoder_init_raw__internal(pConfigIn, &config, pDecoder); +} + +ma_result ma_decoder_init__internal(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result = MA_NO_BACKEND; + + ma_assert(pConfig != NULL); + ma_assert(pDecoder != NULL); + + /* Silence some warnings in the case that we don't have any decoder backends enabled. */ + (void)onRead; + (void)onSeek; + (void)pUserData; + (void)pConfig; + (void)pDecoder; + + /* We use trial and error to open a decoder. */ + +#ifdef MA_HAS_WAV + if (result != MA_SUCCESS) { + result = ma_decoder_init_wav__internal(pConfig, pDecoder); + if (result != MA_SUCCESS) { + onSeek(pDecoder, 0, ma_seek_origin_start); + } + } +#endif +#ifdef MA_HAS_FLAC + if (result != MA_SUCCESS) { + result = ma_decoder_init_flac__internal(pConfig, pDecoder); + if (result != MA_SUCCESS) { + onSeek(pDecoder, 0, ma_seek_origin_start); + } + } +#endif +#ifdef MA_HAS_VORBIS + if (result != MA_SUCCESS) { + result = ma_decoder_init_vorbis__internal(pConfig, pDecoder); + if (result != MA_SUCCESS) { + onSeek(pDecoder, 0, ma_seek_origin_start); + } + } +#endif +#ifdef MA_HAS_MP3 + if (result != MA_SUCCESS) { + result = ma_decoder_init_mp3__internal(pConfig, pDecoder); + if (result != MA_SUCCESS) { + onSeek(pDecoder, 0, ma_seek_origin_start); + } + } +#endif + + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +ma_result ma_decoder_init(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_decoder_config config; + ma_result result; + + config = ma_decoder_config_init_copy(pConfig); + + result = ma_decoder__preinit(onRead, onSeek, pUserData, &config, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + + return ma_decoder_init__internal(onRead, onSeek, pUserData, &config, pDecoder); +} + + +size_t ma_decoder__on_read_memory(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead) +{ + size_t bytesRemaining; + + ma_assert(pDecoder->memory.dataSize >= pDecoder->memory.currentReadPos); + + bytesRemaining = pDecoder->memory.dataSize - pDecoder->memory.currentReadPos; + if (bytesToRead > bytesRemaining) { + bytesToRead = bytesRemaining; + } + + if (bytesToRead > 0) { + ma_copy_memory(pBufferOut, pDecoder->memory.pData + pDecoder->memory.currentReadPos, bytesToRead); + pDecoder->memory.currentReadPos += bytesToRead; + } + + return bytesToRead; +} + +ma_bool32 ma_decoder__on_seek_memory(ma_decoder* pDecoder, int byteOffset, ma_seek_origin origin) +{ + if (origin == ma_seek_origin_current) { + if (byteOffset > 0) { + if (pDecoder->memory.currentReadPos + byteOffset > pDecoder->memory.dataSize) { + byteOffset = (int)(pDecoder->memory.dataSize - pDecoder->memory.currentReadPos); /* Trying to seek too far forward. */ + } + } else { + if (pDecoder->memory.currentReadPos < (size_t)-byteOffset) { + byteOffset = -(int)pDecoder->memory.currentReadPos; /* Trying to seek too far backwards. */ + } + } + + /* This will never underflow thanks to the clamps above. */ + pDecoder->memory.currentReadPos += byteOffset; + } else { + if ((ma_uint32)byteOffset <= pDecoder->memory.dataSize) { + pDecoder->memory.currentReadPos = byteOffset; + } else { + pDecoder->memory.currentReadPos = pDecoder->memory.dataSize; /* Trying to seek too far forward. */ + } + } + + return MA_TRUE; +} + +ma_result ma_decoder__preinit_memory(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result = ma_decoder__preinit(ma_decoder__on_read_memory, ma_decoder__on_seek_memory, NULL, pConfig, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + + if (pData == NULL || dataSize == 0) { + return MA_INVALID_ARGS; + } + + pDecoder->memory.pData = (const ma_uint8*)pData; + pDecoder->memory.dataSize = dataSize; + pDecoder->memory.currentReadPos = 0; + + (void)pConfig; + return MA_SUCCESS; +} + +ma_result ma_decoder_init_memory(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_decoder_config config; + ma_result result; + + config = ma_decoder_config_init_copy(pConfig); /* Make sure the config is not NULL. */ + + result = ma_decoder__preinit_memory(pData, dataSize, &config, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + + return ma_decoder_init__internal(ma_decoder__on_read_memory, ma_decoder__on_seek_memory, NULL, &config, pDecoder); +} + +ma_result ma_decoder_init_memory_wav(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_decoder_config config; + ma_result result; + + config = ma_decoder_config_init_copy(pConfig); /* Make sure the config is not NULL. */ + + result = ma_decoder__preinit_memory(pData, dataSize, &config, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + +#ifdef MA_HAS_WAV + return ma_decoder_init_wav__internal(&config, pDecoder); +#else + return MA_NO_BACKEND; +#endif +} + +ma_result ma_decoder_init_memory_flac(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_decoder_config config; + ma_result result; + + config = ma_decoder_config_init_copy(pConfig); /* Make sure the config is not NULL. */ + + result = ma_decoder__preinit_memory(pData, dataSize, &config, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + +#ifdef MA_HAS_FLAC + return ma_decoder_init_flac__internal(&config, pDecoder); +#else + return MA_NO_BACKEND; +#endif +} + +ma_result ma_decoder_init_memory_vorbis(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_decoder_config config; + ma_result result; + + config = ma_decoder_config_init_copy(pConfig); /* Make sure the config is not NULL. */ + + result = ma_decoder__preinit_memory(pData, dataSize, &config, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + +#ifdef MA_HAS_VORBIS + return ma_decoder_init_vorbis__internal(&config, pDecoder); +#else + return MA_NO_BACKEND; +#endif +} + +ma_result ma_decoder_init_memory_mp3(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_decoder_config config; + ma_result result; + + config = ma_decoder_config_init_copy(pConfig); /* Make sure the config is not NULL. */ + + result = ma_decoder__preinit_memory(pData, dataSize, &config, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + +#ifdef MA_HAS_MP3 + return ma_decoder_init_mp3__internal(&config, pDecoder); +#else + return MA_NO_BACKEND; +#endif +} + +ma_result ma_decoder_init_memory_raw(const void* pData, size_t dataSize, const ma_decoder_config* pConfigIn, const ma_decoder_config* pConfigOut, ma_decoder* pDecoder) +{ + ma_decoder_config config; + ma_result result; + + config = ma_decoder_config_init_copy(pConfigOut); /* Make sure the config is not NULL. */ + + result = ma_decoder__preinit_memory(pData, dataSize, &config, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + + return ma_decoder_init_raw__internal(pConfigIn, &config, pDecoder); +} + +#ifndef MA_NO_STDIO +#include +#if !defined(_MSC_VER) && !defined(__DMC__) +#include /* For strcasecmp(). */ +#include /* For wcsrtombs() */ +#endif + +const char* ma_path_file_name(const char* path) +{ + const char* fileName; + + if (path == NULL) { + return NULL; + } + + fileName = path; + + /* We just loop through the path until we find the last slash. */ + while (path[0] != '\0') { + if (path[0] == '/' || path[0] == '\\') { + fileName = path; + } + + path += 1; + } + + /* At this point the file name is sitting on a slash, so just move forward. */ + while (fileName[0] != '\0' && (fileName[0] == '/' || fileName[0] == '\\')) { + fileName += 1; + } + + return fileName; +} + +const wchar_t* ma_path_file_name_w(const wchar_t* path) +{ + const wchar_t* fileName; + + if (path == NULL) { + return NULL; + } + + fileName = path; + + /* We just loop through the path until we find the last slash. */ + while (path[0] != '\0') { + if (path[0] == '/' || path[0] == '\\') { + fileName = path; + } + + path += 1; + } + + /* At this point the file name is sitting on a slash, so just move forward. */ + while (fileName[0] != '\0' && (fileName[0] == '/' || fileName[0] == '\\')) { + fileName += 1; + } + + return fileName; +} + + +const char* ma_path_extension(const char* path) +{ + const char* extension; + const char* lastOccurance; + + if (path == NULL) { + path = ""; + } + + extension = ma_path_file_name(path); + lastOccurance = NULL; + + /* Just find the last '.' and return. */ + while (extension[0] != '\0') { + if (extension[0] == '.') { + extension += 1; + lastOccurance = extension; + } + + extension += 1; + } + + return (lastOccurance != NULL) ? lastOccurance : extension; +} + +const wchar_t* ma_path_extension_w(const wchar_t* path) +{ + const wchar_t* extension; + const wchar_t* lastOccurance; + + if (path == NULL) { + path = L""; + } + + extension = ma_path_file_name_w(path); + lastOccurance = NULL; + + /* Just find the last '.' and return. */ + while (extension[0] != '\0') { + if (extension[0] == '.') { + extension += 1; + lastOccurance = extension; + } + + extension += 1; + } + + return (lastOccurance != NULL) ? lastOccurance : extension; +} + + +ma_bool32 ma_path_extension_equal(const char* path, const char* extension) +{ + const char* ext1; + const char* ext2; + + if (path == NULL || extension == NULL) { + return MA_FALSE; + } + + ext1 = extension; + ext2 = ma_path_extension(path); + +#if defined(_MSC_VER) || defined(__DMC__) + return _stricmp(ext1, ext2) == 0; +#else + return strcasecmp(ext1, ext2) == 0; +#endif +} + +ma_bool32 ma_path_extension_equal_w(const wchar_t* path, const wchar_t* extension) +{ + const wchar_t* ext1; + const wchar_t* ext2; + + if (path == NULL || extension == NULL) { + return MA_FALSE; + } + + ext1 = extension; + ext2 = ma_path_extension_w(path); + +#if defined(_MSC_VER) || defined(__DMC__) + return _wcsicmp(ext1, ext2) == 0; +#else + /* + I'm not aware of a wide character version of strcasecmp(). I'm therefore converting the extensions to multibyte strings and comparing those. This + isn't the most efficient way to do it, but it should work OK. + */ + { + char ext1MB[4096]; + char ext2MB[4096]; + const wchar_t* pext1 = ext1; + const wchar_t* pext2 = ext2; + mbstate_t mbs1; + mbstate_t mbs2; + + ma_zero_object(&mbs1); + ma_zero_object(&mbs2); + + if (wcsrtombs(ext1MB, &pext1, sizeof(ext1MB), &mbs1) == (size_t)-1) { + return MA_FALSE; + } + if (wcsrtombs(ext2MB, &pext2, sizeof(ext2MB), &mbs2) == (size_t)-1) { + return MA_FALSE; + } + + return strcasecmp(ext1MB, ext2MB) == 0; + } +#endif +} + + +size_t ma_decoder__on_read_stdio(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead) +{ + return fread(pBufferOut, 1, bytesToRead, (FILE*)pDecoder->pUserData); +} + +ma_bool32 ma_decoder__on_seek_stdio(ma_decoder* pDecoder, int byteOffset, ma_seek_origin origin) +{ + return fseek((FILE*)pDecoder->pUserData, byteOffset, (origin == ma_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; +} + +ma_result ma_decoder__preinit_file(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + FILE* pFile; + + if (pDecoder == NULL) { + return MA_INVALID_ARGS; + } + + ma_zero_object(pDecoder); + + if (pFilePath == NULL || pFilePath[0] == '\0') { + return MA_INVALID_ARGS; + } + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (fopen_s(&pFile, pFilePath, "rb") != 0) { + return MA_ERROR; + } +#else + pFile = fopen(pFilePath, "rb"); + if (pFile == NULL) { + return MA_ERROR; + } +#endif + + /* We need to manually set the user data so the calls to ma_decoder__on_seek_stdio() succeed. */ + pDecoder->pUserData = pFile; + + (void)pConfig; + return MA_SUCCESS; +} + +ma_result ma_decoder__preinit_file_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + FILE* pFile; + + if (pDecoder == NULL) { + return MA_INVALID_ARGS; + } + + ma_zero_object(pDecoder); + + if (pFilePath == NULL || pFilePath[0] == '\0') { + return MA_INVALID_ARGS; + } + +#if defined(_WIN32) + /* Use _wfopen() on Windows. */ + #if defined(_MSC_VER) && _MSC_VER >= 1400 + if (_wfopen_s(&pFile, pFilePath, L"rb") != 0) { + return MA_ERROR; + } + #else + pFile = _wfopen(pFilePath, L"rb"); + if (pFile == NULL) { + return MA_ERROR; + } + #endif +#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; + + /* Get the length first. */ + ma_zero_object(&mbs); + lenMB = wcsrtombs(NULL, &pFilePathTemp, 0, &mbs); + if (lenMB == (size_t)-1) { + return MA_ERROR; + } + + pFilePathMB = (char*)MA_MALLOC(lenMB + 1); + if (pFilePathMB == NULL) { + return MA_OUT_OF_MEMORY; + } + + pFilePathTemp = pFilePath; + ma_zero_object(&mbs); + wcsrtombs(pFilePathMB, &pFilePathTemp, lenMB + 1, &mbs); + + pFile = fopen(pFilePathMB, "rb"); + + MA_FREE(pFilePathMB); + } + + if (pFile == NULL) { + return MA_ERROR; + } +#endif + + /* We need to manually set the user data so the calls to ma_decoder__on_seek_stdio() succeed. */ + pDecoder->pUserData = pFile; + + (void)pConfig; + return MA_SUCCESS; +} + +ma_result ma_decoder_init_file(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result = ma_decoder__preinit_file(pFilePath, pConfig, pDecoder); /* This sets pDecoder->pUserData to a FILE*. */ + if (result != MA_SUCCESS) { + return result; + } + + /* WAV */ + if (ma_path_extension_equal(pFilePath, "wav")) { + result = ma_decoder_init_wav(ma_decoder__on_read_stdio, ma_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); + if (result == MA_SUCCESS) { + return MA_SUCCESS; + } + + ma_decoder__on_seek_stdio(pDecoder, 0, ma_seek_origin_start); + } + + /* FLAC */ + if (ma_path_extension_equal(pFilePath, "flac")) { + result = ma_decoder_init_flac(ma_decoder__on_read_stdio, ma_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); + if (result == MA_SUCCESS) { + return MA_SUCCESS; + } + + ma_decoder__on_seek_stdio(pDecoder, 0, ma_seek_origin_start); + } + + /* MP3 */ + if (ma_path_extension_equal(pFilePath, "mp3")) { + result = ma_decoder_init_mp3(ma_decoder__on_read_stdio, ma_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); + if (result == MA_SUCCESS) { + return MA_SUCCESS; + } + + ma_decoder__on_seek_stdio(pDecoder, 0, ma_seek_origin_start); + } + + /* Trial and error. */ + return ma_decoder_init(ma_decoder__on_read_stdio, ma_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); +} + +ma_result ma_decoder_init_file_wav(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result = ma_decoder__preinit_file(pFilePath, pConfig, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + + return ma_decoder_init_wav(ma_decoder__on_read_stdio, ma_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); +} + +ma_result ma_decoder_init_file_flac(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result = ma_decoder__preinit_file(pFilePath, pConfig, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + + return ma_decoder_init_flac(ma_decoder__on_read_stdio, ma_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); +} + +ma_result ma_decoder_init_file_vorbis(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result = ma_decoder__preinit_file(pFilePath, pConfig, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + + return ma_decoder_init_vorbis(ma_decoder__on_read_stdio, ma_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); +} + +ma_result ma_decoder_init_file_mp3(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result = ma_decoder__preinit_file(pFilePath, pConfig, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + + return ma_decoder_init_mp3(ma_decoder__on_read_stdio, ma_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); +} + + +ma_result ma_decoder_init_file_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result = ma_decoder__preinit_file_w(pFilePath, pConfig, pDecoder); /* This sets pDecoder->pUserData to a FILE*. */ + if (result != MA_SUCCESS) { + return result; + } + + /* WAV */ + if (ma_path_extension_equal_w(pFilePath, L"wav")) { + result = ma_decoder_init_wav(ma_decoder__on_read_stdio, ma_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); + if (result == MA_SUCCESS) { + return MA_SUCCESS; + } + + ma_decoder__on_seek_stdio(pDecoder, 0, ma_seek_origin_start); + } + + /* FLAC */ + if (ma_path_extension_equal_w(pFilePath, L"flac")) { + result = ma_decoder_init_flac(ma_decoder__on_read_stdio, ma_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); + if (result == MA_SUCCESS) { + return MA_SUCCESS; + } + + ma_decoder__on_seek_stdio(pDecoder, 0, ma_seek_origin_start); + } + + /* MP3 */ + if (ma_path_extension_equal_w(pFilePath, L"mp3")) { + result = ma_decoder_init_mp3(ma_decoder__on_read_stdio, ma_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); + if (result == MA_SUCCESS) { + return MA_SUCCESS; + } + + ma_decoder__on_seek_stdio(pDecoder, 0, ma_seek_origin_start); + } + + /* Trial and error. */ + return ma_decoder_init(ma_decoder__on_read_stdio, ma_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); +} + +ma_result ma_decoder_init_file_wav_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result = ma_decoder__preinit_file_w(pFilePath, pConfig, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + + return ma_decoder_init_wav(ma_decoder__on_read_stdio, ma_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); +} + +ma_result ma_decoder_init_file_flac_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result = ma_decoder__preinit_file_w(pFilePath, pConfig, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + + return ma_decoder_init_flac(ma_decoder__on_read_stdio, ma_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); +} + +ma_result ma_decoder_init_file_vorbis_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result = ma_decoder__preinit_file_w(pFilePath, pConfig, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + + return ma_decoder_init_vorbis(ma_decoder__on_read_stdio, ma_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); +} + +ma_result ma_decoder_init_file_mp3_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result = ma_decoder__preinit_file_w(pFilePath, pConfig, pDecoder); + if (result != MA_SUCCESS) { + return result; + } + + return ma_decoder_init_mp3(ma_decoder__on_read_stdio, ma_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); +} +#endif + +ma_result ma_decoder_uninit(ma_decoder* pDecoder) +{ + if (pDecoder == NULL) { + return MA_INVALID_ARGS; + } + + if (pDecoder->onUninit) { + pDecoder->onUninit(pDecoder); + } + +#ifndef MA_NO_STDIO + /* If we have a file handle, close it. */ + if (pDecoder->onRead == ma_decoder__on_read_stdio) { + fclose((FILE*)pDecoder->pUserData); + } +#endif + + return MA_SUCCESS; +} + +ma_uint64 ma_decoder_get_length_in_pcm_frames(ma_decoder* pDecoder) +{ + if (pDecoder == NULL) { + return 0; + } + + if (pDecoder->onGetLengthInPCMFrames) { + return pDecoder->onGetLengthInPCMFrames(pDecoder); + } + + return 0; +} + +ma_uint64 ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount) +{ + if (pDecoder == NULL) { + return 0; + } + + return ma_pcm_converter_read(&pDecoder->dsp, pFramesOut, frameCount); +} + +ma_result ma_decoder_seek_to_pcm_frame(ma_decoder* pDecoder, ma_uint64 frameIndex) +{ + if (pDecoder == NULL) { + return 0; + } + + if (pDecoder->onSeekToPCMFrame) { + return pDecoder->onSeekToPCMFrame(pDecoder, frameIndex); + } + + /* Should never get here, but if we do it means onSeekToPCMFrame was not set by the backend. */ + return MA_INVALID_ARGS; +} + + +ma_result ma_decoder__full_decode_and_uninit(ma_decoder* pDecoder, ma_decoder_config* pConfigOut, ma_uint64* pFrameCountOut, void** ppPCMFramesOut) +{ + ma_uint64 totalFrameCount; + ma_uint64 bpf; + ma_uint64 dataCapInFrames; + void* pPCMFramesOut; + + ma_assert(pDecoder != NULL); + + totalFrameCount = 0; + bpf = ma_get_bytes_per_frame(pDecoder->outputFormat, pDecoder->outputChannels); + + /* The frame count is unknown until we try reading. Thus, we just run in a loop. */ + dataCapInFrames = 0; + pPCMFramesOut = NULL; + for (;;) { + ma_uint64 frameCountToTryReading; + ma_uint64 framesJustRead; + + /* Make room if there's not enough. */ + if (totalFrameCount == dataCapInFrames) { + void* pNewPCMFramesOut; + ma_uint64 newDataCapInFrames = dataCapInFrames*2; + if (newDataCapInFrames == 0) { + newDataCapInFrames = 4096; + } + + if ((newDataCapInFrames * bpf) > MA_SIZE_MAX) { + ma_free(pPCMFramesOut); + return MA_TOO_LARGE; + } + + + pNewPCMFramesOut = (void*)ma_realloc(pPCMFramesOut, (size_t)(newDataCapInFrames * bpf)); + if (pNewPCMFramesOut == NULL) { + ma_free(pPCMFramesOut); + return MA_OUT_OF_MEMORY; + } + + dataCapInFrames = newDataCapInFrames; + pPCMFramesOut = pNewPCMFramesOut; + } + + frameCountToTryReading = dataCapInFrames - totalFrameCount; + ma_assert(frameCountToTryReading > 0); + + framesJustRead = ma_decoder_read_pcm_frames(pDecoder, (ma_uint8*)pPCMFramesOut + (totalFrameCount * bpf), frameCountToTryReading); + totalFrameCount += framesJustRead; + + if (framesJustRead < frameCountToTryReading) { + break; + } + } + + + if (pConfigOut != NULL) { + pConfigOut->format = pDecoder->outputFormat; + pConfigOut->channels = pDecoder->outputChannels; + pConfigOut->sampleRate = pDecoder->outputSampleRate; + ma_channel_map_copy(pConfigOut->channelMap, pDecoder->outputChannelMap, pDecoder->outputChannels); + } + + if (ppPCMFramesOut != NULL) { + *ppPCMFramesOut = pPCMFramesOut; + } else { + ma_free(pPCMFramesOut); + } + + if (pFrameCountOut != NULL) { + *pFrameCountOut = totalFrameCount; + } + + ma_decoder_uninit(pDecoder); + return MA_SUCCESS; +} + +#ifndef MA_NO_STDIO +ma_result ma_decode_file(const char* pFilePath, ma_decoder_config* pConfig, ma_uint64* pFrameCountOut, void** ppPCMFramesOut) +{ + ma_decoder_config config; + ma_decoder decoder; + ma_result result; + + if (pFrameCountOut != NULL) { + *pFrameCountOut = 0; + } + if (ppPCMFramesOut != NULL) { + *ppPCMFramesOut = NULL; + } + + if (pFilePath == NULL) { + return MA_INVALID_ARGS; + } + + config = ma_decoder_config_init_copy(pConfig); + + result = ma_decoder_init_file(pFilePath, &config, &decoder); + if (result != MA_SUCCESS) { + return result; + } + + return ma_decoder__full_decode_and_uninit(&decoder, pConfig, pFrameCountOut, ppPCMFramesOut); +} +#endif + +ma_result ma_decode_memory(const void* pData, size_t dataSize, ma_decoder_config* pConfig, ma_uint64* pFrameCountOut, void** ppPCMFramesOut) +{ + ma_decoder_config config; + ma_decoder decoder; + ma_result result; + + if (pFrameCountOut != NULL) { + *pFrameCountOut = 0; + } + if (ppPCMFramesOut != NULL) { + *ppPCMFramesOut = NULL; + } + + if (pData == NULL || dataSize == 0) { + return MA_INVALID_ARGS; + } + + config = ma_decoder_config_init_copy(pConfig); + + result = ma_decoder_init_memory(pData, dataSize, &config, &decoder); + if (result != MA_SUCCESS) { + return result; + } + + return ma_decoder__full_decode_and_uninit(&decoder, pConfig, pFrameCountOut, ppPCMFramesOut); +} + +#endif /* MA_NO_DECODING */ + + + + +/************************************************************************************************************************************************************** + +Generation + +**************************************************************************************************************************************************************/ +ma_result ma_sine_wave_init(double amplitude, double periodsPerSecond, ma_uint32 sampleRate, ma_sine_wave* pSineWave) +{ + if (pSineWave == NULL) { + return MA_INVALID_ARGS; + } + ma_zero_object(pSineWave); + + if (amplitude == 0 || periodsPerSecond == 0) { + return MA_INVALID_ARGS; + } + + if (amplitude > 1) { + amplitude = 1; + } + if (amplitude < -1) { + amplitude = -1; + } + + pSineWave->amplitude = amplitude; + pSineWave->periodsPerSecond = periodsPerSecond; + pSineWave->delta = MA_TAU_D / sampleRate; + pSineWave->time = 0; + + return MA_SUCCESS; +} + +ma_uint64 ma_sine_wave_read_f32(ma_sine_wave* pSineWave, ma_uint64 count, float* pSamples) +{ + return ma_sine_wave_read_f32_ex(pSineWave, count, 1, ma_stream_layout_interleaved, &pSamples); +} + +ma_uint64 ma_sine_wave_read_f32_ex(ma_sine_wave* pSineWave, ma_uint64 frameCount, ma_uint32 channels, ma_stream_layout layout, float** ppFrames) +{ + if (pSineWave == NULL) { + return 0; + } + + if (ppFrames != NULL) { + ma_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + ma_uint32 iChannel; + + float s = (float)(sin(pSineWave->time * pSineWave->periodsPerSecond) * pSineWave->amplitude); + pSineWave->time += pSineWave->delta; + + if (layout == ma_stream_layout_interleaved) { + for (iChannel = 0; iChannel < channels; iChannel += 1) { + ppFrames[0][iFrame*channels + iChannel] = s; + } + } else { + for (iChannel = 0; iChannel < channels; iChannel += 1) { + ppFrames[iChannel][iFrame] = s; + } + } + } + } else { + pSineWave->time += pSineWave->delta * (ma_int64)frameCount; /* Cast to int64 required for VC6. */ + } + + return frameCount; +} + +#if defined(_MSC_VER) + #pragma warning(pop) +#endif + +#endif /* MINIAUDIO_IMPLEMENTATION */ + +/* +BACKEND IMPLEMENTATION GUIDLINES +================================ +Context +------- +- Run-time linking if possible. +- Set whether or not it's an asynchronous backend + +Device +------ +- If a full-duplex device is requested and the backend does not support full duplex devices, have ma_device_init__[backend]() + return MA_DEVICE_TYPE_NOT_SUPPORTED. +- If exclusive mode is requested, but the backend does not support it, return MA_SHARE_MODE_NOT_SUPPORTED. If practical, try + not to fall back to a different share mode - give the client exactly what they asked for. Some backends, such as ALSA, may + not have a practical way to distinguish between the two. +- If pDevice->usingDefault* is set, prefer the device's native value if the backend supports it. Otherwise use the relevant + value from the config. +- If the configs buffer size in frames is 0, set it based on the buffer size in milliseconds, keeping in mind to handle the + case when the default sample rate is being used where practical. +- Backends must set the following members of pDevice before returning successfully from ma_device_init__[backend](): + - internalFormat + - internalChannels + - internalSampleRate + - internalChannelMap + - bufferSizeInFrames + - periods +*/ + +/* +REVISION HISTORY +================ +v0.9.6 - 2019-08-04 + - Add support for loading decoders using a wchar_t string for file paths. + - Don't trigger an assert when ma_device_start() is called on a device that is already started. This will now log a warning + and return MA_INVALID_OPERATION. The same applies for ma_device_stop(). + - Try fixing an issue with PulseAudio taking a long time to start playback. + - Fix a bug in ma_convert_frames() and ma_convert_frames_ex(). + - Fix memory leaks in the WASAPI backend. + - Fix a compilation error with Visual Studio 2010. + +v0.9.5 - 2019-05-21 + - Add logging to ma_dlopen() and ma_dlsym(). + - Add ma_decoder_get_length_in_pcm_frames(). + - Fix a bug with capture on the OpenSL|ES backend. + - Fix a bug with the ALSA backend where a device would not restart after being stopped. + +v0.9.4 - 2019-05-06 + - Add support for C89. With this change, miniaudio should compile clean with GCC/Clang with "-std=c89 -ansi -pedantic" and + Microsoft compilers back to VC6. Other compilers should also work, but have not been tested. + +v0.9.3 - 2019-04-19 + - Fix compiler errors on GCC when compiling with -std=c99. + +v0.9.2 - 2019-04-08 + - Add support for per-context user data. + - Fix a potential bug with context configs. + - Fix some bugs with PulseAudio. + +v0.9.1 - 2019-03-17 + - Fix a bug where the output buffer is not getting zeroed out before calling the data callback. This happens when + the device is running in passthrough mode (not doing any data conversion). + - Fix an issue where the data callback is getting called too frequently on the WASAPI and DirectSound backends. + - Fix error on the UWP build. + - Fix a build error on Apple platforms. + +v0.9 - 2019-03-06 + - Rebranded to "miniaudio". All namespaces have been renamed from "mal" to "ma". + - API CHANGE: ma_device_init() and ma_device_config_init() have changed significantly: + - The device type, device ID and user data pointer have moved from ma_device_init() to the config. + - All variations of ma_device_config_init_*() have been removed in favor of just ma_device_config_init(). + - ma_device_config_init() now takes only one parameter which is the device type. All other properties need + to be set on the returned object directly. + - The onDataCallback and onStopCallback members of ma_device_config have been renamed to "dataCallback" + and "stopCallback". + - The ID of the physical device is now split into two: one for the playback device and the other for the + capture device. This is required for full-duplex. These are named "pPlaybackDeviceID" and "pCaptureDeviceID". + - API CHANGE: The data callback has changed. It now uses a unified callback for all device types rather than + being separate for each. It now takes two pointers - one containing input data and the other output data. This + design in required for full-duplex. The return value is now void instead of the number of frames written. The + new callback looks like the following: + void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); + - API CHANGE: Remove the log callback parameter from ma_context_config_init(). With this change, + ma_context_config_init() now takes no parameters and the log callback is set via the structure directly. The + new policy for config initialization is that only mandatory settings are passed in to *_config_init(). The + "onLog" member of ma_context_config has been renamed to "logCallback". + - API CHANGE: Remove ma_device_get_buffer_size_in_bytes(). + - API CHANGE: Rename decoding APIs to "pcm_frames" convention. + - mal_decoder_read() -> ma_decoder_read_pcm_frames() + - mal_decoder_seek_to_frame() -> ma_decoder_seek_to_pcm_frame() + - API CHANGE: Rename sine wave reading APIs to f32 convention. + - mal_sine_wave_read() -> ma_sine_wave_read_f32() + - mal_sine_wave_read_ex() -> ma_sine_wave_read_f32_ex() + - API CHANGE: Remove some deprecated APIs + - mal_device_set_recv_callback() + - mal_device_set_send_callback() + - mal_src_set_input_sample_rate() + - mal_src_set_output_sample_rate() + - API CHANGE: Add log level to the log callback. New signature: + - void on_log(ma_context* pContext, ma_device* pDevice, ma_uint32 logLevel, const char* message) + - API CHANGE: Changes to result codes. Constants have changed and unused codes have been removed. If you're + a binding mainainer you will need to update your result code constants. + - API CHANGE: Change the order of the ma_backend enums to priority order. If you are a binding maintainer, you + will need to update. + - API CHANGE: Rename mal_dsp to ma_pcm_converter. All functions have been renamed from mal_dsp_*() to + ma_pcm_converter_*(). All structures have been renamed from mal_dsp* to ma_pcm_converter*. + - API CHANGE: Reorder parameters of ma_decoder_read_pcm_frames() to be consistent with the new parameter order scheme. + - The resampling algorithm has been changed from sinc to linear. The rationale for this is that the sinc implementation + is too inefficient right now. This will hopefully be improved at a later date. + - Device initialization will no longer fall back to shared mode if exclusive mode is requested but is unusable. + With this change, if you request an device in exclusive mode, but exclusive mode is not supported, it will not + automatically fall back to shared mode. The client will need to reinitialize the device in shared mode if that's + what they want. + - Add ring buffer API. This is ma_rb and ma_pcm_rb, the difference being that ma_rb operates on bytes and + ma_pcm_rb operates on PCM frames. + - Add Web Audio backend. This is used when compiling with Emscripten. The SDL backend, which was previously + used for web support, will be removed in a future version. + - Add AAudio backend (Android Audio). This is the new priority backend for Android. Support for AAudio starts + with Android 8. OpenSL|ES is used as a fallback for older versions of Android. + - Remove OpenAL and SDL backends. + - Fix a possible deadlock when rapidly stopping the device after it has started. + - Update documentation. + - Change licensing to a choice of public domain _or_ MIT-0 (No Attribution). + +v0.8.14 - 2018-12-16 + - Core Audio: Fix a bug where the device state is not set correctly after stopping. + - Add support for custom weights to the channel router. + - Update decoders to use updated APIs in dr_flac, dr_mp3 and dr_wav. + +v0.8.13 - 2018-12-04 + - Core Audio: Fix a bug with channel mapping. + - Fix a bug with channel routing where the back/left and back/right channels have the wrong weight. + +v0.8.12 - 2018-11-27 + - Drop support for SDL 1.2. The Emscripten build now requires "-s USE_SDL=2". + - Fix a linking error with ALSA. + - Fix a bug on iOS where the device name is not set correctly. + +v0.8.11 - 2018-11-21 + - iOS bug fixes. + - Minor tweaks to PulseAudio. + +v0.8.10 - 2018-10-21 + - Core Audio: Fix a hang when uninitializing a device. + - Fix a bug where an incorrect value is returned from ma_device_stop(). + +v0.8.9 - 2018-09-28 + - Fix a bug with the SDL backend where device initialization fails. + +v0.8.8 - 2018-09-14 + - Fix Linux build with the ALSA backend. + - Minor documentation fix. + +v0.8.7 - 2018-09-12 + - Fix a bug with UWP detection. + +v0.8.6 - 2018-08-26 + - Automatically switch the internal device when the default device is unplugged. Note that this is still in the + early stages and not all backends handle this the same way. As of this version, this will not detect a default + device switch when changed from the operating system's audio preferences (unless the backend itself handles + this automatically). This is not supported in exclusive mode. + - WASAPI and Core Audio: Add support for stream routing. When the application is using a default device and the + user switches the default device via the operating system's audio preferences, miniaudio will automatically switch + the internal device to the new default. This is not supported in exclusive mode. + - WASAPI: Add support for hardware offloading via IAudioClient2. Only supported on Windows 8 and newer. + - WASAPI: Add support for low-latency shared mode via IAudioClient3. Only supported on Windows 10 and newer. + - Add support for compiling the UWP build as C. + - mal_device_set_recv_callback() and mal_device_set_send_callback() have been deprecated. You must now set this + when the device is initialized with mal_device_init*(). These will be removed in version 0.9.0. + +v0.8.5 - 2018-08-12 + - Add support for specifying the size of a device's buffer in milliseconds. You can still set the buffer size in + frames if that suits you. When bufferSizeInFrames is 0, bufferSizeInMilliseconds will be used. If both are non-0 + then bufferSizeInFrames will take priority. If both are set to 0 the default buffer size is used. + - Add support for the audio(4) backend to OpenBSD. + - Fix a bug with the ALSA backend that was causing problems on Raspberry Pi. This significantly improves the + Raspberry Pi experience. + - Fix a bug where an incorrect number of samples is returned from sinc resampling. + - Add support for setting the value to be passed to internal calls to CoInitializeEx(). + - WASAPI and WinMM: Stop the device when it is unplugged. + +v0.8.4 - 2018-08-06 + - Add sndio backend for OpenBSD. + - Add audio(4) backend for NetBSD. + - Drop support for the OSS backend on everything except FreeBSD and DragonFly BSD. + - Formats are now native-endian (were previously little-endian). + - Mark some APIs as deprecated: + - mal_src_set_input_sample_rate() and mal_src_set_output_sample_rate() are replaced with mal_src_set_sample_rate(). + - mal_dsp_set_input_sample_rate() and mal_dsp_set_output_sample_rate() are replaced with mal_dsp_set_sample_rate(). + - Fix a bug when capturing using the WASAPI backend. + - Fix some aliasing issues with resampling, specifically when increasing the sample rate. + - Fix warnings. + +v0.8.3 - 2018-07-15 + - Fix a crackling bug when resampling in capture mode. + - Core Audio: Fix a bug where capture does not work. + - ALSA: Fix a bug where the worker thread can get stuck in an infinite loop. + - PulseAudio: Fix a bug where mal_context_init() succeeds when PulseAudio is unusable. + - JACK: Fix a bug where mal_context_init() succeeds when JACK is unusable. + +v0.8.2 - 2018-07-07 + - Fix a bug on macOS with Core Audio where the internal callback is not called. + +v0.8.1 - 2018-07-06 + - Fix compilation errors and warnings. + +v0.8 - 2018-07-05 + - Changed MA_IMPLEMENTATION to MINI_AL_IMPLEMENTATION for consistency with other libraries. The old + way is still supported for now, but you should update as it may be removed in the future. + - API CHANGE: Replace device enumeration APIs. mal_enumerate_devices() has been replaced with + mal_context_get_devices(). An additional low-level device enumration API has been introduced called + mal_context_enumerate_devices() which uses a callback to report devices. + - API CHANGE: Rename mal_get_sample_size_in_bytes() to mal_get_bytes_per_sample() and add + mal_get_bytes_per_frame(). + - API CHANGE: Replace mal_device_config.preferExclusiveMode with ma_device_config.shareMode. + - This new config can be set to mal_share_mode_shared (default) or ma_share_mode_exclusive. + - API CHANGE: Remove excludeNullDevice from mal_context_config.alsa. + - API CHANGE: Rename MA_MAX_SAMPLE_SIZE_IN_BYTES to MA_MAX_PCM_SAMPLE_SIZE_IN_BYTES. + - API CHANGE: Change the default channel mapping to the standard Microsoft mapping. + - API CHANGE: Remove backend-specific result codes. + - API CHANGE: Changes to the format conversion APIs (mal_pcm_f32_to_s16(), etc.) + - Add support for Core Audio (Apple). + - Add support for PulseAudio. + - This is the highest priority backend on Linux (higher priority than ALSA) since it is commonly + installed by default on many of the popular distros and offer's more seamless integration on + platforms where PulseAudio is used. In addition, if PulseAudio is installed and running (which + is extremely common), it's better to just use PulseAudio directly rather than going through the + "pulse" ALSA plugin (which is what the "default" ALSA device is likely set to). + - Add support for JACK. + - Remove dependency on asound.h for the ALSA backend. This means the ALSA development packages are no + longer required to build miniaudio. + - Remove dependency on dsound.h for the DirectSound backend. This fixes build issues with some + distributions of MinGW. + - Remove dependency on audioclient.h for the WASAPI backend. This fixes build issues with some + distributions of MinGW. + - Add support for dithering to format conversion. + - Add support for configuring the priority of the worker thread. + - Add a sine wave generator. + - Improve efficiency of sample rate conversion. + - Introduce the notion of standard channel maps. Use mal_get_standard_channel_map(). + - Introduce the notion of default device configurations. A default config uses the same configuration + as the backend's internal device, and as such results in a pass-through data transmission pipeline. + - Add support for passing in NULL for the device config in mal_device_init(), which uses a default + config. This requires manually calling mal_device_set_send/recv_callback(). + - Add support for decoding from raw PCM data (mal_decoder_init_raw(), etc.) + - Make mal_device_init_ex() more robust. + - Make some APIs more const-correct. + - Fix errors with SDL detection on Apple platforms. + - Fix errors with OpenAL detection. + - Fix some memory leaks. + - Fix a bug with opening decoders from memory. + - Early work on SSE2, AVX2 and NEON optimizations. + - Miscellaneous bug fixes. + - Documentation updates. + +v0.7 - 2018-02-25 + - API CHANGE: Change mal_src_read_frames() and mal_dsp_read_frames() to use 64-bit sample counts. + - Add decoder APIs for loading WAV, FLAC, Vorbis and MP3 files. + - Allow opening of devices without a context. + - In this case the context is created and managed internally by the device. + - Change the default channel mapping to the same as that used by FLAC. + - Fix build errors with macOS. + +v0.6c - 2018-02-12 + - Fix build errors with BSD/OSS. + +v0.6b - 2018-02-03 + - Fix some warnings when compiling with Visual C++. + +v0.6a - 2018-01-26 + - Fix errors with channel mixing when increasing the channel count. + - Improvements to the build system for the OpenAL backend. + - Documentation fixes. + +v0.6 - 2017-12-08 + - API CHANGE: Expose and improve mutex APIs. If you were using the mutex APIs before this version you'll + need to update. + - API CHANGE: SRC and DSP callbacks now take a pointer to a mal_src and ma_dsp object respectively. + - API CHANGE: Improvements to event and thread APIs. These changes make these APIs more consistent. + - Add support for SDL and Emscripten. + - Simplify the build system further for when development packages for various backends are not installed. + With this change, when the compiler supports __has_include, backends without the relevant development + packages installed will be ignored. This fixes the build for old versions of MinGW. + - Fixes to the Android build. + - Add mal_convert_frames(). This is a high-level helper API for performing a one-time, bulk conversion of + audio data to a different format. + - Improvements to f32 -> u8/s16/s24/s32 conversion routines. + - Fix a bug where the wrong value is returned from mal_device_start() for the OpenSL backend. + - Fixes and improvements for Raspberry Pi. + - Warning fixes. + +v0.5 - 2017-11-11 + - API CHANGE: The mal_context_init() function now takes a pointer to a ma_context_config object for + configuring the context. The works in the same kind of way as the device config. The rationale for this + change is to give applications better control over context-level properties, add support for backend- + specific configurations, and support extensibility without breaking the API. + - API CHANGE: The alsa.preferPlugHW device config variable has been removed since it's not really useful for + anything anymore. + - ALSA: By default, device enumeration will now only enumerate over unique card/device pairs. Applications + can enable verbose device enumeration by setting the alsa.useVerboseDeviceEnumeration context config + variable. + - ALSA: When opening a device in shared mode (the default), the dmix/dsnoop plugin will be prioritized. If + this fails it will fall back to the hw plugin. With this change the preferExclusiveMode config is now + honored. Note that this does not happen when alsa.useVerboseDeviceEnumeration is set to true (see above) + which is by design. + - ALSA: Add support for excluding the "null" device using the alsa.excludeNullDevice context config variable. + - ALSA: Fix a bug with channel mapping which causes an assertion to fail. + - Fix errors with enumeration when pInfo is set to NULL. + - OSS: Fix a bug when starting a device when the client sends 0 samples for the initial buffer fill. + +v0.4 - 2017-11-05 + - API CHANGE: The log callback is now per-context rather than per-device and as is thus now passed to + mal_context_init(). The rationale for this change is that it allows applications to capture diagnostic + messages at the context level. Previously this was only available at the device level. + - API CHANGE: The device config passed to mal_device_init() is now const. + - Added support for OSS which enables support on BSD platforms. + - Added support for WinMM (waveOut/waveIn). + - Added support for UWP (Universal Windows Platform) applications. Currently C++ only. + - Added support for exclusive mode for selected backends. Currently supported on WASAPI. + - POSIX builds no longer require explicit linking to libpthread (-lpthread). + - ALSA: Explicit linking to libasound (-lasound) is no longer required. + - ALSA: Latency improvements. + - ALSA: Use MMAP mode where available. This can be disabled with the alsa.noMMap config. + - ALSA: Use "hw" devices instead of "plughw" devices by default. This can be disabled with the + alsa.preferPlugHW config. + - WASAPI is now the highest priority backend on Windows platforms. + - Fixed an error with sample rate conversion which was causing crackling when capturing. + - Improved error handling. + - Improved compiler support. + - Miscellaneous bug fixes. + +v0.3 - 2017-06-19 + - API CHANGE: Introduced the notion of a context. The context is the highest level object and is required for + enumerating and creating devices. Now, applications must first create a context, and then use that to + enumerate and create devices. The reason for this change is to ensure device enumeration and creation is + tied to the same backend. In addition, some backends are better suited to this design. + - API CHANGE: Removed the rewinding APIs because they're too inconsistent across the different backends, hard + to test and maintain, and just generally unreliable. + - Added helper APIs for initializing mal_device_config objects. + - Null Backend: Fixed a crash when recording. + - Fixed build for UWP. + - Added support for f32 formats to the OpenSL|ES backend. + - Added initial implementation of the WASAPI backend. + - Added initial implementation of the OpenAL backend. + - Added support for low quality linear sample rate conversion. + - Added early support for basic channel mapping. + +v0.2 - 2016-10-28 + - API CHANGE: Add user data pointer as the last parameter to mal_device_init(). The rationale for this + change is to ensure the logging callback has access to the user data during initialization. + - API CHANGE: Have device configuration properties be passed to mal_device_init() via a structure. Rationale: + 1) The number of parameters is just getting too much. + 2) It makes it a bit easier to add new configuration properties in the future. In particular, there's a + chance there will be support added for backend-specific properties. + - Dropped support for f64, A-law and Mu-law formats since they just aren't common enough to justify the + added maintenance cost. + - DirectSound: Increased the default buffer size for capture devices. + - Added initial implementation of the OpenSL|ES backend. + +v0.1 - 2016-10-21 + - Initial versioned release. +*/ + + +/* +This software is available as a choice of the following licenses. Choose +whichever you prefer. + +=============================================================================== +ALTERNATIVE 1 - 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. + +For more information, please refer to + +=============================================================================== +ALTERNATIVE 2 - MIT No Attribution +=============================================================================== +Copyright 2019 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 +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. + +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. +*/ diff --git a/raylib/external/par_shapes.h b/raylib/external/par_shapes.h index 39831c8..af06804 100644 --- a/raylib/external/par_shapes.h +++ b/raylib/external/par_shapes.h @@ -10,7 +10,7 @@ // In addition to the comment block above each function declaration, the API // has informal documentation here: // -// http://github.prideout.net/shapes/ +// https://prideout.net/shapes // // For our purposes, a "mesh" is a list of points and a list of triangles; the // former is a flattened list of three-tuples (32-bit floats) and the latter is diff --git a/raylib/external/rgif.h b/raylib/external/rgif.h index 44ee13b..ae7db35 100644 --- a/raylib/external/rgif.h +++ b/raylib/external/rgif.h @@ -1,6 +1,8 @@ /********************************************************************************************** * -* rgif.h original implementation (gif.h) by Charlie Tangora [ctangora -at- gmail -dot- com] +* 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. @@ -32,7 +34,7 @@ * * ALTERNATIVE A - MIT License * -* Copyright (c) 2017 Ramon Santamaria +* 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 @@ -76,8 +78,8 @@ * **********************************************************************************************/ -#ifndef GIF_H -#define GIF_H +#ifndef RGIF_H +#define RGIF_H #include // Required for: FILE @@ -101,7 +103,7 @@ RGIFDEF bool GifBegin(const char *filename, unsigned int width, unsigned int hei RGIFDEF bool GifWriteFrame(const unsigned char *image, unsigned int width, unsigned int height, unsigned int delay, int bitDepth, bool dither); RGIFDEF bool GifEnd(); -#endif // GIF_H +#endif // RGIF_H /*********************************************************************************** @@ -116,23 +118,23 @@ RGIFDEF bool GifEnd(); #include // Required for: memcpy() // Define these macros to hook into a custom memory allocator. -// GIF_TEMP_MALLOC and GIF_TEMP_FREE will only be called in stack fashion - frees in the reverse order of mallocs +// RGIF_TEMP_MALLOC and RGIF_TEMP_FREE will only be called in stack fashion - frees in the reverse order of mallocs // and any temp memory allocated by a function will be freed before it exits. -#if !defined(GIF_TEMP_MALLOC) +#if !defined(RGIF_TEMP_MALLOC) #include - #define GIF_TEMP_MALLOC malloc - #define GIF_TEMP_FREE free + #define RGIF_TEMP_MALLOC malloc + #define RGIF_TEMP_FREE free #endif // Check if custom malloc/free functions defined, if not, using standard ones -// GIF_MALLOC and GIF_FREE are used only by GifBegin and GifEnd respectively, +// 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(GIF_MALLOC) +#if !defined(RGIF_MALLOC) #include // Required for: malloc(), free() - #define GIF_MALLOC(size) malloc(size) - #define GIF_FREE(ptr) free(ptr) + #define RGIF_MALLOC(size) malloc(size) + #define RGIF_FREE(ptr) free(ptr) #endif //---------------------------------------------------------------------------------- @@ -223,7 +225,7 @@ RGIFDEF bool GifBegin(const char *filename, unsigned int width, unsigned int hei if (!gifFile) return false; // Allocate space for one gif frame - gifFrame = (unsigned char *)GIF_MALLOC(width*height*4); + gifFrame = (unsigned char *)RGIF_MALLOC(width*height*4); // GIF Header fputs("GIF89a",gifFile); @@ -300,7 +302,7 @@ RGIFDEF bool GifEnd() fputc(0x3b, gifFile); // Trailer (end of file) fclose(gifFile); - GIF_FREE(gifFrame); + RGIF_FREE(gifFrame); gifFile = NULL; gifFrame = NULL; @@ -589,7 +591,7 @@ static void GifMakePalette(const unsigned char *lastFrame, const unsigned char * // 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*)GIF_TEMP_MALLOC(imageSize); + unsigned char *destroyableImage = (unsigned char*)RGIF_TEMP_MALLOC(imageSize); memcpy(destroyableImage, nextFrame, imageSize); int numPixels = width*height; @@ -602,7 +604,7 @@ static void GifMakePalette(const unsigned char *lastFrame, const unsigned char * GifSplitPalette(destroyableImage, numPixels, 1, lastElt, splitElt, splitDist, 1, buildForDither, pPal); - GIF_TEMP_FREE(destroyableImage); + RGIF_TEMP_FREE(destroyableImage); // add the bottom node for the transparency index pPal->treeSplit[1 << (bitDepth-1)] = 0; @@ -619,7 +621,7 @@ static void GifDitherImage(const unsigned char *lastFrame, const unsigned char * // 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*)GIF_TEMP_MALLOC(sizeof(int)*numPixels*4); + int *quantPixels = (int*)RGIF_TEMP_MALLOC(sizeof(int)*numPixels*4); for (int ii=0; ii typedef unsigned char stbi_uc; typedef unsigned short stbi_us; @@ -325,11 +341,13 @@ typedef unsigned short stbi_us; extern "C" { #endif +#ifndef STBIDEF #ifdef STB_IMAGE_STATIC #define STBIDEF static #else #define STBIDEF extern #endif +#endif ////////////////////////////////////////////////////////////////////////////// // @@ -354,10 +372,6 @@ typedef struct STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); -#ifndef STBI_NO_GIF -STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); -#endif - #ifndef STBI_NO_STDIO STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); @@ -365,6 +379,14 @@ STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in // for stbi_load_from_file, file pointer is left pointing immediately after image #endif +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +#endif + +#ifdef STBI_WINDOWS_UTF8 +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif + //////////////////////////////////// // // 16-bits-per-channel interface @@ -524,6 +546,12 @@ STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const ch #define STBI_ASSERT(x) assert(x) #endif +#ifdef __cplusplus +#define STBI_EXTERN extern "C" +#else +#define STBI_EXTERN extern +#endif + #ifndef _MSC_VER #ifdef __cplusplus @@ -648,14 +676,18 @@ static int stbi__cpuid3(void) #define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) static int stbi__sse2_available(void) { int info3 = stbi__cpuid3(); return ((info3 >> 26) & 1) != 0; } +#endif + #else // assume GCC-style if not VC++ #define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) static int stbi__sse2_available(void) { // If we're even attempting to compile this on GCC/Clang, that means @@ -663,6 +695,8 @@ static int stbi__sse2_available(void) // instructions at will, and so are we. return 1; } +#endif + #endif #endif @@ -1069,6 +1103,7 @@ static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) } } +#ifndef STBI_NO_GIF static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) { int slice; @@ -1080,6 +1115,7 @@ static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int byt bytes += slice_size; } } +#endif static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) { @@ -1130,7 +1166,7 @@ static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, return (stbi__uint16 *) result; } -#if !defined(STBI_NO_HDR) || !defined(STBI_NO_LINEAR) +#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) { if (stbi__vertically_flip_on_load && result != NULL) { @@ -1142,10 +1178,38 @@ static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, in #ifndef STBI_NO_STDIO +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) +STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); +#endif + +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + static FILE *stbi__fopen(char const *filename, char const *mode) { FILE *f; -#if defined(_MSC_VER) && _MSC_VER >= 1400 +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode))) + return 0; + +#if _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 if (0 != fopen_s(&f, filename, mode)) f=0; #else @@ -1538,18 +1602,18 @@ static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int r // convert source image with img_n components to one with req_comp components; // avoid switch per pixel, so use switch per scanline and massive macros switch (STBI__COMBO(img_n, req_comp)) { - STBI__CASE(1,2) { dest[0]=src[0], dest[1]=255; } break; + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; STBI__CASE(2,1) { dest[0]=src[0]; } break; STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; - STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; - STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; 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; + 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); } #undef STBI__CASE @@ -1587,18 +1651,18 @@ static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int r // convert source image with img_n components to one with req_comp components; // avoid switch per pixel, so use switch per scanline and massive macros switch (STBI__COMBO(img_n, req_comp)) { - STBI__CASE(1,2) { dest[0]=src[0], dest[1]=0xffff; } break; + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=0xffff; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; STBI__CASE(2,1) { dest[0]=src[0]; } break; STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; - STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=0xffff; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; - STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]), dest[1] = 0xffff; } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; 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; + 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); } #undef STBI__CASE @@ -1622,7 +1686,11 @@ static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) for (k=0; k < n; ++k) { output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); } - if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; + } + if (n < comp) { + for (i=0; i < x*y; ++i) { + output[i*comp + n] = data[i*comp + n]/255.0f; + } } STBI_FREE(data); return output; @@ -3595,7 +3663,7 @@ static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp int k; unsigned int i,j; stbi_uc *output; - stbi_uc *coutput[4]; + stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; stbi__resample res_comp[4]; @@ -3716,7 +3784,7 @@ static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp if (n == 1) for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; else - for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255; + for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } } } } @@ -4730,7 +4798,7 @@ static void stbi__de_iphone(stbi__png *z) static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) { stbi_uc palette[1024], pal_img_n=0; - stbi_uc has_trans=0, tc[3]; + stbi_uc has_trans=0, tc[3]={0}; stbi__uint16 tc16[3]; stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; int first=1,k,interlace=0, color=0, is_iphone=0; @@ -5008,11 +5076,11 @@ static int stbi__high_bit(unsigned int z) { int n=0; if (z == 0) return -1; - if (z >= 0x10000) n += 16, z >>= 16; - if (z >= 0x00100) n += 8, z >>= 8; - if (z >= 0x00010) n += 4, z >>= 4; - if (z >= 0x00004) n += 2, z >>= 2; - if (z >= 0x00002) n += 1, z >>= 1; + if (z >= 0x10000) { n += 16; z >>= 16; } + if (z >= 0x00100) { n += 8; z >>= 8; } + if (z >= 0x00010) { n += 4; z >>= 4; } + if (z >= 0x00004) { n += 2; z >>= 2; } + if (z >= 0x00002) { n += 1;/* >>= 1;*/ } return n; } @@ -5029,7 +5097,7 @@ static int stbi__bitcount(unsigned int a) // extract an arbitrarily-aligned N-bit value (N=bits) // from v, and then make it 8-bits long and fractionally // extend it to full full range. -static int stbi__shiftsigned(int v, int shift, int bits) +static int stbi__shiftsigned(unsigned int v, int shift, int bits) { static unsigned int mul_table[9] = { 0, @@ -5170,7 +5238,10 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req psize = (info.offset - 14 - info.hsz) >> 2; } - s->img_n = ma ? 4 : 3; + if (info.bpp == 24 && ma == 0xff000000) + s->img_n = 3; + else + s->img_n = ma ? 4 : 3; if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 target = req_comp; else @@ -5206,6 +5277,8 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req out[z++] = pal[color][0]; out[z++] = pal[color][1]; out[z++] = pal[color][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; if((--bit_offset) < 0) { bit_offset = 7; v = stbi__get8(s); @@ -5298,7 +5371,7 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req stbi_uc *p1 = out + j *s->img_x*target; stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; for (i=0; i < (int) s->img_x*target; ++i) { - t = p1[i], p1[i] = p2[i], p2[i] = t; + t = p1[i]; p1[i] = p2[i]; p2[i] = t; } } } @@ -5478,6 +5551,8 @@ static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req int RLE_repeating = 0; int read_next_pixel = 1; STBI_NOTUSED(ri); + STBI_NOTUSED(tga_x_origin); // @TODO + STBI_NOTUSED(tga_y_origin); // @TODO // do a tiny bit of precessing if ( tga_image_type >= 8 ) @@ -5641,6 +5716,7 @@ static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req // Microsoft's C compilers happy... [8^( tga_palette_start = tga_palette_len = tga_palette_bits = tga_x_origin = tga_y_origin = 0; + STBI_NOTUSED(tga_palette_start); // OK, done return tga_data; } @@ -5788,7 +5864,7 @@ static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req // Else if n is 128, noop. // Endloop - // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, + // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, // which we're going to just skip. stbi__skip(s, h * channelCount * 2 ); @@ -6341,22 +6417,27 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i int first_frame; int pi; int pcount; + STBI_NOTUSED(req_comp); // on first frame, any non-written pixels get the background colour (non-transparent) first_frame = 0; if (g->out == 0) { - if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header - g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h); - g->background = (stbi_uc *) stbi__malloc(4 * g->w * g->h); - g->history = (stbi_uc *) stbi__malloc(g->w * g->h); - if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) + return stbi__errpuc("too large", "GIF image is too large"); + pcount = g->w * g->h; + g->out = (stbi_uc *) stbi__malloc(4 * pcount); + g->background = (stbi_uc *) stbi__malloc(4 * pcount); + g->history = (stbi_uc *) stbi__malloc(pcount); + if (!g->out || !g->background || !g->history) + return stbi__errpuc("outofmem", "Out of memory"); - // image is treated as "tranparent" at the start - ie, nothing overwrites the current background; + // image is treated as "transparent" at the start - ie, nothing overwrites the current background; // background colour is only used for pixels that are not rendered first frame, after that "background" - // color refers to teh color that was there the previous frame. - memset( g->out, 0x00, 4 * g->w * g->h ); - memset( g->background, 0x00, 4 * g->w * g->h ); // state of the background (starts transparent) - memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + // color refers to the color that was there the previous frame. + memset(g->out, 0x00, 4 * pcount); + memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) + 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? @@ -6417,6 +6498,13 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i g->cur_x = g->start_x; g->cur_y = g->start_y; + // if the width of the specified rectangle is 0, that means + // we may not see *any* pixels or the image is malformed; + // to make sure this is caught, move the current y down to + // max_y (which is what out_gif_code checks). + if (w == 0) + g->cur_y = g->max_y; + g->lflags = stbi__get8(s); if (g->lflags & 0x40) { @@ -6436,7 +6524,7 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i return stbi__errpuc("missing color table", "Corrupt GIF"); o = stbi__process_gif_raster(s, g); - if (o == NULL) return NULL; + if (!o) return NULL; // if this was the first frame, pcount = g->w * g->h; @@ -6564,6 +6652,7 @@ static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req stbi_uc *u = 0; stbi__gif g; memset(&g, 0, sizeof(g)); + STBI_NOTUSED(ri); u = stbi__gif_load_next(s, &g, comp, req_comp, 0); if (u == (stbi_uc *) s) u = 0; // end of animated gif marker @@ -6575,6 +6664,9 @@ static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req // can be done for multiple frames. if (req_comp && req_comp != 4) u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } else if (g.out) { + // if there was an error and we allocated an image buffer, free it! + STBI_FREE(g.out); } // free buffers needed for multiple frame loading; @@ -6851,7 +6943,12 @@ static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) return 0; if (x) *x = s->img_x; if (y) *y = s->img_y; - if (comp) *comp = info.ma ? 4 : 3; + if (comp) { + if (info.bpp == 24 && info.ma == 0xff000000) + *comp = 3; + else + *comp = info.ma ? 4 : 3; + } return 1; } #endif @@ -6894,7 +6991,7 @@ static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) static int stbi__psd_is16(stbi__context *s) { - int channelCount, dummy, depth; + int channelCount, depth; if (stbi__get32be(s) != 0x38425053) { stbi__rewind( s ); return 0; @@ -6909,8 +7006,8 @@ static int stbi__psd_is16(stbi__context *s) stbi__rewind( s ); return 0; } - dummy = stbi__get32be(s); - dummy = stbi__get32be(s); + (void) stbi__get32be(s); + (void) stbi__get32be(s); depth = stbi__get16be(s); if (depth != 16) { stbi__rewind( s ); @@ -7237,6 +7334,9 @@ STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user /* revision history: + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug 1-bit BMP *_is_16_bit api diff --git a/raylib/external/stb_image_resize.h b/raylib/external/stb_image_resize.h index 031ca99..4f6ad35 100644 --- a/raylib/external/stb_image_resize.h +++ b/raylib/external/stb_image_resize.h @@ -1,4 +1,4 @@ -/* stb_image_resize - v0.95 - public domain image resizing +/* stb_image_resize - v0.96 - public domain image resizing by Jorge L Rodriguez (@VinoBS) - 2014 http://github.com/nothings/stb @@ -159,6 +159,7 @@ Nathan Reed: warning fixes REVISIONS + 0.96 (2019-03-04) fixed warnings 0.95 (2017-07-23) fixed warnings 0.94 (2017-03-18) fixed warnings 0.93 (2017-03-03) fixed bug with certain combinations of heights @@ -193,6 +194,7 @@ typedef uint16_t stbir_uint16; typedef uint32_t stbir_uint32; #endif +#ifndef STBIRDEF #ifdef STB_IMAGE_RESIZE_STATIC #define STBIRDEF static #else @@ -202,7 +204,7 @@ typedef uint32_t stbir_uint32; #define STBIRDEF extern #endif #endif - +#endif ////////////////////////////////////////////////////////////////////////////// // @@ -2324,8 +2326,9 @@ static int stbir__resize_allocated(stbir__info *info, if (alpha_channel < 0) flags |= STBIR_FLAG_ALPHA_USES_COLORSPACE | STBIR_FLAG_ALPHA_PREMULTIPLIED; - if (!(flags&STBIR_FLAG_ALPHA_USES_COLORSPACE) || !(flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)) + if (!(flags&STBIR_FLAG_ALPHA_USES_COLORSPACE) || !(flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)) { STBIR_ASSERT(alpha_channel >= 0 && alpha_channel < info->channels); + } if (alpha_channel >= info->channels) return 0; diff --git a/raylib/external/stb_image_write.h b/raylib/external/stb_image_write.h index eaf5093..a9bf66c 100644 --- a/raylib/external/stb_image_write.h +++ b/raylib/external/stb_image_write.h @@ -1,4 +1,4 @@ -/* stb_image_write - v1.08 - public domain - http://nothings.org/stb/stb_image_write.h +/* stb_image_write - v1.13 - 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 @@ -10,15 +10,9 @@ Will probably not work correctly with strict-aliasing optimizations. - If using a modern Microsoft Compiler, non-safe versions of CRT calls may cause - compilation warnings or even errors. To avoid this, also before #including, - - #define STBI_MSC_SECURE_CRT - ABOUT: - This header file is a library for writing images to C stdio. It could be - adapted to write to memory or a general streaming interface; let me know. + This header file is a library for writing images to C stdio or a callback. The PNG output is not optimal; it is 20-50% larger than the file written by a decent optimizing implementation; though providing a custom @@ -38,6 +32,14 @@ BUILDING: The returned data will be freed with STBIW_FREE() (free() by default), so it must be heap allocated with STBIW_MALLOC() (malloc() by default), +UNICODE: + + If compiling for Windows and you wish to use Unicode filenames, compile + with + #define STBIW_WINDOWS_UTF8 + and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert + Windows wchar_t filenames to utf8. + USAGE: There are five functions, one for each image file format: @@ -45,8 +47,8 @@ USAGE: int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); - int stbi_write_jpg(char const *filename, int w, int h, int comp, const float *data, int quality); void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically @@ -95,7 +97,7 @@ USAGE: at the end of the line.) PNG allows you to set the deflate compression level by setting the global - variable 'stbi_write_png_level' (it defaults to 8). + variable 'stbi_write_png_compression_level' (it defaults to 8). HDR expects linear float data. Since the format is always 32-bit rgb(e) data, alpha (if provided) is discarded, and for monochrome data it is @@ -133,7 +135,12 @@ CREDITS: github:poppolopoppo Patrick Boettcher github:xeekworx - + Cap Petschulat + Simon Rodriguez + Ivan Tikhonov + github:ignotion + Adam Schackart + LICENSE See end of file for license information. @@ -143,19 +150,26 @@ LICENSE #ifndef INCLUDE_STB_IMAGE_WRITE_H #define INCLUDE_STB_IMAGE_WRITE_H -#ifdef __cplusplus -extern "C" { -#endif +#include +// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' +#ifndef STBIWDEF #ifdef STB_IMAGE_WRITE_STATIC -#define STBIWDEF static +#define STBIWDEF static #else -#define STBIWDEF extern +#ifdef __cplusplus +#define STBIWDEF extern "C" +#else +#define STBIWDEF extern +#endif +#endif #endif -STBIWDEF int stbi_write_tga_with_rle; -STBIWDEF int stbi_write_png_comperssion_level; -STBIWDEF int stbi_write_force_png_filter; +#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations +extern int stbi_write_tga_with_rle; +extern int stbi_write_png_compression_level; +extern int stbi_write_force_png_filter; +#endif #ifndef STBI_WRITE_NO_STDIO STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); @@ -163,6 +177,10 @@ STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); + +#ifdef STBI_WINDOWS_UTF8 +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif #endif typedef void stbi_write_func(void *context, void *data, int size); @@ -175,10 +193,6 @@ STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); -#ifdef __cplusplus -} -#endif - #endif//INCLUDE_STB_IMAGE_WRITE_H #ifdef STB_IMAGE_WRITE_IMPLEMENTATION @@ -233,8 +247,8 @@ STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); #define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) #ifdef STB_IMAGE_WRITE_STATIC -static stbi__flip_vertically_on_write=0; -static int stbi_write_png_compression level = 8; +static int stbi__flip_vertically_on_write=0; +static int stbi_write_png_compression_level = 8; static int stbi_write_tga_with_rle = 1; static int stbi_write_force_png_filter = -1; #else @@ -269,15 +283,52 @@ static void stbi__stdio_write(void *context, void *data, int size) fwrite(data,1,size,(FILE*) context); } -static int stbi__start_write_file(stbi__write_context *s, const char *filename) +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) +#ifdef __cplusplus +#define STBIW_EXTERN extern "C" +#else +#define STBIW_EXTERN extern +#endif +STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); + +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbiw__fopen(char const *filename, char const *mode) { FILE *f; -#ifdef STBI_MSC_SECURE_CRT - if (fopen_s(&f, filename, "wb")) - f = NULL; +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode))) + return 0; + +#if _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; #else - f = fopen(filename, "wb"); + f = _wfopen(wFilename, wMode); #endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + +static int stbi__start_write_file(stbi__write_context *s, const char *filename) +{ + FILE *f = stbiw__fopen(filename, "wb"); stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); return f != NULL; } @@ -337,7 +388,7 @@ static void stbiw__putc(stbi__write_context *s, unsigned char c) 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; + arr[0] = a; arr[1] = b; arr[2] = c; s->func(s->context, arr, 3); } @@ -385,10 +436,11 @@ static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, i if (stbi__flip_vertically_on_write) vdir *= -1; - if (vdir < 0) - j_end = -1, j = y-1; - else - j_end = y, j = 0; + if (vdir < 0) { + j_end = -1; j = y-1; + } else { + j_end = y; j = 0; + } for (; j != j_end; j += vdir) { for (i=0; i < x; ++i) { @@ -546,7 +598,7 @@ STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const #define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) -void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) +static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) { int exponent; float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); @@ -563,7 +615,7 @@ void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) } } -void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) +static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) { unsigned char lengthbyte = STBIW_UCHAR(length+128); STBIW_ASSERT(length+128 <= 255); @@ -571,7 +623,7 @@ void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char dat s->func(s->context, &databyte, 1); } -void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) +static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) { unsigned char lengthbyte = STBIW_UCHAR(length); STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code @@ -579,7 +631,7 @@ void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *d s->func(s->context, data, length); } -void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) +static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) { unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; unsigned char rgbe[4]; @@ -680,15 +732,15 @@ static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, f char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; s->func(s->context, header, sizeof(header)-1); -#ifdef STBI_MSC_SECURE_CRT - len = sprintf_s(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#ifdef __STDC_WANT_SECURE_LIB__ + len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); #else len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); #endif s->func(s->context, buffer, len); for(i=0; i < y; i++) - stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)*x); + stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)); STBIW_FREE(scratch); return 1; } @@ -803,7 +855,7 @@ static unsigned int stbiw__zhash(unsigned char *data) #endif // STBIW_ZLIB_COMPRESS -unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) { #ifdef STBIW_ZLIB_COMPRESS // user provided a zlib compress implementation, use that @@ -816,7 +868,7 @@ unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_l unsigned int bitbuf=0; int i,j, bitcount=0; unsigned char *out = NULL; - unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(char**)); + unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**)); if (hash_table == NULL) return NULL; if (quality < 5) quality = 5; @@ -839,7 +891,7 @@ unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_l for (j=0; j < n; ++j) { if (hlist[j]-data > i-32768) { // if entry lies within window int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); - if (d >= best) best=d,bestloc=hlist[j]; + if (d >= best) { best=d; bestloc=hlist[j]; } } } // when hash table entry is too long, delete half the entries @@ -898,8 +950,8 @@ unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_l int blocklen = (int) (data_len % 5552); j=0; while (j < data_len) { - for (i=0; i < blocklen; ++i) s1 += data[j+i], s2 += s1; - s1 %= 65521, s2 %= 65521; + for (i=0; i < blocklen; ++i) { s1 += data[j+i]; s2 += s1; } + s1 %= 65521; s2 %= 65521; j += blocklen; blocklen = 5552; } @@ -917,6 +969,9 @@ unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_l static unsigned int stbiw__crc32(unsigned char *buffer, int len) { +#ifdef STBIW_CRC32 + return STBIW_CRC32(buffer, len); +#else static unsigned int crc_table[256] = { 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, @@ -958,6 +1013,7 @@ static unsigned int stbiw__crc32(unsigned char *buffer, int len) for (i=0; i < len; ++i) crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; return ~crc; +#endif } #define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) @@ -987,31 +1043,35 @@ static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int int i; int type = mymap[filter_type]; unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); + int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; + + if (type==0) { + memcpy(line_buffer, z, width*n); + return; + } + + // first loop isn't optimized since it's just one pixel for (i = 0; i < n; ++i) { switch (type) { - case 0: line_buffer[i] = z[i]; break; case 1: line_buffer[i] = z[i]; break; - case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; - case 3: line_buffer[i] = z[i] - (z[i-stride_bytes]>>1); break; - case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-stride_bytes],0)); break; + case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break; case 5: line_buffer[i] = z[i]; break; case 6: line_buffer[i] = z[i]; break; } } - for (i=n; i < width*n; ++i) { - switch (type) { - case 0: line_buffer[i] = z[i]; break; - case 1: line_buffer[i] = z[i] - z[i-n]; break; - case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; - case 3: line_buffer[i] = z[i] - ((z[i-n] + z[i-stride_bytes])>>1); break; - case 4: line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-stride_bytes], z[i-stride_bytes-n]); break; - case 5: line_buffer[i] = z[i] - (z[i-n]>>1); break; - case 6: line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; - } + switch (type) { + case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break; + case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break; + case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break; + case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; } } -unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) { int force_filter = stbi_write_force_png_filter; int ctype[5] = { -1, 0, 4, 2, 6 }; @@ -1033,11 +1093,11 @@ unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, in int filter_type; if (force_filter > -1) { filter_type = force_filter; - stbiw__encode_png_line(pixels, stride_bytes, x, y, j, n, force_filter, line_buffer); + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); } else { // Estimate the best filter by running through all of them: int best_filter = 0, best_filter_val = 0x7fffffff, est, i; for (filter_type = 0; filter_type < 5; filter_type++) { - stbiw__encode_png_line(pixels, stride_bytes, x, y, j, n, filter_type, line_buffer); + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); // Estimate the entropy of the line using this filter; the less, the better. est = 0; @@ -1050,7 +1110,7 @@ unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, in } } if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it - stbiw__encode_png_line(pixels, stride_bytes, x, y, j, n, best_filter, line_buffer); + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); filter_type = best_filter; } } @@ -1102,14 +1162,10 @@ STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const { FILE *f; int len; - unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); if (png == NULL) return 0; -#ifdef STBI_MSC_SECURE_CRT - if (fopen_s(&f, filename, "wb")) - f = NULL; -#else - f = fopen(filename, "wb"); -#endif + + f = stbiw__fopen(filename, "wb"); if (!f) { STBIW_FREE(png); return 0; } fwrite(png, 1, len, f); fclose(f); @@ -1121,7 +1177,7 @@ STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) { int len; - unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); if (png == NULL) return 0; func(context, png, len); STBIW_FREE(png); @@ -1416,15 +1472,13 @@ static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, in for(x = 0; x < width; x += 8) { float YDU[64], UDU[64], VDU[64]; for(row = y, pos = 0; row < y+8; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; for(col = x; col < x+8; ++col, ++pos) { - int p = (stbi__flip_vertically_on_write ? height-1-row : row)*width*comp + col*comp; float r, g, b; - if(row >= height) { - p -= width*comp*(row+1 - height); - } - if(col >= width) { - p -= comp*(col+1 - width); - } + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; r = imageData[p+0]; g = imageData[p+ofsG]; @@ -1476,6 +1530,12 @@ STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const #endif // STB_IMAGE_WRITE_IMPLEMENTATION /* Revision history + 1.11 (2019-08-11) + + 1.10 (2019-02-07) + support utf8 filenames in Windows; fix warnings and platform ifdefs + 1.09 (2018-02-11) + fix typo in zlib quality API, improve STB_I_W_STATIC in C++ 1.08 (2018-01-29) add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter 1.07 (2017-07-24) diff --git a/raylib/external/stb_perlin.h b/raylib/external/stb_perlin.h index 5d76222..941773f 100644 --- a/raylib/external/stb_perlin.h +++ b/raylib/external/stb_perlin.h @@ -1,4 +1,4 @@ -// stb_perlin.h - v0.3 - perlin noise +// stb_perlin.h - v0.4 - perlin noise // public domain single-file C implementation by Sean Barrett // // LICENSE @@ -32,6 +32,19 @@ // details of the implementation, even if you ask for larger or no // wrapping.) // +// float stb_perlin_noise3_seed( float x, +// float y, +// float z, +// int x_wrap=0, +// int y_wrap=0, +// int z_wrap=0, +// int seed) +// +// As above, but 'seed' selects from multiple different variations of the +// noise function. The current implementation only uses the bottom 8 bits +// of 'seed', but possibly in the future more bits will be used. +// +// // Fractal Noise: // // Three common fractal noise functions are included, which produce @@ -40,16 +53,13 @@ // 'octaves' times, so this parameter will affect runtime. // // float stb_perlin_ridge_noise3(float x, float y, float z, -// float lacunarity, float gain, float offset, int octaves, -// int x_wrap, int y_wrap, int z_wrap); +// float lacunarity, float gain, float offset, int octaves) // // float stb_perlin_fbm_noise3(float x, float y, float z, -// float lacunarity, float gain, int octaves, -// int x_wrap, int y_wrap, int z_wrap); +// float lacunarity, float gain, int octaves) // // float stb_perlin_turbulence_noise3(float x, float y, float z, -// float lacunarity, float gain,int octaves, -// int x_wrap, int y_wrap, int z_wrap); +// float lacunarity, float gain, int octaves) // // Typical values to start playing with: // octaves = 6 -- number of "octaves" of noise3() to sum @@ -60,6 +70,7 @@ // // Contributors: // Jack Mott - additional noise functions +// Jordan Peck - seeded noise // @@ -67,18 +78,20 @@ extern "C" { #endif extern float stb_perlin_noise3(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap); -extern float stb_perlin_ridge_noise3(float x, float y, float z,float lacunarity, float gain, float offset, int octaves,int x_wrap, int y_wrap, int z_wrap); -extern float stb_perlin_fbm_noise3(float x, float y, float z,float lacunarity, float gain, int octaves,int x_wrap, int y_wrap, int z_wrap); -extern float stb_perlin_turbulence_noise3(float x, float y, float z, float lacunarity, float gain, int octaves,int x_wrap, int y_wrap, int z_wrap); +extern float stb_perlin_ridge_noise3(float x, float y, float z, float lacunarity, float gain, float offset, int octaves); +extern float stb_perlin_fbm_noise3(float x, float y, float z, float lacunarity, float gain, int octaves); +extern float stb_perlin_turbulence_noise3(float x, float y, float z, float lacunarity, float gain, int octaves); +extern float stb_perlin_noise3_wrap_nonpow2(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed); #ifdef __cplusplus } #endif #ifdef STB_PERLIN_IMPLEMENTATION +#include // fabs() + // not same permutation table as Perlin's reference to avoid copyright issues; // Perlin's table can be found at http://mrl.nyu.edu/~perlin/noise/ -// @OPTIMIZE: should this be unsigned char instead of int for cache? static unsigned char stb__perlin_randtab[512] = { 23, 125, 161, 52, 103, 117, 70, 37, 247, 101, 203, 169, 124, 126, 44, 123, @@ -115,6 +128,51 @@ static unsigned char stb__perlin_randtab[512] = 131, 11, 163, 99, 234, 81, 227, 147, 156, 176, 17, 142, 69, 12, 110, 62, 27, 255, 0, 194, 59, 116, 242, 252, 19, 21, 187, 53, 207, 129, 64, 135, 61, 40, 167, 237, 102, 223, 106, 159, 197, 189, 215, 137, 36, 32, 22, 5, +}; + + +// perlin's gradient has 12 cases so some get used 1/16th of the time +// and some 2/16ths. We reduce bias by changing those fractions +// to 5/64ths and 6/64ths + +// this array is designed to match the previous implementation +// of gradient hash: indices[stb__perlin_randtab[i]&63] +static unsigned char stb__perlin_randtab_grad_idx[512] = +{ + 7, 9, 5, 0, 11, 1, 6, 9, 3, 9, 11, 1, 8, 10, 4, 7, + 8, 6, 1, 5, 3, 10, 9, 10, 0, 8, 4, 1, 5, 2, 7, 8, + 7, 11, 9, 10, 1, 0, 4, 7, 5, 0, 11, 6, 1, 4, 2, 8, + 8, 10, 4, 9, 9, 2, 5, 7, 9, 1, 7, 2, 2, 6, 11, 5, + 5, 4, 6, 9, 0, 1, 1, 0, 7, 6, 9, 8, 4, 10, 3, 1, + 2, 8, 8, 9, 10, 11, 5, 11, 11, 2, 6, 10, 3, 4, 2, 4, + 9, 10, 3, 2, 6, 3, 6, 10, 5, 3, 4, 10, 11, 2, 9, 11, + 1, 11, 10, 4, 9, 4, 11, 0, 4, 11, 4, 0, 0, 0, 7, 6, + 10, 4, 1, 3, 11, 5, 3, 4, 2, 9, 1, 3, 0, 1, 8, 0, + 6, 7, 8, 7, 0, 4, 6, 10, 8, 2, 3, 11, 11, 8, 0, 2, + 4, 8, 3, 0, 0, 10, 6, 1, 2, 2, 4, 5, 6, 0, 1, 3, + 11, 9, 5, 5, 9, 6, 9, 8, 3, 8, 1, 8, 9, 6, 9, 11, + 10, 7, 5, 6, 5, 9, 1, 3, 7, 0, 2, 10, 11, 2, 6, 1, + 3, 11, 7, 7, 2, 1, 7, 3, 0, 8, 1, 1, 5, 0, 6, 10, + 11, 11, 0, 2, 7, 0, 10, 8, 3, 5, 7, 1, 11, 1, 0, 7, + 9, 0, 11, 5, 10, 3, 2, 3, 5, 9, 7, 9, 8, 4, 6, 5, + + // and a second copy so we don't need an extra mask or static initializer + 7, 9, 5, 0, 11, 1, 6, 9, 3, 9, 11, 1, 8, 10, 4, 7, + 8, 6, 1, 5, 3, 10, 9, 10, 0, 8, 4, 1, 5, 2, 7, 8, + 7, 11, 9, 10, 1, 0, 4, 7, 5, 0, 11, 6, 1, 4, 2, 8, + 8, 10, 4, 9, 9, 2, 5, 7, 9, 1, 7, 2, 2, 6, 11, 5, + 5, 4, 6, 9, 0, 1, 1, 0, 7, 6, 9, 8, 4, 10, 3, 1, + 2, 8, 8, 9, 10, 11, 5, 11, 11, 2, 6, 10, 3, 4, 2, 4, + 9, 10, 3, 2, 6, 3, 6, 10, 5, 3, 4, 10, 11, 2, 9, 11, + 1, 11, 10, 4, 9, 4, 11, 0, 4, 11, 4, 0, 0, 0, 7, 6, + 10, 4, 1, 3, 11, 5, 3, 4, 2, 9, 1, 3, 0, 1, 8, 0, + 6, 7, 8, 7, 0, 4, 6, 10, 8, 2, 3, 11, 11, 8, 0, 2, + 4, 8, 3, 0, 0, 10, 6, 1, 2, 2, 4, 5, 6, 0, 1, 3, + 11, 9, 5, 5, 9, 6, 9, 8, 3, 8, 1, 8, 9, 6, 9, 11, + 10, 7, 5, 6, 5, 9, 1, 3, 7, 0, 2, 10, 11, 2, 6, 1, + 3, 11, 7, 7, 2, 1, 7, 3, 0, 8, 1, 1, 5, 0, 6, 10, + 11, 11, 0, 2, 7, 0, 10, 8, 3, 5, 7, 1, 11, 1, 0, 7, + 9, 0, 11, 5, 10, 3, 2, 3, 5, 9, 7, 9, 8, 4, 6, 5, }; static float stb__perlin_lerp(float a, float b, float t) @@ -124,12 +182,12 @@ static float stb__perlin_lerp(float a, float b, float t) static int stb__perlin_fastfloor(float a) { - int ai = (int) a; - return (a < ai) ? ai-1 : ai; + int ai = (int) a; + return (a < ai) ? ai-1 : ai; } // different grad function from Perlin's, but easy to modify to match reference -static float stb__perlin_grad(int hash, float x, float y, float z) +static float stb__perlin_grad(int grad_idx, float x, float y, float z) { static float basis[12][4] = { @@ -147,26 +205,11 @@ static float stb__perlin_grad(int hash, float x, float y, float z) { 0,-1,-1 }, }; - // perlin's gradient has 12 cases so some get used 1/16th of the time - // and some 2/16ths. We reduce bias by changing those fractions - // to 5/64ths and 6/64ths, and the same 4 cases get the extra weight. - static unsigned char indices[64] = - { - 0,1,2,3,4,5,6,7,8,9,10,11, - 0,9,1,11, - 0,1,2,3,4,5,6,7,8,9,10,11, - 0,1,2,3,4,5,6,7,8,9,10,11, - 0,1,2,3,4,5,6,7,8,9,10,11, - 0,1,2,3,4,5,6,7,8,9,10,11, - }; - - // if you use reference permutation table, change 63 below to 15 to match reference - // (this is why the ordering of the table above is funky) - float *grad = basis[indices[hash & 63]]; + float *grad = basis[grad_idx]; return grad[0]*x + grad[1]*y + grad[2]*z; } -float stb_perlin_noise3(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap) +float stb_perlin_noise3_internal(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed) { float u,v,w; float n000,n001,n010,n011,n100,n101,n110,n111; @@ -190,22 +233,22 @@ float stb_perlin_noise3(float x, float y, float z, int x_wrap, int y_wrap, int z y -= py; v = stb__perlin_ease(y); z -= pz; w = stb__perlin_ease(z); - r0 = stb__perlin_randtab[x0]; - r1 = stb__perlin_randtab[x1]; + r0 = stb__perlin_randtab[x0+seed]; + r1 = stb__perlin_randtab[x1+seed]; r00 = stb__perlin_randtab[r0+y0]; r01 = stb__perlin_randtab[r0+y1]; r10 = stb__perlin_randtab[r1+y0]; r11 = stb__perlin_randtab[r1+y1]; - n000 = stb__perlin_grad(stb__perlin_randtab[r00+z0], x , y , z ); - n001 = stb__perlin_grad(stb__perlin_randtab[r00+z1], x , y , z-1 ); - n010 = stb__perlin_grad(stb__perlin_randtab[r01+z0], x , y-1, z ); - n011 = stb__perlin_grad(stb__perlin_randtab[r01+z1], x , y-1, z-1 ); - n100 = stb__perlin_grad(stb__perlin_randtab[r10+z0], x-1, y , z ); - n101 = stb__perlin_grad(stb__perlin_randtab[r10+z1], x-1, y , z-1 ); - n110 = stb__perlin_grad(stb__perlin_randtab[r11+z0], x-1, y-1, z ); - n111 = stb__perlin_grad(stb__perlin_randtab[r11+z1], x-1, y-1, z-1 ); + n000 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z0], x , y , z ); + n001 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z1], x , y , z-1 ); + n010 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z0], x , y-1, z ); + n011 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z1], x , y-1, z-1 ); + n100 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z0], x-1, y , z ); + n101 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z1], x-1, y , z-1 ); + n110 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z0], x-1, y-1, z ); + n111 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z1], x-1, y-1, z-1 ); n00 = stb__perlin_lerp(n000,n001,w); n01 = stb__perlin_lerp(n010,n011,w); @@ -218,7 +261,17 @@ float stb_perlin_noise3(float x, float y, float z, int x_wrap, int y_wrap, int z return stb__perlin_lerp(n0,n1,u); } -float stb_perlin_ridge_noise3(float x, float y, float z,float lacunarity, float gain, float offset, int octaves,int x_wrap, int y_wrap, int z_wrap) +float stb_perlin_noise3(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap) +{ + return stb_perlin_noise3_internal(x,y,z,x_wrap,y_wrap,z_wrap,0); +} + +float stb_perlin_noise3_seed(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, int seed) +{ + return stb_perlin_noise3_internal(x,y,z,x_wrap,y_wrap,z_wrap, (unsigned char) seed); +} + +float stb_perlin_ridge_noise3(float x, float y, float z, float lacunarity, float gain, float offset, int octaves) { int i; float frequency = 1.0f; @@ -227,9 +280,8 @@ float stb_perlin_ridge_noise3(float x, float y, float z,float lacunarity, float float sum = 0.0f; for (i = 0; i < octaves; i++) { - float r = (float)(stb_perlin_noise3(x*frequency,y*frequency,z*frequency,x_wrap,y_wrap,z_wrap)); - r = r<0 ? -r : r; // fabs() - r = offset - r; + float r = stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i); + r = offset - (float) fabs(r); r = r*r; sum += r*amplitude*prev; prev = r; @@ -239,7 +291,7 @@ float stb_perlin_ridge_noise3(float x, float y, float z,float lacunarity, float return sum; } -float stb_perlin_fbm_noise3(float x, float y, float z,float lacunarity, float gain, int octaves,int x_wrap, int y_wrap, int z_wrap) +float stb_perlin_fbm_noise3(float x, float y, float z, float lacunarity, float gain, int octaves) { int i; float frequency = 1.0f; @@ -247,14 +299,14 @@ float stb_perlin_fbm_noise3(float x, float y, float z,float lacunarity, float ga float sum = 0.0f; for (i = 0; i < octaves; i++) { - sum += stb_perlin_noise3(x*frequency,y*frequency,z*frequency,x_wrap,y_wrap,z_wrap)*amplitude; + sum += stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i)*amplitude; frequency *= lacunarity; amplitude *= gain; } return sum; } -float stb_perlin_turbulence_noise3(float x, float y, float z, float lacunarity, float gain, int octaves,int x_wrap, int y_wrap, int z_wrap) +float stb_perlin_turbulence_noise3(float x, float y, float z, float lacunarity, float gain, int octaves) { int i; float frequency = 1.0f; @@ -262,15 +314,74 @@ float stb_perlin_turbulence_noise3(float x, float y, float z, float lacunarity, float sum = 0.0f; for (i = 0; i < octaves; i++) { - float r = stb_perlin_noise3(x*frequency,y*frequency,z*frequency,x_wrap,y_wrap,z_wrap)*amplitude; - r = r<0 ? -r : r; // fabs() - sum += r; + float r = stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i)*amplitude; + sum += (float) fabs(r); frequency *= lacunarity; amplitude *= gain; } return sum; } +float stb_perlin_noise3_wrap_nonpow2(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed) +{ + float u,v,w; + float n000,n001,n010,n011,n100,n101,n110,n111; + float n00,n01,n10,n11; + float n0,n1; + + int px = stb__perlin_fastfloor(x); + int py = stb__perlin_fastfloor(y); + int pz = stb__perlin_fastfloor(z); + int x_wrap2 = (x_wrap ? x_wrap : 256); + int y_wrap2 = (y_wrap ? y_wrap : 256); + int z_wrap2 = (z_wrap ? z_wrap : 256); + int x0 = px % x_wrap2, x1; + int y0 = py % y_wrap2, y1; + int z0 = pz % z_wrap2, z1; + int r0,r1, r00,r01,r10,r11; + + if (x0 < 0) x0 += x_wrap2; + if (y0 < 0) y0 += y_wrap2; + if (z0 < 0) z0 += z_wrap2; + x1 = (x0+1) % x_wrap2; + y1 = (y0+1) % y_wrap2; + z1 = (z0+1) % z_wrap2; + + #define stb__perlin_ease(a) (((a*6-15)*a + 10) * a * a * a) + + x -= px; u = stb__perlin_ease(x); + y -= py; v = stb__perlin_ease(y); + z -= pz; w = stb__perlin_ease(z); + + r0 = stb__perlin_randtab[x0]; + r0 = stb__perlin_randtab[r0+seed]; + r1 = stb__perlin_randtab[x1]; + r1 = stb__perlin_randtab[r1+seed]; + + r00 = stb__perlin_randtab[r0+y0]; + r01 = stb__perlin_randtab[r0+y1]; + r10 = stb__perlin_randtab[r1+y0]; + r11 = stb__perlin_randtab[r1+y1]; + + n000 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z0], x , y , z ); + n001 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z1], x , y , z-1 ); + n010 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z0], x , y-1, z ); + n011 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z1], x , y-1, z-1 ); + n100 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z0], x-1, y , z ); + n101 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z1], x-1, y , z-1 ); + n110 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z0], x-1, y-1, z ); + n111 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z1], x-1, y-1, z-1 ); + + n00 = stb__perlin_lerp(n000,n001,w); + n01 = stb__perlin_lerp(n010,n011,w); + n10 = stb__perlin_lerp(n100,n101,w); + n11 = stb__perlin_lerp(n110,n111,w); + + n0 = stb__perlin_lerp(n00,n01,v); + n1 = stb__perlin_lerp(n10,n11,v); + + return stb__perlin_lerp(n0,n1,u); +} #endif // STB_PERLIN_IMPLEMENTATION /* diff --git a/raylib/external/stb_rect_pack.h b/raylib/external/stb_rect_pack.h index 9faf578..d32c8f9 100644 --- a/raylib/external/stb_rect_pack.h +++ b/raylib/external/stb_rect_pack.h @@ -1,4 +1,4 @@ -// stb_rect_pack.h - v0.11 - public domain - rectangle packing +// stb_rect_pack.h - v1.00 - public domain - rectangle packing // Sean Barrett 2014 // // Useful for e.g. packing rectangular textures into an atlas. @@ -31,9 +31,12 @@ // // Bugfixes / warning fixes // Jeremy Jaussaud +// Fabian Giesen // // Version history: // +// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles +// 0.99 (2019-02-07) warning fixes // 0.11 (2017-03-03) return packing success/fail result // 0.10 (2016-10-25) remove cast-away-const to avoid warnings // 0.09 (2016-08-27) fix compiler warnings @@ -347,6 +350,13 @@ static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int widt width -= width % c->align; STBRP_ASSERT(width % c->align == 0); + // if it can't possibly fit, bail immediately + if (width > c->width || height > c->height) { + fr.prev_link = NULL; + fr.x = fr.y = 0; + return fr; + } + node = c->active_head; prev = &c->active_head; while (node->x + width <= c->width) { @@ -410,7 +420,7 @@ static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int widt } STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); - if (y + height < c->height) { + if (y + height <= c->height) { if (y <= best_y) { if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { best_x = xpos; @@ -492,17 +502,14 @@ static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, i STBRP_ASSERT(cur->next == NULL); { - stbrp_node *L1 = NULL, *L2 = NULL; int count=0; cur = context->active_head; while (cur) { - L1 = cur; cur = cur->next; ++count; } cur = context->free_head; while (cur) { - L2 = cur; cur = cur->next; ++count; } @@ -544,9 +551,6 @@ STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int nu // we use the 'was_packed' field internally to allow sorting/unsorting for (i=0; i < num_rects; ++i) { rects[i].was_packed = i; - #ifndef STBRP_LARGE_RECTS - STBRP_ASSERT(rects[i].w <= 0xffff && rects[i].h <= 0xffff); - #endif } // sort according to heuristic diff --git a/raylib/external/stb_truetype.h b/raylib/external/stb_truetype.h index 98c8acc..4a3ad33 100644 --- a/raylib/external/stb_truetype.h +++ b/raylib/external/stb_truetype.h @@ -1,5 +1,5 @@ -// stb_truetype.h - v1.18 - public domain -// authored from 2009-2016 by Sean Barrett / RAD Game Tools +// stb_truetype.h - v1.22 - public domain +// authored from 2009-2019 by Sean Barrett / RAD Game Tools // // This library processes TrueType files: // parse files @@ -22,12 +22,14 @@ // Mikko Mononen: compound shape support, more cmap formats // Tor Andersson: kerning, subpixel rendering // Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning // // Misc other: // Ryan Gordon // Simon Glass // github:IntellectualKitty // Imanol Celaya +// Daniel Ribeiro Maciel // // Bug/warning reports/fixes: // "Zer" on mollyrocket Fabian "ryg" Giesen @@ -44,9 +46,14 @@ // Rob Loach Cort Stratton // Kenney Phillis Jr. github:oyvindjam // Brian Costabile github:vassvik +// Ken Voskuil (kaesve) Ryan Griege // // VERSION HISTORY // +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod // 1.18 (2018-01-29) add missing function // 1.17 (2017-07-23) make more arguments const; doc fix // 1.16 (2017-07-12) SDF support @@ -72,7 +79,7 @@ // // USAGE // -// Include this file in whatever places neeed to refer to it. In ONE C/C++ +// Include this file in whatever places need to refer to it. In ONE C/C++ // file, write: // #define STB_TRUETYPE_IMPLEMENTATION // before the #include of this file. This expands out the actual @@ -239,19 +246,6 @@ // recommend it. // // -// SOURCE STATISTICS (based on v0.6c, 2050 LOC) -// -// Documentation & header file 520 LOC \___ 660 LOC documentation -// Sample code 140 LOC / -// Truetype parsing 620 LOC ---- 620 LOC TrueType -// Software rasterization 240 LOC \ . -// Curve tesselation 120 LOC \__ 550 LOC Bitmap creation -// Bitmap management 100 LOC / -// Baked bitmap interface 70 LOC / -// Font name matching & access 150 LOC ---- 150 -// C runtime library abstraction 60 LOC ---- 60 -// -// // PERFORMANCE MEASUREMENTS FOR 1.06: // // 32-bit 64-bit @@ -411,7 +405,8 @@ int main(int arg, char **argv) //// INTEGRATION WITH YOUR CODEBASE //// //// The following sections allow you to supply alternate definitions -//// of C library functions used by stb_truetype. +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. #ifdef STB_TRUETYPE_IMPLEMENTATION // #define your own (u)stbtt_int8/16/32 before including to override this @@ -427,7 +422,7 @@ int main(int arg, char **argv) typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; - // #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h #ifndef STBTT_ifloor #include #define STBTT_ifloor(x) ((int) floor(x)) @@ -440,6 +435,11 @@ int main(int arg, char **argv) #define STBTT_pow(x,y) pow(x,y) #endif + #ifndef STBTT_fmod + #include + #define STBTT_fmod(x,y) fmod(x,y) + #endif + #ifndef STBTT_cos #include #define STBTT_cos(x) cos(x) @@ -547,6 +547,8 @@ STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int p // // It's inefficient; you might want to c&p it and optimize it. +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); +// Query the font vertical metrics without having to create a font first. ////////////////////////////////////////////////////////////////////////////// @@ -632,6 +634,12 @@ STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h // To use with PackFontRangesGather etc., you must set it before calls // call to PackFontRangesGatherRects. +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); +// If skip != 0, this tells stb_truetype to skip any codepoints for which +// there is no corresponding glyph. If skip=0, which is the default, then +// codepoints without a glyph recived the font's "missing character" glyph, +// typically an empty box by convention. + STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above int char_index, // character to display float *xpos, float *ypos, // pointers to current position in screen pixel space @@ -660,6 +668,7 @@ struct stbtt_pack_context { int height; int stride_in_bytes; int padding; + int skip_missing; unsigned int h_oversample, v_oversample; unsigned char *pixels; void *nodes; @@ -685,7 +694,7 @@ STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); // file will only define one font and it always be at offset 0, so it will // return '0' for index 0, and -1 for all other indices. -// The following structure is defined publically so you can declare one on +// The following structure is defined publicly so you can declare one on // the stack or as a global or etc, but you should treat it as opaque. struct stbtt_fontinfo { @@ -695,7 +704,7 @@ struct stbtt_fontinfo int numGlyphs; // number of glyphs, needed for range checking - int loca,head,glyf,hhea,hmtx,kern; // table locations as offset from start of .ttf + int loca,head,glyf,hhea,hmtx,kern,gpos; // table locations as offset from start of .ttf int index_map; // a cmap mapping for our chosen character encoding int indexToLocFormat; // format needed to map from glyph index to glyph @@ -724,6 +733,7 @@ STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codep // and you want a speed-up, call this function with the character you're // going to process, then use glyph-based functions instead of the // codepoint-based functions. +// Returns 0 if the character codepoint is not defined in the font. ////////////////////////////////////////////////////////////////////////////// @@ -811,7 +821,7 @@ STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, s // returns # of vertices and fills *vertices with the pointer to them // these are expressed in "unscaled" coordinates // -// The shape is a series of countours. Each one starts with +// The shape is a series of contours. Each one starts with // a STBTT_moveto, then consists of a series of mixed // STBTT_lineto and STBTT_curveto segments. A lineto // draws a line from previous endpoint to its x,y; a curveto @@ -907,7 +917,7 @@ STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float sc STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); // These functions compute a discretized SDF field for a single character, suitable for storing // in a single-channel texture, sampling with bilinear filtering, and testing against -// larger than some threshhold to produce scalable fonts. +// larger than some threshold to produce scalable fonts. // info -- the font // scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap // glyph/codepoint -- the character to generate the SDF for @@ -1338,6 +1348,7 @@ static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, in info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required if (!cmap || !info->head || !info->hhea || !info->hmtx) return 0; @@ -2256,7 +2267,7 @@ STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_inde } } -STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) { stbtt_uint8 *data = info->data + info->kern; stbtt_uint32 needle, straw; @@ -2286,9 +2297,260 @@ STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, return 0; } +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch(coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + } break; + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch(classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + + classDefTable = classDef1ValueArray + 2 * glyphCount; + } break; + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + + classDefTable = classRangeRecords + 6 * classRangeCount; + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } break; + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + STBTT_assert(glyph1class < class1Count); + STBTT_assert(glyph2class < class2Count); + + // TODO: Support more formats. + STBTT_GPOS_TODO_assert(valueFormat1 == 4); + if (valueFormat1 != 4) return 0; + STBTT_GPOS_TODO_assert(valueFormat2 == 0); + if (valueFormat2 != 0) return 0; + + if (glyph1class >= 0 && glyph1class < class1Count && glyph2class >= 0 && glyph2class < class2Count) { + stbtt_uint8 *class1Records = table + 16; + stbtt_uint8 *class2Records = class1Records + 2 * (glyph1class * class2Count); + stbtt_int16 xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + break; + }; + } + } + break; + }; + + default: + // TODO: Implement other stuff. + break; + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) { - if (!info->kern) // if no kerning table, don't waste time looking up both codepoint->glyphs + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs return 0; return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); } @@ -2899,7 +3161,13 @@ static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, if (e->y0 != e->y1) { stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); if (z != NULL) { - STBTT_assert(z->ey >= scan_y_top); + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp rounding error i think + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds // insert at front z->next = active; active = z; @@ -2968,7 +3236,7 @@ static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) { - /* threshhold for transitioning to insertion sort */ + /* threshold for transitioning to insertion sort */ while (n > 12) { stbtt__edge t; int c01,c12,c,m,i,j; @@ -3103,7 +3371,7 @@ static void stbtt__add_point(stbtt__point *points, int n, float x, float y) points[n].y = y; } -// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching +// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) { // midpoint @@ -3528,6 +3796,7 @@ STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, in spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; spc->h_oversample = 1; spc->v_oversample = 1; + spc->skip_missing = 0; stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); @@ -3553,6 +3822,11 @@ STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h spc->v_oversample = v_oversample; } +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc->skip_missing = skip; +} + #define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) @@ -3695,6 +3969,7 @@ static float stbtt__oversample_shift(int oversample) STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) { int i,j,k; + int missing_glyph_added = 0; k=0; for (i=0; i < num_ranges; ++i) { @@ -3706,13 +3981,19 @@ STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stb int x0,y0,x1,y1; int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; int glyph = stbtt_FindGlyphIndex(info, codepoint); - stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, - scale * spc->h_oversample, - scale * spc->v_oversample, - 0,0, - &x0,&y0,&x1,&y1); - rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); - rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + if (glyph == 0) + missing_glyph_added = 1; + } ++k; } } @@ -3746,7 +4027,7 @@ STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info // rects array must be big enough to accommodate all characters in the given ranges STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) { - int i,j,k, return_value = 1; + int i,j,k, missing_glyph = -1, return_value = 1; // save current values int old_h_over = spc->h_oversample; @@ -3765,7 +4046,7 @@ STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const sub_y = stbtt__oversample_shift(spc->v_oversample); for (j=0; j < ranges[i].num_chars; ++j) { stbrp_rect *r = &rects[k]; - if (r->was_packed) { + if (r->was_packed && r->w != 0 && r->h != 0) { stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; int advance, lsb, x0,y0,x1,y1; int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; @@ -3811,6 +4092,13 @@ STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const bc->yoff = (float) y0 * recip_v + sub_y; bc->xoff2 = (x0 + r->w) * recip_h + sub_x; bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + + if (glyph == 0) + missing_glyph = j; + } else if (spc->skip_missing) { + return_value = 0; + } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { + ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; } else { return_value = 0; // if any fail, report failure } @@ -3879,6 +4167,19 @@ STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char * return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); } +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float) i_ascent * scale; + *descent = (float) i_descent * scale; + *lineGap = (float) i_lineGap * scale; +} + STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) { float ipw = 1.0f / pw, iph = 1.0f / ph; @@ -3932,7 +4233,7 @@ static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float discr = b*b - a*c; if (discr > 0.0) { float rcpna = -1 / a; - float d = (float) sqrt(discr); + float d = (float) STBTT_sqrt(discr); s0 = (b+d) * rcpna; s1 = (b-d) * rcpna; if (s0 >= 0.0 && s0 <= 1.0) @@ -3994,7 +4295,7 @@ static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex orig[1] = y; // make sure y never passes through a vertex of the shape - y_frac = (float) fmod(y, 1.0f); + y_frac = (float) STBTT_fmod(y, 1.0f); if (y_frac < 0.01f) y += 0.01f; else if (y_frac > 0.99f) @@ -4099,12 +4400,7 @@ STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float sc int w,h; unsigned char *data; - // if one scale is 0, use same scale for both - if (scale_x == 0) scale_x = scale_y; - if (scale_y == 0) { - if (scale_x == 0) return NULL; // if both scales are 0, return NULL - scale_y = scale_x; - } + if (scale == 0) return NULL; stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); @@ -4494,6 +4790,9 @@ STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const // FULL VERSION HISTORY // +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix // 1.16 (2017-07-12) SDF support // 1.15 (2017-03-03) make more arguments const // 1.14 (2017-01-16) num-fonts-in-TTC function diff --git a/raylib/external/stb_vorbis.h b/raylib/external/stb_vorbis.h index 4a33909..90f3b06 100644 --- a/raylib/external/stb_vorbis.h +++ b/raylib/external/stb_vorbis.h @@ -1,4 +1,4 @@ -// Ogg Vorbis audio decoder - v1.13b - public domain +// Ogg Vorbis audio decoder - v1.14 - public domain // http://nothings.org/stb_vorbis/ // // Original version written by Sean Barrett in 2007. @@ -30,24 +30,26 @@ // Tom Beaumont Ingo Leitgeb Nicolas Guillemot // Phillip Bennefall Rohit Thiago Goulart // manxorist@github saga musix github:infatum +// Timur Gagiev BareRose // // Partial history: -// 1.13 - 2018/01/29 - fix truncation of last frame (hopefully) -// 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 'truncation of last frame' fix from previous version -// 1.08 - 2016/04/02 - warnings; setup memory leaks; truncation of last frame -// 1.07 - 2015/01/16 - fixes for crashes on invalid files; warning fixes; const -// 1.06 - 2015/08/31 - full, correct support for seeking API (Dougall Johnson) +// 1.14 - 2018-02-11 - delete bogus dealloca usage +// 1.13 - 2018-01-29 - fix truncation of last frame (hopefully) +// 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 stbv_ilog(); clear error in open_memory +// 1.09 - 2016-04-04 - back out 'truncation of last frame' fix from previous version +// 1.08 - 2016-04-02 - warnings; setup memory leaks; truncation of last frame +// 1.07 - 2015-01-16 - fixes for crashes on invalid files; warning fixes; const +// 1.06 - 2015-08-31 - full, correct support for seeking API (Dougall Johnson) // some crash fixes when out of memory or with corrupt files // fix some inappropriately signed shifts -// 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 comparison as explicitly _cdecl in Windows -// 1.01 - 2014/06/18 - fix stb_vorbis_get_samples_float (interleaved was correct) -// 1.0 - 2014/05/26 - fix memory leaks; fix warnings; fix bugs in >2-channel; +// 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 comparison as explicitly _cdecl in Windows +// 1.01 - 2014-06-18 - fix stb_vorbis_get_samples_float (interleaved was correct) +// 1.0 - 2014-05-26 - fix memory leaks; fix warnings; fix bugs in >2-channel; // (API change) report sample rate for decode-full-file funcs // // See end of file for full version history. @@ -62,7 +64,7 @@ #define STB_VORBIS_INCLUDE_STB_VORBIS_H #if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO) -#define STB_VORBIS_NO_STDIO 1 +#define STB_VORBIS_NO_STDIO #endif #ifndef STB_VORBIS_NO_STDIO @@ -78,6 +80,12 @@ extern "C" { #endif +#ifdef STB_VORBIS_STATIC +#define STBVDEF static +#else +#define STBVDEF extern +#endif + /////////// THREAD SAFETY // Individual stb_vorbis* handles are not thread-safe; you cannot decode from @@ -93,8 +101,8 @@ extern "C" { // data in the file and how you set the compile flags for speed // vs. size. In my test files the maximal-size usage is ~150KB.) // -// You can modify the wrapper functions in the source (setup_malloc, -// setup_temp_malloc, temp_malloc) to change this behavior, or you +// You can modify the wrapper functions in the source (stbv_setup_malloc, +// stbv_setup_temp_malloc, temp_malloc) to change this behavior, or you // can use a simpler allocation model: you pass in a buffer from // which stb_vorbis will allocate _all_ its memory (including the // temp memory). "open" may fail with a VORBIS_outofmem if you @@ -131,24 +139,24 @@ typedef struct } stb_vorbis_info; // get general information about the file -extern stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f); +STBVDEF stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f); // get the last error detected (clears it, too) -extern int stb_vorbis_get_error(stb_vorbis *f); +STBVDEF int stb_vorbis_get_error(stb_vorbis *f); // close an ogg vorbis file and free all memory in use -extern void stb_vorbis_close(stb_vorbis *f); +STBVDEF void stb_vorbis_close(stb_vorbis *f); // this function returns the offset (in samples) from the beginning of the // file that will be returned by the next decode, if it is known, or -1 // otherwise. after a flush_pushdata() call, this may take a while before // it becomes valid again. // NOT WORKING YET after a seek with PULLDATA API -extern int stb_vorbis_get_sample_offset(stb_vorbis *f); +STBVDEF int stb_vorbis_get_sample_offset(stb_vorbis *f); // returns the current seek point within the file, or offset from the beginning // of the memory buffer. In pushdata mode it returns 0. -extern unsigned int stb_vorbis_get_file_offset(stb_vorbis *f); +STBVDEF unsigned int stb_vorbis_get_file_offset(stb_vorbis *f); /////////// PUSHDATA API @@ -161,7 +169,7 @@ extern unsigned int stb_vorbis_get_file_offset(stb_vorbis *f); // need to give it the same data again PLUS more. Note that the Vorbis // specification does not bound the size of an individual frame. -extern stb_vorbis *stb_vorbis_open_pushdata( +STBVDEF stb_vorbis *stb_vorbis_open_pushdata( const unsigned char * datablock, int datablock_length_in_bytes, int *datablock_memory_consumed_in_bytes, int *error, @@ -175,7 +183,7 @@ extern stb_vorbis *stb_vorbis_open_pushdata( // if returns NULL and *error is VORBIS_need_more_data, then the input block was // incomplete and you need to pass in a larger block from the start of the file -extern int stb_vorbis_decode_frame_pushdata( +STBVDEF int stb_vorbis_decode_frame_pushdata( stb_vorbis *f, const unsigned char *datablock, int datablock_length_in_bytes, int *channels, // place to write number of float * buffers @@ -205,7 +213,7 @@ extern int stb_vorbis_decode_frame_pushdata( // the first channel, and (*output)[1][0] contains the first sample from // the second channel. -extern void stb_vorbis_flush_pushdata(stb_vorbis *f); +STBVDEF void stb_vorbis_flush_pushdata(stb_vorbis *f); // inform stb_vorbis that your next datablock will not be contiguous with // previous ones (e.g. you've seeked in the data); future attempts to decode // frames will cause stb_vorbis to resynchronize (as noted above), and @@ -230,28 +238,28 @@ extern void stb_vorbis_flush_pushdata(stb_vorbis *f); // just want to go ahead and use pushdata.) #if !defined(STB_VORBIS_NO_STDIO) && !defined(STB_VORBIS_NO_INTEGER_CONVERSION) -extern int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output); +STBVDEF int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output); #endif -#if !defined(STB_VORBIS_NO_INTEGER_CONVERSION) -extern int stb_vorbis_decode_memory(const unsigned char *mem, int len, int *channels, int *sample_rate, short **output); +#ifndef STB_VORBIS_NO_INTEGER_CONVERSION +STBVDEF int stb_vorbis_decode_memory(const unsigned char *mem, int len, int *channels, int *sample_rate, short **output); #endif // decode an entire file and output the data interleaved into a malloc()ed // buffer stored in *output. The return value is the number of samples // decoded, or -1 if the file could not be opened or was not an ogg vorbis file. // When you're done with it, just free() the pointer returned in *output. -extern stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, +STBVDEF stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, int *error, const stb_vorbis_alloc *alloc_buffer); // create an ogg vorbis decoder from an ogg vorbis stream in memory (note // this must be the entire stream!). on failure, returns NULL and sets *error #ifndef STB_VORBIS_NO_STDIO -extern stb_vorbis * stb_vorbis_open_filename(const char *filename, +STBVDEF stb_vorbis * stb_vorbis_open_filename(const char *filename, int *error, const stb_vorbis_alloc *alloc_buffer); // create an ogg vorbis decoder from a filename via fopen(). on failure, // returns NULL and sets *error (possibly to VORBIS_file_open_failure). -extern stb_vorbis * stb_vorbis_open_file(FILE *f, int close_handle_on_close, +STBVDEF stb_vorbis * stb_vorbis_open_file(FILE *f, int close_handle_on_close, int *error, const stb_vorbis_alloc *alloc_buffer); // create an ogg vorbis decoder from an open FILE *, looking for a stream at // the _current_ seek point (ftell). on failure, returns NULL and sets *error. @@ -261,7 +269,7 @@ extern stb_vorbis * stb_vorbis_open_file(FILE *f, int close_handle_on_close, // owns the _entire_ rest of the file after the start point. Use the next // function, stb_vorbis_open_file_section(), to limit it. -extern stb_vorbis * stb_vorbis_open_file_section(FILE *f, int close_handle_on_close, +STBVDEF stb_vorbis * stb_vorbis_open_file_section(FILE *f, int close_handle_on_close, int *error, const stb_vorbis_alloc *alloc_buffer, unsigned int len); // create an ogg vorbis decoder from an open FILE *, looking for a stream at // the _current_ seek point (ftell); the stream will be of length 'len' bytes. @@ -270,8 +278,8 @@ extern stb_vorbis * stb_vorbis_open_file_section(FILE *f, int close_handle_on_cl // confused. #endif -extern int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number); -extern int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number); +STBVDEF int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number); +STBVDEF int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number); // these functions seek in the Vorbis file to (approximately) 'sample_number'. // after calling seek_frame(), the next call to get_frame_*() will include // the specified sample. after calling stb_vorbis_seek(), the next call to @@ -279,14 +287,14 @@ extern int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number); // do not need to seek to EXACTLY the target sample when using get_samples_*, // you can also use seek_frame(). -extern int stb_vorbis_seek_start(stb_vorbis *f); +STBVDEF int stb_vorbis_seek_start(stb_vorbis *f); // this function is equivalent to stb_vorbis_seek(f,0) -extern unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f); -extern float stb_vorbis_stream_length_in_seconds(stb_vorbis *f); +STBVDEF unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f); +STBVDEF float stb_vorbis_stream_length_in_seconds(stb_vorbis *f); // these functions return the total length of the vorbis stream -extern int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output); +STBVDEF int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output); // decode the next frame and return the number of samples. the number of // channels returned are stored in *channels (which can be NULL--it is always // the same as the number of channels reported by get_info). *output will @@ -297,8 +305,8 @@ extern int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***out // and stb_vorbis_get_samples_*(), since the latter calls the former. #ifndef STB_VORBIS_NO_INTEGER_CONVERSION -extern int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts); -extern int stb_vorbis_get_frame_short (stb_vorbis *f, int num_c, short **buffer, int num_samples); +STBVDEF int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts); +STBVDEF int stb_vorbis_get_frame_short (stb_vorbis *f, int num_c, short **buffer, int num_samples); #endif // decode the next frame and return the number of *samples* per channel. // Note that for interleaved data, you pass in the number of shorts (the @@ -325,16 +333,16 @@ extern int stb_vorbis_get_frame_short (stb_vorbis *f, int num_c, shor // Note that this is not _good_ surround etc. mixing at all! It's just so // you get something useful. -extern int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats); -extern int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples); +STBVDEF int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats); +STBVDEF int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples); // gets num_samples samples, not necessarily on a frame boundary--this requires // buffering so you have to supply the buffers. DOES NOT APPLY THE COERCION RULES. // Returns the number of samples stored per channel; it may be less than requested // at the end of the file. If there are no more samples in the file, returns 0. #ifndef STB_VORBIS_NO_INTEGER_CONVERSION -extern int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts); -extern int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int num_samples); +STBVDEF int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts); +STBVDEF int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int num_samples); #endif // gets num_samples samples, not necessarily on a frame boundary--this requires // buffering so you have to supply the buffers. Applies the coercion rules above @@ -390,3 +398,5078 @@ enum STBVorbisError // HEADER ENDS HERE // ////////////////////////////////////////////////////////////////////////////// + +#ifdef STB_VORBIS_IMPLEMENTATION + +// 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 + #ifndef alloca + #define alloca(s) __builtin_alloca(s) + #endif +#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 STBV_CHECK(f) _CrtIsValidHeapPointer(f->channel_buffers[1]) +#else +#define STBV_CHECK(f) ((void) 0) +#endif + +#define STBV_MAX_BLOCKSIZE_LOG 13 // from specification +#define STBV_MAX_BLOCKSIZE (1 << STBV_MAX_BLOCKSIZE_LOG) + + +typedef unsigned char stbv_uint8; +typedef signed char stbv_int8; +typedef unsigned short stbv_uint16; +typedef signed short stbv_int16; +typedef unsigned int stbv_uint32; +typedef signed int stbv_int32; + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +typedef float stbv_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 STBV_FAST_HUFFMAN_TABLE_SIZE (1 << STB_VORBIS_FAST_HUFFMAN_LENGTH) +#define STBV_FAST_HUFFMAN_TABLE_MASK (STBV_FAST_HUFFMAN_TABLE_SIZE - 1) + +typedef struct +{ + int dimensions, entries; + stbv_uint8 *codeword_lengths; + float minimum_value; + float delta_value; + stbv_uint8 value_bits; + stbv_uint8 lookup_type; + stbv_uint8 sequence_p; + stbv_uint8 sparse; + stbv_uint32 lookup_values; + stbv_codetype *multiplicands; + stbv_uint32 *codewords; + #ifdef STB_VORBIS_FAST_HUFFMAN_SHORT + stbv_int16 fast_huffman[STBV_FAST_HUFFMAN_TABLE_SIZE]; + #else + stbv_int32 fast_huffman[STBV_FAST_HUFFMAN_TABLE_SIZE]; + #endif + stbv_uint32 *sorted_codewords; + int *sorted_values; + int sorted_entries; +} StbvCodebook; + +typedef struct +{ + stbv_uint8 order; + stbv_uint16 rate; + stbv_uint16 bark_map_size; + stbv_uint8 amplitude_bits; + stbv_uint8 amplitude_offset; + stbv_uint8 number_of_books; + stbv_uint8 book_list[16]; // varies +} StbvFloor0; + +typedef struct +{ + stbv_uint8 partitions; + stbv_uint8 partition_class_list[32]; // varies + stbv_uint8 class_dimensions[16]; // varies + stbv_uint8 class_subclasses[16]; // varies + stbv_uint8 class_masterbooks[16]; // varies + stbv_int16 subclass_books[16][8]; // varies + stbv_uint16 Xlist[31*8+2]; // varies + stbv_uint8 sorted_order[31*8+2]; + stbv_uint8 stbv_neighbors[31*8+2][2]; + stbv_uint8 floor1_multiplier; + stbv_uint8 rangebits; + int values; +} StbvFloor1; + +typedef union +{ + StbvFloor0 floor0; + StbvFloor1 floor1; +} StbvFloor; + +typedef struct +{ + stbv_uint32 begin, end; + stbv_uint32 part_size; + stbv_uint8 classifications; + stbv_uint8 classbook; + stbv_uint8 **classdata; + stbv_int16 (*residue_books)[8]; +} StbvResidue; + +typedef struct +{ + stbv_uint8 magnitude; + stbv_uint8 angle; + stbv_uint8 mux; +} StbvMappingChannel; + +typedef struct +{ + stbv_uint16 coupling_steps; + StbvMappingChannel *chan; + stbv_uint8 submaps; + stbv_uint8 submap_floor[15]; // varies + stbv_uint8 submap_residue[15]; // varies +} StbvMapping; + +typedef struct +{ + stbv_uint8 blockflag; + stbv_uint8 mapping; + stbv_uint16 windowtype; + stbv_uint16 transformtype; +} StbvMode; + +typedef struct +{ + stbv_uint32 goal_crc; // expected crc if match + int bytes_left; // bytes left in packet + stbv_uint32 crc_so_far; // running crc + int bytes_done; // bytes processed in _current_ chunk + stbv_uint32 sample_loc; // granule pos encoded in page +} StbvCRCscan; + +typedef struct +{ + stbv_uint32 page_start, page_end; + stbv_uint32 last_decoded_sample; +} StbvProbedPage; + +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; + stbv_uint32 f_start; + int close_on_free; +#endif + + stbv_uint8 *stream; + stbv_uint8 *stream_start; + stbv_uint8 *stream_end; + + stbv_uint32 stream_len; + + stbv_uint8 push_mode; + + stbv_uint32 first_audio_page_offset; + + StbvProbedPage 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; + StbvCodebook *codebooks; + int floor_count; + stbv_uint16 floor_types[64]; // varies + StbvFloor *floor_config; + int residue_count; + stbv_uint16 residue_types[64]; // varies + StbvResidue *residue_config; + int mapping_count; + StbvMapping *mapping; + int mode_count; + StbvMode mode_config[64]; // varies + + stbv_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 + stbv_int16 *finalY[STB_VORBIS_MAX_CHANNELS]; + #else + float *floor_buffers[STB_VORBIS_MAX_CHANNELS]; + #endif + + stbv_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]; + stbv_uint16 *stbv_bit_reverse[2]; + + // current page/packet/segment streaming info + stbv_uint32 serial; // stream serial number for verification + int last_page; + int segment_count; + stbv_uint8 segments[255]; + stbv_uint8 page_flag; + stbv_uint8 bytes_in_seg; + stbv_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? + stbv_uint32 acc; + int valid_bits; + int packet_bytes; + int end_seg_with_known_loc; + stbv_uint32 known_loc_for_packet; + int discard_samples_deferred; + stbv_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 + StbvCRCscan 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 STBV_IS_PUSH_MODE(f) FALSE +#elif defined(STB_VORBIS_NO_PULLDATA_API) + #define STBV_IS_PUSH_MODE(f) TRUE +#else + #define STBV_IS_PUSH_MODE(f) ((f)->push_mode) +#endif + +typedef struct stb_vorbis stbv_vorb; + +static int stbv_error(stbv_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 stbv_array_size_required(count,size) (count*(sizeof(void *)+(size))) + +#define stbv_temp_alloc(f,size) (f->alloc.alloc_buffer ? stbv_setup_temp_malloc(f,size) : alloca(size)) +#define stbv_temp_free(f,p) 0 +#define stbv_temp_alloc_save(f) ((f)->temp_offset) +#define stbv_temp_alloc_restore(f,p) ((f)->temp_offset = (p)) + +#define stbv_temp_block_array(f,count,size) stbv_make_block_array(stbv_temp_alloc(f,stbv_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 *stbv_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 *stbv_setup_malloc(stbv_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 stbv_setup_free(stbv_vorb *f, void *p) +{ + if (f->alloc.alloc_buffer) return; // do nothing; setup mem is a stack + free(p); +} + +static void *stbv_setup_temp_malloc(stbv_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 stbv_setup_temp_free(stbv_vorb *f, void *p, int sz) +{ + if (f->alloc.alloc_buffer) { + f->temp_offset += (sz+3)&~3; + return; + } + free(p); +} + +#define STBV_CRC32_POLY 0x04c11db7 // from spec + +static stbv_uint32 stbv_crc_table[256]; +static void stbv_crc32_init(void) +{ + int i,j; + stbv_uint32 s; + for(i=0; i < 256; i++) { + for (s=(stbv_uint32) i << 24, j=0; j < 8; ++j) + s = (s << 1) ^ (s >= (1U<<31) ? STBV_CRC32_POLY : 0); + stbv_crc_table[i] = s; + } +} + +static __forceinline stbv_uint32 stbv_crc32_update(stbv_uint32 crc, stbv_uint8 byte) +{ + return (crc << 8) ^ stbv_crc_table[byte ^ (crc >> 24)]; +} + + +// used in setup, and for huffman that doesn't go fast path +static unsigned int stbv_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 stbv_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 stbv_ilog(stbv_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 stbv_float32_unpack(stbv_uint32 x) +{ + // from the specification + stbv_uint32 mantissa = x & 0x1fffff; + stbv_uint32 sign = x & 0x80000000; + stbv_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 stbv_add_entry(StbvCodebook *c, stbv_uint32 huff_code, int symbol, int count, int len, stbv_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 stbv_compute_codewords(StbvCodebook *c, stbv_uint8 *len, int n, stbv_uint32 *values) +{ + int i,k,m=0; + stbv_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 + stbv_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) { + stbv_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; + stbv_add_entry(c, stbv_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 stbv_compute_accelerated_huffman(StbvCodebook *c) +{ + int i, len; + for (i=0; i < STBV_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) { + stbv_uint32 z = c->sparse ? stbv_bit_reverse(c->sorted_codewords[i]) : c->codewords[i]; + // set table entries for all bit combinations in the higher bits + while (z < STBV_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 stbv_uint32_compare(const void *p, const void *q) +{ + stbv_uint32 x = * (stbv_uint32 *) p; + stbv_uint32 y = * (stbv_uint32 *) q; + return x < y ? -1 : x > y; +} + +static int stbv_include_in_sort(StbvCodebook *c, stbv_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 stbv_compute_sorted_huffman(StbvCodebook *c, stbv_uint8 *lengths, stbv_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 (stbv_include_in_sort(c, lengths[i])) + c->sorted_codewords[k++] = stbv_bit_reverse(c->codewords[i]); + assert(k == c->sorted_entries); + } else { + for (i=0; i < c->sorted_entries; ++i) + c->sorted_codewords[i] = stbv_bit_reverse(c->codewords[i]); + } + + qsort(c->sorted_codewords, c->sorted_entries, sizeof(c->sorted_codewords[0]), stbv_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 (stbv_include_in_sort(c,huff_len)) { + stbv_uint32 code = stbv_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 stbv_vorbis_validate(stbv_uint8 *data) +{ + static stbv_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 stbv_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 stbv_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 stbv_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 * stbv_square((float) sin((i - 0 + 0.5) / n2 * 0.5 * M_PI))); +} + +static void stbv_compute_bitreverse(int n, stbv_uint16 *rev) +{ + int ld = stbv_ilog(n) - 1; // stbv_ilog is off-by-one from normal definitions + int i, n8 = n >> 3; + for (i=0; i < n8; ++i) + rev[i] = (stbv_bit_reverse(i) >> (32-ld+3)) << 2; +} + +static int stbv_init_blocksize(stbv_vorb *f, int b, int n) +{ + int n2 = n >> 1, n4 = n >> 2, n8 = n >> 3; + f->A[b] = (float *) stbv_setup_malloc(f, sizeof(float) * n2); + f->B[b] = (float *) stbv_setup_malloc(f, sizeof(float) * n2); + f->C[b] = (float *) stbv_setup_malloc(f, sizeof(float) * n4); + if (!f->A[b] || !f->B[b] || !f->C[b]) return stbv_error(f, VORBIS_outofmem); + stbv_compute_twiddle_factors(n, f->A[b], f->B[b], f->C[b]); + f->window[b] = (float *) stbv_setup_malloc(f, sizeof(float) * n2); + if (!f->window[b]) return stbv_error(f, VORBIS_outofmem); + stbv_compute_window(n, f->window[b]); + f->stbv_bit_reverse[b] = (stbv_uint16 *) stbv_setup_malloc(f, sizeof(stbv_uint16) * n8); + if (!f->stbv_bit_reverse[b]) return stbv_error(f, VORBIS_outofmem); + stbv_compute_bitreverse(n, f->stbv_bit_reverse[b]); + return TRUE; +} + +static void stbv_neighbors(stbv_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 +{ + stbv_uint16 x,id; +} stbv_floor_ordering; + +static int STBV_CDECL stbv_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 STBV_USE_MEMORY(z) TRUE +#else + #define STBV_USE_MEMORY(z) ((z)->stream) +#endif + +static stbv_uint8 stbv_get8(stbv_vorb *z) +{ + if (STBV_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 stbv_uint32 stbv_get32(stbv_vorb *f) +{ + stbv_uint32 x; + x = stbv_get8(f); + x += stbv_get8(f) << 8; + x += stbv_get8(f) << 16; + x += (stbv_uint32) stbv_get8(f) << 24; + return x; +} + +static int stbv_getn(stbv_vorb *z, stbv_uint8 *data, int n) +{ + if (STBV_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 stbv_skip(stbv_vorb *z, int n) +{ + if (STBV_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 stbv_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 (STBV_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 stbv_uint8 stbv_ogg_page_header[4] = { 0x4f, 0x67, 0x67, 0x53 }; + +static int stbv_capture_pattern(stbv_vorb *f) +{ + if (0x4f != stbv_get8(f)) return FALSE; + if (0x67 != stbv_get8(f)) return FALSE; + if (0x67 != stbv_get8(f)) return FALSE; + if (0x53 != stbv_get8(f)) return FALSE; + return TRUE; +} + +#define STBV_PAGEFLAG_continued_packet 1 +#define STBV_PAGEFLAG_first_page 2 +#define STBV_PAGEFLAG_last_page 4 + +static int stbv_start_page_no_capturepattern(stbv_vorb *f) +{ + stbv_uint32 loc0,loc1,n; + // stream structure version + if (0 != stbv_get8(f)) return stbv_error(f, VORBIS_invalid_stream_structure_version); + // header flag + f->page_flag = stbv_get8(f); + // absolute granule position + loc0 = stbv_get32(f); + loc1 = stbv_get32(f); + // @TODO: validate loc0,loc1 as valid positions? + // stream serial number -- vorbis doesn't interleave, so discard + stbv_get32(f); + //if (f->serial != stbv_get32(f)) return stbv_error(f, VORBIS_incorrect_stream_serial_number); + // page sequence number + n = stbv_get32(f); + f->last_page = n; + // CRC32 + stbv_get32(f); + // page_segments + f->segment_count = stbv_get8(f); + if (!stbv_getn(f, f->segments, f->segment_count)) + return stbv_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; + StbvProbedPage 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 stbv_start_page(stbv_vorb *f) +{ + if (!stbv_capture_pattern(f)) return stbv_error(f, VORBIS_missing_capture_pattern); + return stbv_start_page_no_capturepattern(f); +} + +static int stbv_start_packet(stbv_vorb *f) +{ + while (f->next_seg == -1) { + if (!stbv_start_page(f)) return FALSE; + if (f->page_flag & STBV_PAGEFLAG_continued_packet) + return stbv_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 stbv_maybe_start_packet(stbv_vorb *f) +{ + if (f->next_seg == -1) { + int x = stbv_get8(f); + if (f->eof) return FALSE; // EOF at page boundary is not an error! + if (0x4f != x ) return stbv_error(f, VORBIS_missing_capture_pattern); + if (0x67 != stbv_get8(f)) return stbv_error(f, VORBIS_missing_capture_pattern); + if (0x67 != stbv_get8(f)) return stbv_error(f, VORBIS_missing_capture_pattern); + if (0x53 != stbv_get8(f)) return stbv_error(f, VORBIS_missing_capture_pattern); + if (!stbv_start_page_no_capturepattern(f)) return FALSE; + if (f->page_flag & STBV_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 stbv_error(f, VORBIS_continued_packet_flag_invalid); + } + } + return stbv_start_packet(f); +} + +static int stbv_next_segment(stbv_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 stbv_start_page fails + if (!stbv_start_page(f)) { f->last_seg = 1; return 0; } + if (!(f->page_flag & STBV_PAGEFLAG_continued_packet)) return stbv_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 STBV_EOP (-1) +#define STBV_INVALID_BITS (-1) + +static int stbv_get8_packet_raw(stbv_vorb *f) +{ + if (!f->bytes_in_seg) { // CLANG! + if (f->last_seg) return STBV_EOP; + else if (!stbv_next_segment(f)) return STBV_EOP; + } + assert(f->bytes_in_seg > 0); + --f->bytes_in_seg; + ++f->packet_bytes; + return stbv_get8(f); +} + +static int stbv_get8_packet(stbv_vorb *f) +{ + int x = stbv_get8_packet_raw(f); + f->valid_bits = 0; + return x; +} + +static void stbv_flush_packet(stbv_vorb *f) +{ + while (stbv_get8_packet_raw(f) != STBV_EOP); +} + +// @OPTIMIZE: this is the secondary bit decoder, so it's probably not as important +// as the huffman decoder? +static stbv_uint32 stbv_get_bits(stbv_vorb *f, int n) +{ + stbv_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 = stbv_get_bits(f, 24); + z += stbv_get_bits(f, n-24) << 24; + return z; + } + if (f->valid_bits == 0) f->acc = 0; + while (f->valid_bits < n) { + int z = stbv_get8_packet_raw(f); + if (z == STBV_EOP) { + f->valid_bits = STBV_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 stbv_prep_huffman(stbv_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 = stbv_get8_packet_raw(f); + if (z == STBV_EOP) return; + f->acc += (unsigned) z << f->valid_bits; + f->valid_bits += 8; + } while (f->valid_bits <= 24); + } +} + +enum +{ + STBV_VORBIS_packet_id = 1, + STBV_VORBIS_packet_comment = 3, + STBV_VORBIS_packet_setup = 5 +}; + +static int stbv_codebook_decode_scalar_raw(stbv_vorb *f, StbvCodebook *c) +{ + int i; + stbv_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 + stbv_uint32 code = stbv_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; + } + } + + stbv_error(f, VORBIS_invalid_stream); + f->valid_bits = 0; + return -1; +} + +#ifndef STB_VORBIS_NO_INLINE_DECODE + +#define STBV_DECODE_RAW(var, f,c) \ + if (f->valid_bits < STB_VORBIS_FAST_HUFFMAN_LENGTH) \ + stbv_prep_huffman(f); \ + var = f->acc & STBV_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 = stbv_codebook_decode_scalar_raw(f,c); \ + } + +#else + +static int stbv_codebook_decode_scalar(stbv_vorb *f, StbvCodebook *c) +{ + int i; + if (f->valid_bits < STB_VORBIS_FAST_HUFFMAN_LENGTH) + stbv_prep_huffman(f); + // fast huffman table lookup + i = f->acc & STBV_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 stbv_codebook_decode_scalar_raw(f,c); +} + +#define STBV_DECODE_RAW(var,f,c) var = stbv_codebook_decode_scalar(f,c); + +#endif + +#define STBV_DECODE(var,f,c) \ + STBV_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) STBV_DECODE_RAW(var,f,c) +#else + #define DECODE_VQ(var,f,c) STBV_DECODE(var,f,c) +#endif + + + + + + +// STBV_CODEBOOK_ELEMENT_FAST is an optimization for the CODEBOOK_FLOATS case +// where we avoid one addition +#define STBV_CODEBOOK_ELEMENT(c,off) (c->multiplicands[off]) +#define STBV_CODEBOOK_ELEMENT_FAST(c,off) (c->multiplicands[off]) +#define STBV_CODEBOOK_ELEMENT_BASE(c) (0) + +static int stbv_codebook_decode_start(stbv_vorb *f, StbvCodebook *c) +{ + int z = -1; + + // type 0 is only legal in a scalar context + if (c->lookup_type == 0) + stbv_error(f, VORBIS_invalid_stream); + else { + DECODE_VQ(z,f,c); + if (c->sparse) assert(z < c->sorted_entries); + if (z < 0) { // check for STBV_EOP + if (!f->bytes_in_seg) + if (f->last_seg) + return z; + stbv_error(f, VORBIS_invalid_stream); + } + } + return z; +} + +static int stbv_codebook_decode(stbv_vorb *f, StbvCodebook *c, float *output, int len) +{ + int i,z = stbv_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 = STBV_CODEBOOK_ELEMENT_BASE(c); + int div = 1; + for (i=0; i < len; ++i) { + int off = (z / div) % c->lookup_values; + float val = STBV_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 = STBV_CODEBOOK_ELEMENT_BASE(c); + for (i=0; i < len; ++i) { + float val = STBV_CODEBOOK_ELEMENT_FAST(c,z+i) + last; + output[i] += val; + last = val + c->minimum_value; + } + } else { + float last = STBV_CODEBOOK_ELEMENT_BASE(c); + for (i=0; i < len; ++i) { + output[i] += STBV_CODEBOOK_ELEMENT_FAST(c,z+i) + last; + } + } + + return TRUE; +} + +static int stbv_codebook_decode_step(stbv_vorb *f, StbvCodebook *c, float *output, int len, int step) +{ + int i,z = stbv_codebook_decode_start(f,c); + float last = STBV_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 = STBV_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 = STBV_CODEBOOK_ELEMENT_FAST(c,z+i) + last; + output[i*step] += val; + if (c->sequence_p) last = val; + } + + return TRUE; +} + +static int stbv_codebook_decode_deinterleave_repeat(stbv_vorb *f, StbvCodebook *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 stbv_error(f, VORBIS_invalid_stream); + + while (total_decode > 0) { + float last = STBV_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 stbv_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 = STBV_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 = STBV_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 = STBV_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 stbv_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 stbv_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 STBV_LINE_OP(a,b) a *= b +#else +#define STBV_LINE_OP(a,b) a = b +#endif + +#ifdef STB_VORBIS_DIVIDE_TABLE +#define STBV_DIVTAB_NUMER 32 +#define STBV_DIVTAB_DENOM 64 +stbv_int8 stbv_integer_divide_table[STBV_DIVTAB_NUMER][STBV_DIVTAB_DENOM]; // 2KB +#endif + +static __forceinline void stbv_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 < STBV_DIVTAB_DENOM && ady < STBV_DIVTAB_NUMER) { + if (dy < 0) { + base = -stbv_integer_divide_table[ady][adx]; + sy = base-1; + } else { + base = stbv_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) { + STBV_LINE_OP(output[x], stbv_inverse_db_table[y]); + for (++x; x < x1; ++x) { + err += ady; + if (err >= adx) { + err -= adx; + y += sy; + } else + y += base; + STBV_LINE_OP(output[x], stbv_inverse_db_table[y]); + } + } +} + +static int stbv_residue_decode(stbv_vorb *f, StbvCodebook *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 (!stbv_codebook_decode_step(f, book, target+offset+k, n-offset-k, step)) + return FALSE; + } else { + for (k=0; k < n; ) { + if (!stbv_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 stbv_decode_residue(stbv_vorb *f, float *residue_buffers[], int ch, int n, int rn, stbv_uint8 *do_not_decode) +{ + int i,j,pass; + StbvResidue *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 = stbv_temp_alloc_save(f); + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + stbv_uint8 ***part_classdata = (stbv_uint8 ***) stbv_temp_block_array(f,f->channels, part_read * sizeof(**part_classdata)); + #else + int **classifications = (int **) stbv_temp_block_array(f,f->channels, part_read * sizeof(**classifications)); + #endif + + STBV_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) { + StbvCodebook *c = f->codebooks+r->classbook; + int q; + STBV_DECODE(q,f,c); + if (q == STBV_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) { + StbvCodebook *book = f->codebooks + b; + #ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (!stbv_codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) + goto done; + #else + // saves 1% + if (!stbv_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) { + StbvCodebook *c = f->codebooks+r->classbook; + int q; + STBV_DECODE(q,f,c); + if (q == STBV_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) { + StbvCodebook *book = f->codebooks + b; + if (!stbv_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) { + StbvCodebook *c = f->codebooks+r->classbook; + int q; + STBV_DECODE(q,f,c); + if (q == STBV_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) { + StbvCodebook *book = f->codebooks + b; + if (!stbv_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; + } + STBV_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]) { + StbvCodebook *c = f->codebooks+r->classbook; + int temp; + STBV_DECODE(temp,f,c); + if (temp == STBV_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; + StbvCodebook *book = f->codebooks + b; + if (!stbv_residue_decode(f, book, target, offset, n, rtype)) + goto done; + } + } + } + } + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + ++class_set; + #endif + } + } + done: + STBV_CHECK(f); + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + stbv_temp_free(f,part_classdata); + #else + stbv_temp_free(f,classifications); + #endif + stbv_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, stbv_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, stbv_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 stbv_inverse_mdct(float *buffer, int n, stbv_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 stbv_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 stbv_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 stbv_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 stbv_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 stbv_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; + + stbv_iter_54(z); + stbv_iter_54(z-8); + z -= 16; + } +} + +static void stbv_inverse_mdct(float *buffer, int n, stbv_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 = stbv_temp_alloc_save(f); + float *buf2 = (float *) stbv_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 = stbv_ilog(n) - 1; // stbv_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 + stbv_imdct_step3_iter0_loop(n >> 4, u, n2-1-n4*0, -(n >> 3), A); + stbv_imdct_step3_iter0_loop(n >> 4, u, n2-1-n4*1, -(n >> 3), A); + + // this is iteration 1 of step 3 + stbv_imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*0, -(n >> 4), A, 16); + stbv_imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*1, -(n >> 4), A, 16); + stbv_imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*2, -(n >> 4), A, 16); + stbv_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) + stbv_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) { + stbv_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 + stbv_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 + { + stbv_uint16 *bitrev = f->stbv_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; + } + } + + stbv_temp_free(f,buf2); + stbv_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 = stbv_ilog(n) - 1; // stbv_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 = stbv_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 *stbv_get_window(stbv_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 stbv_int16 STBV_YTYPE; +#else +typedef int STBV_YTYPE; +#endif +static int stbv_do_floor(stbv_vorb *f, StbvMapping *map, int i, int n, float *target, STBV_YTYPE *finalY, stbv_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 stbv_error(f, VORBIS_invalid_stream); + } else { + StbvFloor1 *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) + stbv_draw_line(target, lx,ly, hx,hy, n2); + STBV_CHECK(f); + lx = hx, ly = hy; + } + } + if (lx < n2) { + // optimization of: stbv_draw_line(target, lx,ly, n,ly, n2); + for (j=lx; j < n2; ++j) + STBV_LINE_OP(target[j], stbv_inverse_db_table[ly]); + STBV_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 stbv_vorbis_decode_initial(stbv_vorb *f, int *p_left_start, int *p_left_end, int *p_right_start, int *p_right_end, int *mode) +{ + StbvMode *m; + int i, n, prev, next, window_center; + f->channel_buffer_start = f->channel_buffer_end = 0; + + retry: + if (f->eof) return FALSE; + if (!stbv_maybe_start_packet(f)) + return FALSE; + // check packet type + if (stbv_get_bits(f,1) != 0) { + if (STBV_IS_PUSH_MODE(f)) + return stbv_error(f,VORBIS_bad_packet_type); + while (STBV_EOP != stbv_get8_packet(f)); + goto retry; + } + + if (f->alloc.alloc_buffer) + assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); + + i = stbv_get_bits(f, stbv_ilog(f->mode_count-1)); + if (i == STBV_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 = stbv_get_bits(f,1); + next = stbv_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 stbv_vorbis_decode_packet_rest(stbv_vorb *f, int *len, StbvMode *m, int left_start, int left_end, int right_start, int right_end, int *p_left) +{ + StbvMapping *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; + + STBV_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 stbv_error(f, VORBIS_invalid_stream); + } else { + StbvFloor1 *g = &f->floor_config[floor].floor1; + if (stbv_get_bits(f, 1)) { + short *finalY; + stbv_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] = stbv_get_bits(f, stbv_ilog(range)-1); + finalY[1] = stbv_get_bits(f, stbv_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) { + StbvCodebook *c = f->codebooks + g->class_masterbooks[pclass]; + STBV_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; + StbvCodebook *c = f->codebooks + book; + STBV_DECODE(temp,f,c); + finalY[offset++] = temp; + } else + finalY[offset++] = 0; + } + } + if (f->valid_bits == STBV_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->stbv_neighbors[j][0]; + high = g->stbv_neighbors[j][1]; + //stbv_neighbors(g->Xlist, j, &low, &high); + pred = stbv_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 + stbv_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 + } + } + STBV_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; + } + + STBV_CHECK(f); +// RESIDUE STBV_DECODE + for (i=0; i < map->submaps; ++i) { + float *residue_buffers[STB_VORBIS_MAX_CHANNELS]; + int r; + stbv_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]; + stbv_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); + STBV_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; + } + } + STBV_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 { + stbv_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 + STBV_CHECK(f); + for (i=0; i < f->channels; ++i) + stbv_inverse_mdct(f->channel_buffers[i], n, f, m->blockflag); + STBV_CHECK(f); + + // this shouldn't be necessary, unless we exited on an error + // and want to flush to get to the next packet + stbv_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 & STBV_PAGEFLAG_last_page)) { + stbv_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 + STBV_CHECK(f); + + return TRUE; +} + +static int stbv_vorbis_decode_packet(stbv_vorb *f, int *len, int *p_left, int *p_right) +{ + int mode, left_end, right_end; + if (!stbv_vorbis_decode_initial(f, p_left, &left_end, p_right, &right_end, &mode)) return 0; + return stbv_vorbis_decode_packet_rest(f, len, f->mode_config + mode, *p_left, left_end, *p_right, right_end, p_left); +} + +static int stbv_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 = stbv_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 stbv_vorbis_pump_first_frame(stb_vorbis *f) +{ + int len, right, left, res; + res = stbv_vorbis_decode_packet(f, &len, &left, &right); + if (res) + stbv_vorbis_finish_frame(f, len, left, right); + return res; +} + +#ifndef STB_VORBIS_NO_PUSHDATA_API +static int stbv_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 stbv_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; + stbv_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 stbv_error(f, VORBIS_invalid_stream); + if (s == f->segment_count) + s = -1; // set 'crosses page' flag + if (p > f->stream_end) return stbv_error(f, VORBIS_need_more_data); + first = FALSE; + } + for (; s == -1;) { + stbv_uint8 *q; + int n; + + // check that we have the page header ready + if (p + 26 >= f->stream_end) return stbv_error(f, VORBIS_need_more_data); + // validate the page + if (memcmp(p, stbv_ogg_page_header, 4)) return stbv_error(f, VORBIS_invalid_stream); + if (p[4] != 0) return stbv_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] & STBV_PAGEFLAG_continued_packet)) return stbv_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] & STBV_PAGEFLAG_continued_packet)) return stbv_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 stbv_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 stbv_error(f, VORBIS_invalid_stream); + if (s == n) + s = -1; // set 'crosses page' flag + if (p > f->stream_end) return stbv_error(f, VORBIS_need_more_data); + first = FALSE; + } + return TRUE; +} +#endif // !STB_VORBIS_NO_PUSHDATA_API + +static int stbv_start_decoder(stbv_vorb *f) +{ + stbv_uint8 header[6], x,y; + int len,i,j,k, max_submaps = 0; + int longest_floorlist=0; + + // first page, first packet + + if (!stbv_start_page(f)) return FALSE; + // validate page flag + if (!(f->page_flag & STBV_PAGEFLAG_first_page)) return stbv_error(f, VORBIS_invalid_first_page); + if (f->page_flag & STBV_PAGEFLAG_last_page) return stbv_error(f, VORBIS_invalid_first_page); + if (f->page_flag & STBV_PAGEFLAG_continued_packet) return stbv_error(f, VORBIS_invalid_first_page); + // check for expected packet length + if (f->segment_count != 1) return stbv_error(f, VORBIS_invalid_first_page); + if (f->segments[0] != 30) return stbv_error(f, VORBIS_invalid_first_page); + // read packet + // check packet header + if (stbv_get8(f) != STBV_VORBIS_packet_id) return stbv_error(f, VORBIS_invalid_first_page); + if (!stbv_getn(f, header, 6)) return stbv_error(f, VORBIS_unexpected_eof); + if (!stbv_vorbis_validate(header)) return stbv_error(f, VORBIS_invalid_first_page); + // vorbis_version + if (stbv_get32(f) != 0) return stbv_error(f, VORBIS_invalid_first_page); + f->channels = stbv_get8(f); if (!f->channels) return stbv_error(f, VORBIS_invalid_first_page); + if (f->channels > STB_VORBIS_MAX_CHANNELS) return stbv_error(f, VORBIS_too_many_channels); + f->sample_rate = stbv_get32(f); if (!f->sample_rate) return stbv_error(f, VORBIS_invalid_first_page); + stbv_get32(f); // bitrate_maximum + stbv_get32(f); // bitrate_nominal + stbv_get32(f); // bitrate_minimum + x = stbv_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 stbv_error(f, VORBIS_invalid_setup); + if (log1 < 6 || log1 > 13) return stbv_error(f, VORBIS_invalid_setup); + if (log0 > log1) return stbv_error(f, VORBIS_invalid_setup); + } + + // framing_flag + x = stbv_get8(f); + if (!(x & 1)) return stbv_error(f, VORBIS_invalid_first_page); + + // second packet! + if (!stbv_start_page(f)) return FALSE; + + if (!stbv_start_packet(f)) return FALSE; + do { + len = stbv_next_segment(f); + stbv_skip(f, len); + f->bytes_in_seg = 0; + } while (len); + + // third packet! + if (!stbv_start_packet(f)) return FALSE; + + #ifndef STB_VORBIS_NO_PUSHDATA_API + if (STBV_IS_PUSH_MODE(f)) { + if (!stbv_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 + + stbv_crc32_init(); // always init it, to avoid multithread race conditions + + if (stbv_get8_packet(f) != STBV_VORBIS_packet_setup) return stbv_error(f, VORBIS_invalid_setup); + for (i=0; i < 6; ++i) header[i] = stbv_get8_packet(f); + if (!stbv_vorbis_validate(header)) return stbv_error(f, VORBIS_invalid_setup); + + // codebooks + + f->codebook_count = stbv_get_bits(f,8) + 1; + f->codebooks = (StbvCodebook *) stbv_setup_malloc(f, sizeof(*f->codebooks) * f->codebook_count); + if (f->codebooks == NULL) return stbv_error(f, VORBIS_outofmem); + memset(f->codebooks, 0, sizeof(*f->codebooks) * f->codebook_count); + for (i=0; i < f->codebook_count; ++i) { + stbv_uint32 *values; + int ordered, sorted_count; + int total=0; + stbv_uint8 *lengths; + StbvCodebook *c = f->codebooks+i; + STBV_CHECK(f); + x = stbv_get_bits(f, 8); if (x != 0x42) return stbv_error(f, VORBIS_invalid_setup); + x = stbv_get_bits(f, 8); if (x != 0x43) return stbv_error(f, VORBIS_invalid_setup); + x = stbv_get_bits(f, 8); if (x != 0x56) return stbv_error(f, VORBIS_invalid_setup); + x = stbv_get_bits(f, 8); + c->dimensions = (stbv_get_bits(f, 8)<<8) + x; + x = stbv_get_bits(f, 8); + y = stbv_get_bits(f, 8); + c->entries = (stbv_get_bits(f, 8)<<16) + (y<<8) + x; + ordered = stbv_get_bits(f,1); + c->sparse = ordered ? 0 : stbv_get_bits(f,1); + + if (c->dimensions == 0 && c->entries != 0) return stbv_error(f, VORBIS_invalid_setup); + + if (c->sparse) + lengths = (stbv_uint8 *) stbv_setup_temp_malloc(f, c->entries); + else + lengths = c->codeword_lengths = (stbv_uint8 *) stbv_setup_malloc(f, c->entries); + + if (!lengths) return stbv_error(f, VORBIS_outofmem); + + if (ordered) { + int current_entry = 0; + int current_length = stbv_get_bits(f,5) + 1; + while (current_entry < c->entries) { + int limit = c->entries - current_entry; + int n = stbv_get_bits(f, stbv_ilog(limit)); + if (current_entry + n > (int) c->entries) { return stbv_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 ? stbv_get_bits(f,1) : 1; + if (present) { + lengths[j] = stbv_get_bits(f, 5) + 1; + ++total; + if (lengths[j] == 32) + return stbv_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 = (stbv_uint8 *) stbv_setup_malloc(f, c->entries); + if (c->codeword_lengths == NULL) return stbv_error(f, VORBIS_outofmem); + memcpy(c->codeword_lengths, lengths, c->entries); + stbv_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; + + STBV_CHECK(f); + if (!c->sparse) { + c->codewords = (stbv_uint32 *) stbv_setup_malloc(f, sizeof(c->codewords[0]) * c->entries); + if (!c->codewords) return stbv_error(f, VORBIS_outofmem); + } else { + unsigned int size; + if (c->sorted_entries) { + c->codeword_lengths = (stbv_uint8 *) stbv_setup_malloc(f, c->sorted_entries); + if (!c->codeword_lengths) return stbv_error(f, VORBIS_outofmem); + c->codewords = (stbv_uint32 *) stbv_setup_temp_malloc(f, sizeof(*c->codewords) * c->sorted_entries); + if (!c->codewords) return stbv_error(f, VORBIS_outofmem); + values = (stbv_uint32 *) stbv_setup_temp_malloc(f, sizeof(*values) * c->sorted_entries); + if (!values) return stbv_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 (!stbv_compute_codewords(c, lengths, c->entries, values)) { + if (c->sparse) stbv_setup_temp_free(f, values, 0); + return stbv_error(f, VORBIS_invalid_setup); + } + + if (c->sorted_entries) { + // allocate an extra slot for sentinels + c->sorted_codewords = (stbv_uint32 *) stbv_setup_malloc(f, sizeof(*c->sorted_codewords) * (c->sorted_entries+1)); + if (c->sorted_codewords == NULL) return stbv_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 *) stbv_setup_malloc(f, sizeof(*c->sorted_values ) * (c->sorted_entries+1)); + if (c->sorted_values == NULL) return stbv_error(f, VORBIS_outofmem); + ++c->sorted_values; + c->sorted_values[-1] = -1; + stbv_compute_sorted_huffman(c, lengths, values); + } + + if (c->sparse) { + stbv_setup_temp_free(f, values, sizeof(*values)*c->sorted_entries); + stbv_setup_temp_free(f, c->codewords, sizeof(*c->codewords)*c->sorted_entries); + stbv_setup_temp_free(f, lengths, c->entries); + c->codewords = NULL; + } + + stbv_compute_accelerated_huffman(c); + + STBV_CHECK(f); + c->lookup_type = stbv_get_bits(f, 4); + if (c->lookup_type > 2) return stbv_error(f, VORBIS_invalid_setup); + if (c->lookup_type > 0) { + stbv_uint16 *mults; + c->minimum_value = stbv_float32_unpack(stbv_get_bits(f, 32)); + c->delta_value = stbv_float32_unpack(stbv_get_bits(f, 32)); + c->value_bits = stbv_get_bits(f, 4)+1; + c->sequence_p = stbv_get_bits(f,1); + if (c->lookup_type == 1) { + c->lookup_values = stbv_lookup1_values(c->entries, c->dimensions); + } else { + c->lookup_values = c->entries * c->dimensions; + } + if (c->lookup_values == 0) return stbv_error(f, VORBIS_invalid_setup); + mults = (stbv_uint16 *) stbv_setup_temp_malloc(f, sizeof(mults[0]) * c->lookup_values); + if (mults == NULL) return stbv_error(f, VORBIS_outofmem); + for (j=0; j < (int) c->lookup_values; ++j) { + int q = stbv_get_bits(f, c->value_bits); + if (q == STBV_EOP) { stbv_setup_temp_free(f,mults,sizeof(mults[0])*c->lookup_values); return stbv_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 stbv_skip; + c->multiplicands = (stbv_codetype *) stbv_setup_malloc(f, sizeof(c->multiplicands[0]) * c->sorted_entries * c->dimensions); + } else + c->multiplicands = (stbv_codetype *) stbv_setup_malloc(f, sizeof(c->multiplicands[0]) * c->entries * c->dimensions); + if (c->multiplicands == NULL) { stbv_setup_temp_free(f,mults,sizeof(mults[0])*c->lookup_values); return stbv_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) { + stbv_setup_temp_free(f, mults,sizeof(mults[0])*c->lookup_values); + return stbv_error(f, VORBIS_invalid_setup); + } + div *= c->lookup_values; + } + } + } + c->lookup_type = 2; + } + else +#endif + { + float last=0; + STBV_CHECK(f); + c->multiplicands = (stbv_codetype *) stbv_setup_malloc(f, sizeof(c->multiplicands[0]) * c->lookup_values); + if (c->multiplicands == NULL) { stbv_setup_temp_free(f, mults,sizeof(mults[0])*c->lookup_values); return stbv_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 + stbv_skip:; +#endif + stbv_setup_temp_free(f, mults, sizeof(mults[0])*c->lookup_values); + + STBV_CHECK(f); + } + STBV_CHECK(f); + } + + // time domain transfers (notused) + + x = stbv_get_bits(f, 6) + 1; + for (i=0; i < x; ++i) { + stbv_uint32 z = stbv_get_bits(f, 16); + if (z != 0) return stbv_error(f, VORBIS_invalid_setup); + } + + // Floors + f->floor_count = stbv_get_bits(f, 6)+1; + f->floor_config = (StbvFloor *) stbv_setup_malloc(f, f->floor_count * sizeof(*f->floor_config)); + if (f->floor_config == NULL) return stbv_error(f, VORBIS_outofmem); + for (i=0; i < f->floor_count; ++i) { + f->floor_types[i] = stbv_get_bits(f, 16); + if (f->floor_types[i] > 1) return stbv_error(f, VORBIS_invalid_setup); + if (f->floor_types[i] == 0) { + StbvFloor0 *g = &f->floor_config[i].floor0; + g->order = stbv_get_bits(f,8); + g->rate = stbv_get_bits(f,16); + g->bark_map_size = stbv_get_bits(f,16); + g->amplitude_bits = stbv_get_bits(f,6); + g->amplitude_offset = stbv_get_bits(f,8); + g->number_of_books = stbv_get_bits(f,4) + 1; + for (j=0; j < g->number_of_books; ++j) + g->book_list[j] = stbv_get_bits(f,8); + return stbv_error(f, VORBIS_feature_not_supported); + } else { + stbv_floor_ordering p[31*8+2]; + StbvFloor1 *g = &f->floor_config[i].floor1; + int max_class = -1; + g->partitions = stbv_get_bits(f, 5); + for (j=0; j < g->partitions; ++j) { + g->partition_class_list[j] = stbv_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] = stbv_get_bits(f, 3)+1; + g->class_subclasses[j] = stbv_get_bits(f, 2); + if (g->class_subclasses[j]) { + g->class_masterbooks[j] = stbv_get_bits(f, 8); + if (g->class_masterbooks[j] >= f->codebook_count) return stbv_error(f, VORBIS_invalid_setup); + } + for (k=0; k < 1 << g->class_subclasses[j]; ++k) { + g->subclass_books[j][k] = stbv_get_bits(f,8)-1; + if (g->subclass_books[j][k] >= f->codebook_count) return stbv_error(f, VORBIS_invalid_setup); + } + } + g->floor1_multiplier = stbv_get_bits(f,2)+1; + g->rangebits = stbv_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] = stbv_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]), stbv_point_compare); + for (j=0; j < g->values; ++j) + g->sorted_order[j] = (stbv_uint8) p[j].id; + // precompute the stbv_neighbors + for (j=2; j < g->values; ++j) { + int low,hi; + stbv_neighbors(g->Xlist, j, &low,&hi); + g->stbv_neighbors[j][0] = low; + g->stbv_neighbors[j][1] = hi; + } + + if (g->values > longest_floorlist) + longest_floorlist = g->values; + } + } + + // StbvResidue + f->residue_count = stbv_get_bits(f, 6)+1; + f->residue_config = (StbvResidue *) stbv_setup_malloc(f, f->residue_count * sizeof(f->residue_config[0])); + if (f->residue_config == NULL) return stbv_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) { + stbv_uint8 residue_cascade[64]; + StbvResidue *r = f->residue_config+i; + f->residue_types[i] = stbv_get_bits(f, 16); + if (f->residue_types[i] > 2) return stbv_error(f, VORBIS_invalid_setup); + r->begin = stbv_get_bits(f, 24); + r->end = stbv_get_bits(f, 24); + if (r->end < r->begin) return stbv_error(f, VORBIS_invalid_setup); + r->part_size = stbv_get_bits(f,24)+1; + r->classifications = stbv_get_bits(f,6)+1; + r->classbook = stbv_get_bits(f,8); + if (r->classbook >= f->codebook_count) return stbv_error(f, VORBIS_invalid_setup); + for (j=0; j < r->classifications; ++j) { + stbv_uint8 high_bits=0; + stbv_uint8 low_bits=stbv_get_bits(f,3); + if (stbv_get_bits(f,1)) + high_bits = stbv_get_bits(f,5); + residue_cascade[j] = high_bits*8 + low_bits; + } + r->residue_books = (short (*)[8]) stbv_setup_malloc(f, sizeof(r->residue_books[0]) * r->classifications); + if (r->residue_books == NULL) return stbv_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] = stbv_get_bits(f, 8); + if (r->residue_books[j][k] >= f->codebook_count) return stbv_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 = (stbv_uint8 **) stbv_setup_malloc(f, sizeof(*r->classdata) * f->codebooks[r->classbook].entries); + if (!r->classdata) return stbv_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] = (stbv_uint8 *) stbv_setup_malloc(f, sizeof(r->classdata[j][0]) * classwords); + if (r->classdata[j] == NULL) return stbv_error(f, VORBIS_outofmem); + for (k=classwords-1; k >= 0; --k) { + r->classdata[j][k] = temp % r->classifications; + temp /= r->classifications; + } + } + } + + f->mapping_count = stbv_get_bits(f,6)+1; + f->mapping = (StbvMapping *) stbv_setup_malloc(f, f->mapping_count * sizeof(*f->mapping)); + if (f->mapping == NULL) return stbv_error(f, VORBIS_outofmem); + memset(f->mapping, 0, f->mapping_count * sizeof(*f->mapping)); + for (i=0; i < f->mapping_count; ++i) { + StbvMapping *m = f->mapping + i; + int mapping_type = stbv_get_bits(f,16); + if (mapping_type != 0) return stbv_error(f, VORBIS_invalid_setup); + m->chan = (StbvMappingChannel *) stbv_setup_malloc(f, f->channels * sizeof(*m->chan)); + if (m->chan == NULL) return stbv_error(f, VORBIS_outofmem); + if (stbv_get_bits(f,1)) + m->submaps = stbv_get_bits(f,4)+1; + else + m->submaps = 1; + if (m->submaps > max_submaps) + max_submaps = m->submaps; + if (stbv_get_bits(f,1)) { + m->coupling_steps = stbv_get_bits(f,8)+1; + for (k=0; k < m->coupling_steps; ++k) { + m->chan[k].magnitude = stbv_get_bits(f, stbv_ilog(f->channels-1)); + m->chan[k].angle = stbv_get_bits(f, stbv_ilog(f->channels-1)); + if (m->chan[k].magnitude >= f->channels) return stbv_error(f, VORBIS_invalid_setup); + if (m->chan[k].angle >= f->channels) return stbv_error(f, VORBIS_invalid_setup); + if (m->chan[k].magnitude == m->chan[k].angle) return stbv_error(f, VORBIS_invalid_setup); + } + } else + m->coupling_steps = 0; + + // reserved field + if (stbv_get_bits(f,2)) return stbv_error(f, VORBIS_invalid_setup); + if (m->submaps > 1) { + for (j=0; j < f->channels; ++j) { + m->chan[j].mux = stbv_get_bits(f, 4); + if (m->chan[j].mux >= m->submaps) return stbv_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) { + stbv_get_bits(f,8); // discard + m->submap_floor[j] = stbv_get_bits(f,8); + m->submap_residue[j] = stbv_get_bits(f,8); + if (m->submap_floor[j] >= f->floor_count) return stbv_error(f, VORBIS_invalid_setup); + if (m->submap_residue[j] >= f->residue_count) return stbv_error(f, VORBIS_invalid_setup); + } + } + + // Modes + f->mode_count = stbv_get_bits(f, 6)+1; + for (i=0; i < f->mode_count; ++i) { + StbvMode *m = f->mode_config+i; + m->blockflag = stbv_get_bits(f,1); + m->windowtype = stbv_get_bits(f,16); + m->transformtype = stbv_get_bits(f,16); + m->mapping = stbv_get_bits(f,8); + if (m->windowtype != 0) return stbv_error(f, VORBIS_invalid_setup); + if (m->transformtype != 0) return stbv_error(f, VORBIS_invalid_setup); + if (m->mapping >= f->mapping_count) return stbv_error(f, VORBIS_invalid_setup); + } + + stbv_flush_packet(f); + + f->previous_length = 0; + + for (i=0; i < f->channels; ++i) { + f->channel_buffers[i] = (float *) stbv_setup_malloc(f, sizeof(float) * f->blocksize_1); + f->previous_window[i] = (float *) stbv_setup_malloc(f, sizeof(float) * f->blocksize_1/2); + f->finalY[i] = (stbv_int16 *) stbv_setup_malloc(f, sizeof(stbv_int16) * longest_floorlist); + if (f->channel_buffers[i] == NULL || f->previous_window[i] == NULL || f->finalY[i] == NULL) return stbv_error(f, VORBIS_outofmem); + memset(f->channel_buffers[i], 0, sizeof(float) * f->blocksize_1); + #ifdef STB_VORBIS_NO_DEFER_FLOOR + f->floor_buffers[i] = (float *) stbv_setup_malloc(f, sizeof(float) * f->blocksize_1/2); + if (f->floor_buffers[i] == NULL) return stbv_error(f, VORBIS_outofmem); + #endif + } + + if (!stbv_init_blocksize(f, 0, f->blocksize_0)) return FALSE; + if (!stbv_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 (stbv_integer_divide_table[1][1]==0) + for (i=0; i < STBV_DIVTAB_NUMER; ++i) + for (j=1; j < STBV_DIVTAB_DENOM; ++j) + stbv_integer_divide_table[i][j] = i / j; +#endif + + // compute how much temporary memory is needed + + // 1. + { + stbv_uint32 imdct_mem = (f->blocksize_1 * sizeof(float) >> 1); + stbv_uint32 classify_mem; + int i,max_part_read=0; + for (i=0; i < f->residue_count; ++i) { + StbvResidue *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(stbv_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 stbv_error(f, VORBIS_outofmem); + } + + f->first_audio_page_offset = stb_vorbis_get_file_offset(f); + + return TRUE; +} + +static void stbv_vorbis_deinit(stb_vorbis *p) +{ + int i,j; + if (p->residue_config) { + for (i=0; i < p->residue_count; ++i) { + StbvResidue *r = p->residue_config+i; + if (r->classdata) { + for (j=0; j < p->codebooks[r->classbook].entries; ++j) + stbv_setup_free(p, r->classdata[j]); + stbv_setup_free(p, r->classdata); + } + stbv_setup_free(p, r->residue_books); + } + } + + if (p->codebooks) { + STBV_CHECK(p); + for (i=0; i < p->codebook_count; ++i) { + StbvCodebook *c = p->codebooks + i; + stbv_setup_free(p, c->codeword_lengths); + stbv_setup_free(p, c->multiplicands); + stbv_setup_free(p, c->codewords); + stbv_setup_free(p, c->sorted_codewords); + // c->sorted_values[-1] is the first entry in the array + stbv_setup_free(p, c->sorted_values ? c->sorted_values-1 : NULL); + } + stbv_setup_free(p, p->codebooks); + } + stbv_setup_free(p, p->floor_config); + stbv_setup_free(p, p->residue_config); + if (p->mapping) { + for (i=0; i < p->mapping_count; ++i) + stbv_setup_free(p, p->mapping[i].chan); + stbv_setup_free(p, p->mapping); + } + STBV_CHECK(p); + for (i=0; i < p->channels && i < STB_VORBIS_MAX_CHANNELS; ++i) { + stbv_setup_free(p, p->channel_buffers[i]); + stbv_setup_free(p, p->previous_window[i]); + #ifdef STB_VORBIS_NO_DEFER_FLOOR + stbv_setup_free(p, p->floor_buffers[i]); + #endif + stbv_setup_free(p, p->finalY[i]); + } + for (i=0; i < 2; ++i) { + stbv_setup_free(p, p->A[i]); + stbv_setup_free(p, p->B[i]); + stbv_setup_free(p, p->C[i]); + stbv_setup_free(p, p->window[i]); + stbv_setup_free(p, p->stbv_bit_reverse[i]); + } + #ifndef STB_VORBIS_NO_STDIO + if (p->close_on_free) fclose(p->f); + #endif +} + +STBVDEF void stb_vorbis_close(stb_vorbis *p) +{ + if (p == NULL) return; + stbv_vorbis_deinit(p); + stbv_setup_free(p,p); +} + +static void stbv_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 +} + +STBVDEF int stb_vorbis_get_sample_offset(stb_vorbis *f) +{ + if (f->current_loc_valid) + return f->current_loc; + else + return -1; +} + +STBVDEF 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; +} + +STBVDEF int stb_vorbis_get_error(stb_vorbis *f) +{ + int e = f->error; + f->error = VORBIS__no_error; + return e; +} + +static stb_vorbis * stbv_vorbis_alloc(stb_vorbis *f) +{ + stb_vorbis *p = (stb_vorbis *) stbv_setup_malloc(f, sizeof(*p)); + return p; +} + +#ifndef STB_VORBIS_NO_PUSHDATA_API + +STBVDEF 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 stbv_vorbis_search_for_page_pushdata(stbv_vorb *f, stbv_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, stbv_ogg_page_header, 4)) { + int j,len; + stbv_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 = stbv_crc32_update(crc, data[i+j]); + // now process 4 0-bytes + for ( ; j < 26; ++j) + crc = stbv_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;) { + stbv_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 = stbv_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 +STBVDEF int stb_vorbis_decode_frame_pushdata( + stb_vorbis *f, // the file we're decoding + const stbv_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 (!STBV_IS_PUSH_MODE(f)) return stbv_error(f, VORBIS_invalid_api_mixing); + + if (f->page_crc_tests >= 0) { + *samples = 0; + return stbv_vorbis_search_for_page_pushdata(f, (stbv_uint8 *) data, data_len); + } + + f->stream = (stbv_uint8 *) data; + f->stream_end = (stbv_uint8 *) data + data_len; + f->error = VORBIS__no_error; + + // check that we have the entire packet in memory + if (!stbv_is_whole_packet_present(f, FALSE)) { + *samples = 0; + return 0; + } + + if (!stbv_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 (stbv_get8_packet(f) != STBV_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 (stbv_get8_packet(f) != STBV_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 = stbv_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); +} + +STBVDEF 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; + stbv_vorbis_init(&p, alloc); + p.stream = (stbv_uint8 *) data; + p.stream_end = (stbv_uint8 *) data + data_len; + p.push_mode = TRUE; + if (!stbv_start_decoder(&p)) { + if (p.eof) + *error = VORBIS_need_more_data; + else + *error = p.error; + return NULL; + } + f = stbv_vorbis_alloc(&p); + if (f) { + *f = p; + *data_used = (int) (f->stream - data); + *error = 0; + return f; + } else { + stbv_vorbis_deinit(&p); + return NULL; + } +} +#endif // STB_VORBIS_NO_PUSHDATA_API + +STBVDEF unsigned int stb_vorbis_get_file_offset(stb_vorbis *f) +{ + #ifndef STB_VORBIS_NO_PUSHDATA_API + if (f->push_mode) return 0; + #endif + if (STBV_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 stbv_uint32 stbv_vorbis_find_page(stb_vorbis *f, stbv_uint32 *end, stbv_uint32 *last) +{ + for(;;) { + int n; + if (f->eof) return 0; + n = stbv_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 (stbv_get8(f) != stbv_ogg_page_header[i]) + break; + if (f->eof) return 0; + if (i == 4) { + stbv_uint8 header[27]; + stbv_uint32 i, crc, goal, len; + for (i=0; i < 4; ++i) + header[i] = stbv_ogg_page_header[i]; + for (; i < 27; ++i) + header[i] = stbv_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 = stbv_crc32_update(crc, header[i]); + len = 0; + for (i=0; i < header[26]; ++i) { + int s = stbv_get8(f); + crc = stbv_crc32_update(crc, s); + len += s; + } + if (len && f->eof) return 0; + for (i=0; i < len; ++i) + crc = stbv_crc32_update(crc, stbv_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; + } + stbv_set_file_offset(f, retry_loc-1); + return 1; + } + } + invalid: + // not a valid page, so rewind and look for next one + stbv_set_file_offset(f, retry_loc); + } + } +} + + +#define STBV_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 stbv_get_seek_page_info(stb_vorbis *f, StbvProbedPage *z) +{ + stbv_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 + stbv_getn(f, header, 27); + if (header[0] != 'O' || header[1] != 'g' || header[2] != 'g' || header[3] != 'S') + return 0; + stbv_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 + stbv_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 stbv_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; + + stbv_set_file_offset(f, previous_safe); + + while (stbv_vorbis_find_page(f, &end, NULL)) { + if (end >= limit_offset && stb_vorbis_get_file_offset(f) < limit_offset) + return 1; + stbv_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 stbv_seek_to_sample_coarse(stb_vorbis *f, stbv_uint32 sample_number) +{ + StbvProbedPage left, right, mid; + int i, start_seg_with_known_loc, end_pos, page_start; + stbv_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 stbv_error(f, VORBIS_seek_without_length); + if (sample_number > stream_length) return stbv_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' + stbv_set_file_offset(f, left.page_end); + if (!stbv_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 + stbv_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; + + stbv_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) + stbv_set_file_offset(f, left.page_end + (delta / 2) - 32768); + } + + if (!stbv_vorbis_find_page(f, NULL, NULL)) goto error; + } + + for (;;) { + if (!stbv_get_seek_page_info(f, &mid)) goto error; + if (mid.last_decoded_sample != ~0U) break; + // (untested) no frames end on this page + stbv_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; + stbv_set_file_offset(f, page_start); + if (!stbv_start_page(f)) return stbv_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 & STBV_PAGEFLAG_continued_packet)) + break; + + // (untested) the final packet begins on an earlier page + if (!stbv_go_to_page_before(f, page_start)) + goto error; + + page_start = stb_vorbis_get_file_offset(f); + if (!stbv_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++) + stbv_skip(f, f->segments[i]); + + // start decoding (optimizable - this frame is generally discarded) + if (!stbv_vorbis_pump_first_frame(f)) + return 0; + if (f->current_loc > sample_number) + return stbv_error(f, VORBIS_seek_failed); + return 1; + +error: + // try to restore the file to a valid state + stb_vorbis_seek_start(f); + return stbv_error(f, VORBIS_seek_failed); +} + +// the same as stbv_vorbis_decode_initial, but without advancing +static int stbv_peek_decode_initial(stbv_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 (!stbv_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 + stbv_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; + stbv_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; +} + +STBVDEF int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number) +{ + stbv_uint32 max_frame_samples; + + if (STBV_IS_PUSH_MODE(f)) return stbv_error(f, VORBIS_invalid_api_mixing); + + // fast page-level search + if (!stbv_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 (!stbv_peek_decode_initial(f, &left_start, &left_end, &right_start, &right_end, &mode)) + return stbv_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 + stbv_vorbis_pump_first_frame(f); + } else { + // this frame is too early to be relevant + f->current_loc += frame_samples; + f->previous_length = 0; + stbv_maybe_start_packet(f); + stbv_flush_packet(f); + } + } + // the next frame will start with the sample + assert(f->current_loc == sample_number); + return 1; +} + +STBVDEF 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; + stbv_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; +} + +STBVDEF int stb_vorbis_seek_start(stb_vorbis *f) +{ + if (STBV_IS_PUSH_MODE(f)) { return stbv_error(f, VORBIS_invalid_api_mixing); } + stbv_set_file_offset(f, f->first_audio_page_offset); + f->previous_length = 0; + f->first_decode = TRUE; + f->next_seg = -1; + return stbv_vorbis_pump_first_frame(f); +} + +STBVDEF unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f) +{ + unsigned int restore_offset, previous_safe; + unsigned int end, last_page_loc; + + if (STBV_IS_PUSH_MODE(f)) return stbv_error(f, VORBIS_invalid_api_mixing); + if (!f->total_samples) { + unsigned int last; + stbv_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; + + stbv_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 (!stbv_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) { + stbv_set_file_offset(f, end); + if (!stbv_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); + } + + stbv_set_file_offset(f, last_page_loc); + + // parse the header + stbv_getn(f, (unsigned char *)header, 6); + // extract the absolute granule position + lo = stbv_get32(f); + hi = stbv_get32(f); + if (lo == 0xffffffff && hi == 0xffffffff) { + f->error = VORBIS_cant_find_last_page; + f->total_samples = STBV_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: + stbv_set_file_offset(f, restore_offset); + } + return f->total_samples == STBV_SAMPLE_unknown ? 0 : f->total_samples; +} + +STBVDEF float stb_vorbis_stream_length_in_seconds(stb_vorbis *f) +{ + return stb_vorbis_stream_length_in_samples(f) / (float) f->sample_rate; +} + + + +STBVDEF int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output) +{ + int len, right,left,i; + if (STBV_IS_PUSH_MODE(f)) return stbv_error(f, VORBIS_invalid_api_mixing); + + if (!stbv_vorbis_decode_packet(f, &len, &left, &right)) { + f->channel_buffer_start = f->channel_buffer_end = 0; + return 0; + } + + len = stbv_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 + +STBVDEF 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; + stbv_vorbis_init(&p, alloc); + p.f = file; + p.f_start = (stbv_uint32) ftell(file); + p.stream_len = length; + p.close_on_free = close_on_free; + if (stbv_start_decoder(&p)) { + f = stbv_vorbis_alloc(&p); + if (f) { + *f = p; + stbv_vorbis_pump_first_frame(f); + return f; + } + } + if (error) *error = p.error; + stbv_vorbis_deinit(&p); + return NULL; +} + +STBVDEF 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); +} + +STBVDEF 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 + +STBVDEF 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; + stbv_vorbis_init(&p, alloc); + p.stream = (stbv_uint8 *) data; + p.stream_end = (stbv_uint8 *) data + len; + p.stream_start = (stbv_uint8 *) p.stream; + p.stream_len = len; + p.push_mode = FALSE; + if (stbv_start_decoder(&p)) { + f = stbv_vorbis_alloc(&p); + if (f) { + *f = p; + stbv_vorbis_pump_first_frame(f); + if (error) *error = VORBIS__no_error; + return f; + } + } + if (error) *error = p.error; + stbv_vorbis_deinit(&p); + return NULL; +} + +#ifndef STB_VORBIS_NO_INTEGER_CONVERSION +#define STBV_PLAYBACK_MONO 1 +#define STBV_PLAYBACK_LEFT 2 +#define STBV_PLAYBACK_RIGHT 4 + +#define STBV_L (STBV_PLAYBACK_LEFT | STBV_PLAYBACK_MONO) +#define STBV_C (STBV_PLAYBACK_LEFT | STBV_PLAYBACK_RIGHT | STBV_PLAYBACK_MONO) +#define STBV_R (STBV_PLAYBACK_RIGHT | STBV_PLAYBACK_MONO) + +static stbv_int8 stbv_channel_position[7][6] = +{ + { 0 }, + { STBV_C }, + { STBV_L, STBV_R }, + { STBV_L, STBV_C, STBV_R }, + { STBV_L, STBV_R, STBV_L, STBV_R }, + { STBV_L, STBV_C, STBV_R, STBV_L, STBV_R }, + { STBV_L, STBV_C, STBV_R, STBV_L, STBV_R, STBV_C }, +}; + + +#ifndef STB_VORBIS_NO_FAST_SCALED_FLOAT + typedef union { + float f; + int i; + } stbv_float_conv; + typedef char stb_vorbis_float_size_test[sizeof(float)==4 && sizeof(int) == 4]; + #define STBV_FASTDEF(x) stbv_float_conv x + // add (1<<23) to convert to int, then divide by 2^SHIFT, then add 0.5/2^SHIFT to round + #define STBV_MAGIC(SHIFT) (1.5f * (1 << (23-SHIFT)) + 0.5f/(1 << SHIFT)) + #define STBV_ADDEND(SHIFT) (((150-SHIFT) << 23) + (1 << 22)) + #define STBV_FAST_SCALED_FLOAT_TO_INT(temp,x,s) (temp.f = (x) + STBV_MAGIC(s), temp.i - STBV_ADDEND(s)) + #define stbv_check_endianness() +#else + #define STBV_FAST_SCALED_FLOAT_TO_INT(temp,x,s) ((int) ((x) * (1 << (s)))) + #define stbv_check_endianness() + #define STBV_FASTDEF(x) +#endif + +static void stbv_copy_samples(short *dest, float *src, int len) +{ + int i; + stbv_check_endianness(); + for (i=0; i < len; ++i) { + STBV_FASTDEF(temp); + int v = STBV_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 stbv_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; + stbv_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 (stbv_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) { + STBV_FASTDEF(temp); + int v = STBV_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 stbv_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 + stbv_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 = stbv_channel_position[num_c][j] & (STBV_PLAYBACK_LEFT | STBV_PLAYBACK_RIGHT); + if (m == (STBV_PLAYBACK_LEFT | STBV_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 == STBV_PLAYBACK_LEFT) { + for (i=0; i < n; ++i) { + buffer[i*2+0] += data[j][d_offset+o+i]; + } + } else if (m == STBV_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) { + STBV_FASTDEF(temp); + int v = STBV_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 stbv_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}, {STBV_PLAYBACK_MONO}, {STBV_PLAYBACK_LEFT, STBV_PLAYBACK_RIGHT} }; + for (i=0; i < buf_c; ++i) + stbv_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) + stbv_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); + } +} + +STBVDEF 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) + stbv_convert_samples_short(num_c, buffer, 0, f->channels, output, 0, len); + return len; +} + +static void stbv_convert_channels_short_interleaved(int buf_c, short *buffer, int data_c, float **data, int d_offset, int len) +{ + int i; + stbv_check_endianness(); + if (buf_c != data_c && buf_c <= 2 && data_c <= 6) { + assert(buf_c == 2); + for (i=0; i < buf_c; ++i) + stbv_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) { + STBV_FASTDEF(temp); + float f = data[i][d_offset+j]; + int v = STBV_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; + } + } +} + +STBVDEF 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; + stbv_convert_channels_short_interleaved(num_c, buffer, f->channels, output, 0, len); + } + return len; +} + +STBVDEF 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) + stbv_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; +} + +STBVDEF 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) + stbv_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 +STBVDEF 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 + +STBVDEF int stb_vorbis_decode_memory(const stbv_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 + +STBVDEF 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; +} + +STBVDEF 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 stbv_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_IMPLEMENTATION + + +/* +------------------------------------------------------------------------------ +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/tinyobj_loader_c.h b/raylib/external/tinyobj_loader_c.h new file mode 100644 index 0000000..242b47d --- /dev/null +++ b/raylib/external/tinyobj_loader_c.h @@ -0,0 +1,1587 @@ +/* + The MIT License (MIT) + + Copyright (c) 2016 - 2019 Syoyo Fujita and many contributors. + + 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. + */ +#ifndef TINOBJ_LOADER_C_H_ +#define TINOBJ_LOADER_C_H_ + +/* @todo { Remove stddef dependency. unsigned int? } ---> RAY: DONE. */ +//#include + +typedef struct { + char *name; + + 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 (see http://www.fileformat.info/format/material/) */ + int illum; + + int pad0; + + 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; + +typedef struct { + char *name; /* group name or object name. */ + unsigned int face_offset; + unsigned int length; +} tinyobj_shape_t; + +typedef struct { int v_idx, vt_idx, vn_idx; } tinyobj_vertex_index_t; + +typedef struct { + unsigned int num_vertices; + unsigned int num_normals; + unsigned int num_texcoords; + unsigned int num_faces; + unsigned int num_face_num_verts; + + int pad0; + + float *vertices; + float *normals; + float *texcoords; + tinyobj_vertex_index_t *faces; + int *face_num_verts; + int *material_ids; +} tinyobj_attrib_t; + + +#define TINYOBJ_FLAG_TRIANGULATE (1 << 0) + +#define TINYOBJ_INVALID_INDEX (0x80000000) + +#define TINYOBJ_SUCCESS (0) +#define TINYOBJ_ERROR_EMPTY (-1) +#define TINYOBJ_ERROR_INVALID_PARAMETER (-2) +#define TINYOBJ_ERROR_FILE_OPERATION (-3) + +/* Parse wavefront .obj(.obj string data is expanded to linear char array `buf') + * flags are combination of TINYOBJ_FLAG_*** + * Returns TINYOBJ_SUCCESS if things goes well. + * Returns TINYOBJ_ERR_*** when there is an error. + */ +extern int tinyobj_parse_obj(tinyobj_attrib_t *attrib, tinyobj_shape_t **shapes, + unsigned int *num_shapes, tinyobj_material_t **materials, + unsigned int *num_materials, const char *buf, unsigned int len, + unsigned int flags); +extern int tinyobj_parse_mtl_file(tinyobj_material_t **materials_out, + unsigned int *num_materials_out, + const char *filename); + +extern void tinyobj_attrib_init(tinyobj_attrib_t *attrib); +extern void tinyobj_attrib_free(tinyobj_attrib_t *attrib); +extern void tinyobj_shapes_free(tinyobj_shape_t *shapes, unsigned int num_shapes); +extern void tinyobj_materials_free(tinyobj_material_t *materials, + unsigned int num_materials); + +#ifdef TINYOBJ_LOADER_C_IMPLEMENTATION +#include +#include +#include +#include + +#if defined(TINYOBJ_MALLOC) && defined(TINYOBJ_REALLOC) && defined(TINYOBJ_CALLOC) && defined(TINYOBJ_FREE) +/* ok */ +#elif !defined(TINYOBJ_MALLOC) && !defined(TINYOBJ_REALLOC) && !defined(TINYOBJ_CALLOC) && !defined(TINYOBJ_FREE) +/* ok */ +#else +#error "Must define all or none of TINYOBJ_MALLOC, TINYOBJ_REALLOC, TINYOBJ_CALLOC and TINYOBJ_FREE." +#endif + +#ifndef TINYOBJ_MALLOC +#include +#define TINYOBJ_MALLOC malloc +#define TINYOBJ_REALLOC realloc +#define TINYOBJ_CALLOC calloc +#define TINYOBJ_FREE free +#endif + +#define TINYOBJ_MAX_FACES_PER_F_LINE (16) + +#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t')) +#define IS_DIGIT(x) ((unsigned int)((x) - '0') < (unsigned int)(10)) +#define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0')) + +static void skip_space(const char **token) { + while ((*token)[0] == ' ' || (*token)[0] == '\t') { + (*token)++; + } +} + +static void skip_space_and_cr(const char **token) { + while ((*token)[0] == ' ' || (*token)[0] == '\t' || (*token)[0] == '\r') { + (*token)++; + } +} + +static int until_space(const char *token) { + const char *p = token; + while (p[0] != '\0' && p[0] != ' ' && p[0] != '\t' && p[0] != '\r') { + p++; + } + + return (int)(p - token); +} + +static unsigned int length_until_newline(const char *token, unsigned int n) { + unsigned int len = 0; + + /* Assume token[n-1] = '\0' */ + for (len = 0; len < n - 1; len++) { + if (token[len] == '\n') { + break; + } + if ((token[len] == '\r') && ((len < (n - 2)) && (token[len + 1] != '\n'))) { + break; + } + } + + return len; +} + +static unsigned int length_until_line_feed(const char *token, unsigned int n) { + unsigned int len = 0; + + /* Assume token[n-1] = '\0' */ + for (len = 0; len < n; len++) { + if ((token[len] == '\n') || (token[len] == '\r')) { + break; + } + } + + return len; +} + +/* http://stackoverflow.com/questions/5710091/how-does-atoi-function-in-c-work +*/ +static int my_atoi(const char *c) { + int value = 0; + int sign = 1; + if (*c == '+' || *c == '-') { + if (*c == '-') sign = -1; + c++; + } + while (((*c) >= '0') && ((*c) <= '9')) { /* isdigit(*c) */ + value *= 10; + value += (int)(*c - '0'); + c++; + } + return value * sign; +} + +/* Make index zero-base, and also support relative index. */ +static int fixIndex(int idx, unsigned int n) { + if (idx > 0) return idx - 1; + if (idx == 0) return 0; + return (int)n + idx; /* negative value = relative */ +} + +/* Parse raw triples: i, i/j/k, i//k, i/j */ +static tinyobj_vertex_index_t parseRawTriple(const char **token) { + tinyobj_vertex_index_t vi; + /* 0x80000000 = -2147483648 = invalid */ + vi.v_idx = (int)(0x80000000); + vi.vn_idx = (int)(0x80000000); + vi.vt_idx = (int)(0x80000000); + + vi.v_idx = my_atoi((*token)); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && + (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + if ((*token)[0] != '/') { + return vi; + } + (*token)++; + + /* i//k */ + if ((*token)[0] == '/') { + (*token)++; + vi.vn_idx = my_atoi((*token)); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && + (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + return vi; + } + + /* i/j/k or i/j */ + vi.vt_idx = my_atoi((*token)); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && + (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + if ((*token)[0] != '/') { + return vi; + } + + /* i/j/k */ + (*token)++; /* skip '/' */ + vi.vn_idx = my_atoi((*token)); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && + (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + return vi; +} + +static int parseInt(const char **token) { + int i = 0; + skip_space(token); + i = my_atoi((*token)); + (*token) += until_space((*token)); + return i; +} + +/* + * Tries to parse a floating point number located at s. + * + * s_end should be a location in the string where reading should absolutely + * stop. For example at the end of the string, to prevent buffer overflows. + * + * Parses the following EBNF grammar: + * sign = "+" | "-" ; + * END = ? anything not in digit ? + * digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; + * integer = [sign] , digit , {digit} ; + * decimal = integer , ["." , integer] ; + * float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; + * + * Valid strings are for example: + * -0 +3.1417e+2 -0.0E-3 1.0324 -1.41 11e2 + * + * If the parsing is a success, result is set to the parsed value and true + * is returned. + * + * The function is greedy and will parse until any of the following happens: + * - a non-conforming character is encountered. + * - s_end is reached. + * + * The following situations triggers a failure: + * - s >= s_end. + * - parse failure. + */ +static int tryParseDouble(const char *s, const char *s_end, double *result) { + double mantissa = 0.0; + /* This exponent is base 2 rather than 10. + * However the exponent we parse is supposed to be one of ten, + * thus we must take care to convert the exponent/and or the + * mantissa to a * 2^E, where a is the mantissa and E is the + * exponent. + * To get the final double we will use ldexp, it requires the + * exponent to be in base 2. + */ + int exponent = 0; + + /* NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED + * TO JUMP OVER DEFINITIONS. + */ + char sign = '+'; + char exp_sign = '+'; + char const *curr = s; + + /* How many characters were read in a loop. */ + int read = 0; + /* Tells whether a loop terminated due to reaching s_end. */ + int end_not_reached = 0; + + /* + BEGIN PARSING. + */ + + if (s >= s_end) { + return 0; /* fail */ + } + + /* Find out what sign we've got. */ + if (*curr == '+' || *curr == '-') { + sign = *curr; + curr++; + } else if (IS_DIGIT(*curr)) { /* Pass through. */ + } else { + goto fail; + } + + /* Read the integer part. */ + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + mantissa *= 10; + mantissa += (int)(*curr - 0x30); + curr++; + read++; + end_not_reached = (curr != s_end); + } + + /* We must make sure we actually got something. */ + if (read == 0) goto fail; + /* We allow numbers of form "#", "###" etc. */ + if (!end_not_reached) goto assemble; + + /* Read the decimal part. */ + if (*curr == '.') { + curr++; + read = 1; + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + /* pow(10.0, -read) */ + double frac_value = 1.0; + int f; + for (f = 0; f < read; f++) { + frac_value *= 0.1; + } + mantissa += (int)(*curr - 0x30) * frac_value; + read++; + curr++; + end_not_reached = (curr != s_end); + } + } else if (*curr == 'e' || *curr == 'E') { + } else { + goto assemble; + } + + if (!end_not_reached) goto assemble; + + /* Read the exponent part. */ + if (*curr == 'e' || *curr == 'E') { + curr++; + /* Figure out if a sign is present and if it is. */ + end_not_reached = (curr != s_end); + if (end_not_reached && (*curr == '+' || *curr == '-')) { + exp_sign = *curr; + curr++; + } else if (IS_DIGIT(*curr)) { /* Pass through. */ + } else { + /* Empty E is not allowed. */ + goto fail; + } + + read = 0; + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + exponent *= 10; + exponent += (int)(*curr - 0x30); + curr++; + read++; + end_not_reached = (curr != s_end); + } + if (read == 0) goto fail; + } + +assemble : + + { + double a = 1.0; /* = pow(5.0, exponent); */ + double b = 1.0; /* = 2.0^exponent */ + int i; + for (i = 0; i < exponent; i++) { + a = a * 5.0; + } + + for (i = 0; i < exponent; i++) { + b = b * 2.0; + } + + if (exp_sign == '-') { + a = 1.0 / a; + b = 1.0 / b; + } + + *result = + /* (sign == '+' ? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), + exponent); */ + (sign == '+' ? 1 : -1) * (mantissa * a * b); + } + + return 1; +fail: + return 0; +} + +static float parseFloat(const char **token) { + const char *end; + double val = 0.0; + float f = 0.0f; + skip_space(token); + end = (*token) + until_space((*token)); + val = 0.0; + tryParseDouble((*token), end, &val); + f = (float)(val); + (*token) = end; + return f; +} + +static void parseFloat2(float *x, float *y, const char **token) { + (*x) = parseFloat(token); + (*y) = parseFloat(token); +} + +static void parseFloat3(float *x, float *y, float *z, const char **token) { + (*x) = parseFloat(token); + (*y) = parseFloat(token); + (*z) = parseFloat(token); +} + +static unsigned int my_strnlen(const char *s, unsigned int n) { + const char *p = memchr(s, 0, n); + return p ? (unsigned int)(p - s) : n; +} + +static char *my_strdup(const char *s, unsigned int max_length) { + char *d; + unsigned int len; + + if (s == NULL) return NULL; + + /* Do not consider CRLF line ending(#19) */ + len = length_until_line_feed(s, max_length); + /* len = strlen(s); */ + + /* trim line ending and append '\0' */ + d = (char *)TINYOBJ_MALLOC(len + 1); /* + '\0' */ + memcpy(d, s, (unsigned int)(len)); + d[len] = '\0'; + + return d; +} + +static char *my_strndup(const char *s, unsigned int len) { + char *d; + unsigned int slen; + + if (s == NULL) return NULL; + if (len == 0) return NULL; + + slen = my_strnlen(s, len); + d = (char *)TINYOBJ_MALLOC(slen + 1); /* + '\0' */ + if (!d) { + return NULL; + } + memcpy(d, s, slen); + d[slen] = '\0'; + + return d; +} + +char *dynamic_fgets(char **buf, unsigned int *size, FILE *file) { + char *offset; + char *ret; + unsigned int old_size; + + if (!(ret = fgets(*buf, (int)*size, file))) { + return ret; + } + + if (NULL != strchr(*buf, '\n')) { + return ret; + } + + do { + old_size = *size; + *size *= 2; + *buf = (char*)TINYOBJ_REALLOC(*buf, *size); + offset = &((*buf)[old_size - 1]); + + ret = fgets(offset, (int)(old_size + 1), file); + } while(ret && (NULL == strchr(*buf, '\n'))); + + return ret; +} + +static void initMaterial(tinyobj_material_t *material) { + int i; + material->name = NULL; + material->ambient_texname = NULL; + material->diffuse_texname = NULL; + material->specular_texname = NULL; + material->specular_highlight_texname = NULL; + material->bump_texname = NULL; + material->displacement_texname = NULL; + material->alpha_texname = NULL; + for (i = 0; i < 3; i++) { + material->ambient[i] = 0.f; + material->diffuse[i] = 0.f; + material->specular[i] = 0.f; + material->transmittance[i] = 0.f; + material->emission[i] = 0.f; + } + material->illum = 0; + material->dissolve = 1.f; + material->shininess = 1.f; + material->ior = 1.f; +} + +/* Implementation of string to int hashtable */ + +#define HASH_TABLE_ERROR 1 +#define HASH_TABLE_SUCCESS 0 + +#define HASH_TABLE_DEFAULT_SIZE 10 + +typedef struct hash_table_entry_t +{ + unsigned long hash; + int filled; + int pad0; + long value; + + struct hash_table_entry_t* next; +} hash_table_entry_t; + +typedef struct +{ + unsigned long* hashes; + hash_table_entry_t* entries; + unsigned int capacity; + unsigned int n; +} hash_table_t; + +static unsigned long hash_djb2(const unsigned char* str) +{ + unsigned long hash = 5381; + int c; + + while ((c = *str++)) { + hash = ((hash << 5) + hash) + (unsigned long)(c); + } + + return hash; +} + +static void create_hash_table(unsigned int start_capacity, hash_table_t* hash_table) +{ + if (start_capacity < 1) + start_capacity = HASH_TABLE_DEFAULT_SIZE; + hash_table->hashes = (unsigned long*) TINYOBJ_MALLOC(start_capacity * sizeof(unsigned long)); + hash_table->entries = (hash_table_entry_t*) TINYOBJ_CALLOC(start_capacity, sizeof(hash_table_entry_t)); + hash_table->capacity = start_capacity; + hash_table->n = 0; +} + +static void destroy_hash_table(hash_table_t* hash_table) +{ + TINYOBJ_FREE(hash_table->entries); + TINYOBJ_FREE(hash_table->hashes); +} + +/* Insert with quadratic probing */ +static int hash_table_insert_value(unsigned long hash, long value, hash_table_t* hash_table) +{ + /* Insert value */ + unsigned int start_index = hash % hash_table->capacity; + unsigned int index = start_index; + hash_table_entry_t* start_entry = hash_table->entries + start_index; + unsigned int i; + hash_table_entry_t* entry; + + for (i = 1; hash_table->entries[index].filled; i++) + { + if (i >= hash_table->capacity) + return HASH_TABLE_ERROR; + index = (start_index + (i * i)) % hash_table->capacity; + } + + entry = hash_table->entries + index; + entry->hash = hash; + entry->filled = 1; + entry->value = value; + + if (index != start_index) { + /* This is a new entry, but not the start entry, hence we need to add a next pointer to our entry */ + entry->next = start_entry->next; + start_entry->next = entry; + } + + return HASH_TABLE_SUCCESS; +} + +static int hash_table_insert(unsigned long hash, long value, hash_table_t* hash_table) +{ + int ret = hash_table_insert_value(hash, value, hash_table); + if (ret == HASH_TABLE_SUCCESS) + { + hash_table->hashes[hash_table->n] = hash; + hash_table->n++; + } + return ret; +} + +static hash_table_entry_t* hash_table_find(unsigned long hash, hash_table_t* hash_table) +{ + hash_table_entry_t* entry = hash_table->entries + (hash % hash_table->capacity); + while (entry) + { + if (entry->hash == hash && entry->filled) + { + return entry; + } + entry = entry->next; + } + return NULL; +} + +static void hash_table_maybe_grow(unsigned int new_n, hash_table_t* hash_table) +{ + unsigned int new_capacity; + hash_table_t new_hash_table; + unsigned int i; + + if (new_n <= hash_table->capacity) { + return; + } + new_capacity = 2 * ((2 * hash_table->capacity) > new_n ? hash_table->capacity : new_n); + /* Create a new hash table. We're not calling create_hash_table because we want to realloc the hash array */ + new_hash_table.hashes = hash_table->hashes = (unsigned long*) TINYOBJ_REALLOC((void*) hash_table->hashes, sizeof(unsigned long) * new_capacity); + new_hash_table.entries = (hash_table_entry_t*) TINYOBJ_CALLOC(new_capacity, sizeof(hash_table_entry_t)); + new_hash_table.capacity = new_capacity; + new_hash_table.n = hash_table->n; + + /* Rehash */ + for (i = 0; i < hash_table->capacity; i++) + { + hash_table_entry_t* entry = hash_table_find(hash_table->hashes[i], hash_table); + hash_table_insert_value(hash_table->hashes[i], entry->value, &new_hash_table); + } + + TINYOBJ_FREE(hash_table->entries); + (*hash_table) = new_hash_table; +} + +static int hash_table_exists(const char* name, hash_table_t* hash_table) +{ + return hash_table_find(hash_djb2((const unsigned char*)name), hash_table) != NULL; +} + +static void hash_table_set(const char* name, unsigned int val, hash_table_t* hash_table) +{ + /* Hash name */ + unsigned long hash = hash_djb2((const unsigned char *)name); + + hash_table_entry_t* entry = hash_table_find(hash, hash_table); + if (entry) + { + entry->value = (long)val; + return; + } + + /* Expand if necessary + * Grow until the element has been added + */ + do + { + hash_table_maybe_grow(hash_table->n + 1, hash_table); + } + while (hash_table_insert(hash, (long)val, hash_table) != HASH_TABLE_SUCCESS); +} + +static long hash_table_get(const char* name, hash_table_t* hash_table) +{ + hash_table_entry_t* ret = hash_table_find(hash_djb2((const unsigned char*)(name)), hash_table); + return ret->value; +} + +static tinyobj_material_t *tinyobj_material_add(tinyobj_material_t *prev, + unsigned int num_materials, + tinyobj_material_t *new_mat) { + tinyobj_material_t *dst; + dst = (tinyobj_material_t *)TINYOBJ_REALLOC( + prev, sizeof(tinyobj_material_t) * (num_materials + 1)); + + dst[num_materials] = (*new_mat); /* Just copy pointer for char* members */ + return dst; +} + +static int tinyobj_parse_and_index_mtl_file(tinyobj_material_t **materials_out, + unsigned int *num_materials_out, + const char *filename, + hash_table_t* material_table) { + tinyobj_material_t material; + unsigned int buffer_size = 128; + char *linebuf; + FILE *fp; + unsigned int num_materials = 0; + tinyobj_material_t *materials = NULL; + int has_previous_material = 0; + const char *line_end = NULL; + + if (materials_out == NULL) { + return TINYOBJ_ERROR_INVALID_PARAMETER; + } + + if (num_materials_out == NULL) { + return TINYOBJ_ERROR_INVALID_PARAMETER; + } + + (*materials_out) = NULL; + (*num_materials_out) = 0; + + fp = fopen(filename, "r"); + if (!fp) { + fprintf(stderr, "TINYOBJ: Error reading file '%s': %s (%d)\n", filename, strerror(errno), errno); + return TINYOBJ_ERROR_FILE_OPERATION; + } + + /* Create a default material */ + initMaterial(&material); + + linebuf = (char*)TINYOBJ_MALLOC(buffer_size); + while (NULL != dynamic_fgets(&linebuf, &buffer_size, fp)) { + const char *token = linebuf; + + line_end = token + strlen(token); + + /* Skip leading space. */ + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') continue; /* empty line */ + + if (token[0] == '#') continue; /* comment line */ + + /* new mtl */ + if ((0 == strncmp(token, "newmtl", 6)) && IS_SPACE((token[6]))) { + char namebuf[4096]; + + /* flush previous material. */ + if (has_previous_material) { + materials = tinyobj_material_add(materials, num_materials, &material); + num_materials++; + } else { + has_previous_material = 1; + } + + /* initial temporary material */ + initMaterial(&material); + + /* set new mtl name */ + token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + sscanf(token, "%s", namebuf); +#endif + material.name = my_strdup(namebuf, (unsigned int) (line_end - token)); + + /* Add material to material table */ + if (material_table) + hash_table_set(material.name, num_materials, material_table); + + continue; + } + + /* ambient */ + if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) { + float r, g, b; + token += 2; + parseFloat3(&r, &g, &b, &token); + material.ambient[0] = r; + material.ambient[1] = g; + material.ambient[2] = b; + continue; + } + + /* diffuse */ + if (token[0] == 'K' && token[1] == 'd' && IS_SPACE((token[2]))) { + float r, g, b; + token += 2; + parseFloat3(&r, &g, &b, &token); + material.diffuse[0] = r; + material.diffuse[1] = g; + material.diffuse[2] = b; + continue; + } + + /* specular */ + if (token[0] == 'K' && token[1] == 's' && IS_SPACE((token[2]))) { + float r, g, b; + token += 2; + parseFloat3(&r, &g, &b, &token); + material.specular[0] = r; + material.specular[1] = g; + material.specular[2] = b; + continue; + } + + /* transmittance */ + if (token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) { + float r, g, b; + token += 2; + parseFloat3(&r, &g, &b, &token); + material.transmittance[0] = r; + material.transmittance[1] = g; + material.transmittance[2] = b; + continue; + } + + /* ior(index of refraction) */ + if (token[0] == 'N' && token[1] == 'i' && IS_SPACE((token[2]))) { + token += 2; + material.ior = parseFloat(&token); + continue; + } + + /* emission */ + if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(token[2])) { + float r, g, b; + token += 2; + parseFloat3(&r, &g, &b, &token); + material.emission[0] = r; + material.emission[1] = g; + material.emission[2] = b; + continue; + } + + /* shininess */ + if (token[0] == 'N' && token[1] == 's' && IS_SPACE(token[2])) { + token += 2; + material.shininess = parseFloat(&token); + continue; + } + + /* illum model */ + if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) { + token += 6; + material.illum = parseInt(&token); + continue; + } + + /* dissolve */ + if ((token[0] == 'd' && IS_SPACE(token[1]))) { + token += 1; + material.dissolve = parseFloat(&token); + continue; + } + if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) { + token += 2; + /* Invert value of Tr(assume Tr is in range [0, 1]) */ + material.dissolve = 1.0f - parseFloat(&token); + continue; + } + + /* ambient texture */ + if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) { + token += 7; + material.ambient_texname = my_strdup(token, (unsigned int) (line_end - token)); + continue; + } + + /* diffuse texture */ + if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) { + token += 7; + material.diffuse_texname = my_strdup(token, (unsigned int) (line_end - token)); + continue; + } + + /* specular texture */ + if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) { + token += 7; + material.specular_texname = my_strdup(token, (unsigned int) (line_end - token)); + continue; + } + + /* specular highlight texture */ + if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) { + token += 7; + material.specular_highlight_texname = my_strdup(token, (unsigned int) (line_end - token)); + continue; + } + + /* bump texture */ + if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) { + token += 9; + material.bump_texname = my_strdup(token, (unsigned int) (line_end - token)); + continue; + } + + /* alpha texture */ + if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) { + token += 6; + material.alpha_texname = my_strdup(token, (unsigned int) (line_end - token)); + continue; + } + + /* bump texture */ + if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) { + token += 5; + material.bump_texname = my_strdup(token, (unsigned int) (line_end - token)); + continue; + } + + /* displacement texture */ + if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) { + token += 5; + material.displacement_texname = my_strdup(token, (unsigned int) (line_end - token)); + continue; + } + + /* @todo { unknown parameter } */ + } + + if (material.name) { + /* Flush last material element */ + materials = tinyobj_material_add(materials, num_materials, &material); + num_materials++; + } + + (*num_materials_out) = num_materials; + (*materials_out) = materials; + + if (linebuf) { + TINYOBJ_FREE(linebuf); + } + + return TINYOBJ_SUCCESS; +} + +int tinyobj_parse_mtl_file(tinyobj_material_t **materials_out, + unsigned int *num_materials_out, + const char *filename) { + return tinyobj_parse_and_index_mtl_file(materials_out, num_materials_out, filename, NULL); +} + + +typedef enum { + COMMAND_EMPTY, + COMMAND_V, + COMMAND_VN, + COMMAND_VT, + COMMAND_F, + COMMAND_G, + COMMAND_O, + COMMAND_USEMTL, + COMMAND_MTLLIB + +} CommandType; + +typedef struct { + float vx, vy, vz; + float nx, ny, nz; + float tx, ty; + + /* @todo { Use dynamic array } */ + tinyobj_vertex_index_t f[TINYOBJ_MAX_FACES_PER_F_LINE]; + unsigned int num_f; + + int f_num_verts[TINYOBJ_MAX_FACES_PER_F_LINE]; + unsigned int num_f_num_verts; + + const char *group_name; + unsigned int group_name_len; + int pad0; + + const char *object_name; + unsigned int object_name_len; + int pad1; + + const char *material_name; + unsigned int material_name_len; + int pad2; + + const char *mtllib_name; + unsigned int mtllib_name_len; + + CommandType type; +} Command; + +static int parseLine(Command *command, const char *p, unsigned int p_len, + int triangulate) { + char linebuf[4096]; + const char *token; + assert(p_len < 4095); + + memcpy(linebuf, p, p_len); + linebuf[p_len] = '\0'; + + token = linebuf; + + command->type = COMMAND_EMPTY; + + /* Skip leading space. */ + skip_space(&token); + + assert(token); + if (token[0] == '\0') { /* empty line */ + return 0; + } + + if (token[0] == '#') { /* comment line */ + return 0; + } + + /* vertex */ + if (token[0] == 'v' && IS_SPACE((token[1]))) { + float x, y, z; + token += 2; + parseFloat3(&x, &y, &z, &token); + command->vx = x; + command->vy = y; + command->vz = z; + command->type = COMMAND_V; + return 1; + } + + /* normal */ + if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { + float x, y, z; + token += 3; + parseFloat3(&x, &y, &z, &token); + command->nx = x; + command->ny = y; + command->nz = z; + command->type = COMMAND_VN; + return 1; + } + + /* texcoord */ + if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { + float x, y; + token += 3; + parseFloat2(&x, &y, &token); + command->tx = x; + command->ty = y; + command->type = COMMAND_VT; + return 1; + } + + /* face */ + if (token[0] == 'f' && IS_SPACE((token[1]))) { + unsigned int num_f = 0; + + tinyobj_vertex_index_t f[TINYOBJ_MAX_FACES_PER_F_LINE]; + token += 2; + skip_space(&token); + + while (!IS_NEW_LINE(token[0])) { + tinyobj_vertex_index_t vi = parseRawTriple(&token); + skip_space_and_cr(&token); + + f[num_f] = vi; + num_f++; + } + + command->type = COMMAND_F; + + if (triangulate) { + unsigned int k; + unsigned int n = 0; + + tinyobj_vertex_index_t i0 = f[0]; + tinyobj_vertex_index_t i1; + tinyobj_vertex_index_t i2 = f[1]; + + assert(3 * num_f < TINYOBJ_MAX_FACES_PER_F_LINE); + + for (k = 2; k < num_f; k++) { + i1 = i2; + i2 = f[k]; + command->f[3 * n + 0] = i0; + command->f[3 * n + 1] = i1; + command->f[3 * n + 2] = i2; + + command->f_num_verts[n] = 3; + n++; + } + command->num_f = 3 * n; + command->num_f_num_verts = n; + + } else { + unsigned int k = 0; + assert(num_f < TINYOBJ_MAX_FACES_PER_F_LINE); + for (k = 0; k < num_f; k++) { + command->f[k] = f[k]; + } + + command->num_f = num_f; + command->f_num_verts[0] = (int)num_f; + command->num_f_num_verts = 1; + } + + return 1; + } + + /* use mtl */ + if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { + token += 7; + + skip_space(&token); + command->material_name = p + (token - linebuf); + command->material_name_len = (unsigned int)length_until_newline( + token, (p_len - (unsigned int)(token - linebuf)) + 1); + command->type = COMMAND_USEMTL; + + return 1; + } + + /* load mtl */ + if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { + /* By specification, `mtllib` should be appear only once in .obj */ + token += 7; + + skip_space(&token); + command->mtllib_name = p + (token - linebuf); + command->mtllib_name_len = (unsigned int)length_until_newline( + token, p_len - (unsigned int)(token - linebuf)) + + 1; + command->type = COMMAND_MTLLIB; + + return 1; + } + + /* group name */ + if (token[0] == 'g' && IS_SPACE((token[1]))) { + /* @todo { multiple group name. } */ + token += 2; + + command->group_name = p + (token - linebuf); + command->group_name_len = (unsigned int)length_until_newline( + token, p_len - (unsigned int)(token - linebuf)) + + 1; + command->type = COMMAND_G; + + return 1; + } + + /* object name */ + if (token[0] == 'o' && IS_SPACE((token[1]))) { + /* @todo { multiple object name? } */ + token += 2; + + command->object_name = p + (token - linebuf); + command->object_name_len = (unsigned int)length_until_newline( + token, p_len - (unsigned int)(token - linebuf)) + + 1; + command->type = COMMAND_O; + + return 1; + } + + return 0; +} + +typedef struct { + unsigned int pos; + unsigned int len; +} LineInfo; + +static int is_line_ending(const char *p, unsigned int i, unsigned int end_i) { + if (p[i] == '\0') return 1; + if (p[i] == '\n') return 1; /* this includes \r\n */ + if (p[i] == '\r') { + if (((i + 1) < end_i) && (p[i + 1] != '\n')) { /* detect only \r case */ + return 1; + } + } + return 0; +} + +int tinyobj_parse_obj(tinyobj_attrib_t *attrib, tinyobj_shape_t **shapes, + unsigned int *num_shapes, tinyobj_material_t **materials_out, + unsigned int *num_materials_out, const char *buf, unsigned int len, + unsigned int flags) { + LineInfo *line_infos = NULL; + Command *commands = NULL; + unsigned int num_lines = 0; + + unsigned int num_v = 0; + unsigned int num_vn = 0; + unsigned int num_vt = 0; + unsigned int num_f = 0; + unsigned int num_faces = 0; + + int mtllib_line_index = -1; + + tinyobj_material_t *materials = NULL; + unsigned int num_materials = 0; + + hash_table_t material_table; + + if (len < 1) return TINYOBJ_ERROR_INVALID_PARAMETER; + if (attrib == NULL) return TINYOBJ_ERROR_INVALID_PARAMETER; + if (shapes == NULL) return TINYOBJ_ERROR_INVALID_PARAMETER; + if (num_shapes == NULL) return TINYOBJ_ERROR_INVALID_PARAMETER; + if (buf == NULL) return TINYOBJ_ERROR_INVALID_PARAMETER; + if (materials_out == NULL) return TINYOBJ_ERROR_INVALID_PARAMETER; + if (num_materials_out == NULL) return TINYOBJ_ERROR_INVALID_PARAMETER; + + tinyobj_attrib_init(attrib); + /* 1. Find '\n' and create line data. */ + { + unsigned int i; + unsigned int end_idx = len; + unsigned int prev_pos = 0; + unsigned int line_no = 0; + unsigned int last_line_ending = 0; + + /* Count # of lines. */ + for (i = 0; i < end_idx; i++) { + if (is_line_ending(buf, i, end_idx)) { + num_lines++; + last_line_ending = i; + } + } + /* The last char from the input may not be a line + * ending character so add an extra line if there + * are more characters after the last line ending + * that was found. */ + if (end_idx - last_line_ending > 0) { + num_lines++; + } + + if (num_lines == 0) return TINYOBJ_ERROR_EMPTY; + + line_infos = (LineInfo *)TINYOBJ_MALLOC(sizeof(LineInfo) * num_lines); + + /* Fill line infos. */ + for (i = 0; i < end_idx; i++) { + if (is_line_ending(buf, i, end_idx)) { + line_infos[line_no].pos = prev_pos; + line_infos[line_no].len = i - prev_pos; + prev_pos = i + 1; + line_no++; + } + } + if (end_idx - last_line_ending > 0) { + line_infos[line_no].pos = prev_pos; + line_infos[line_no].len = end_idx - 1 - last_line_ending; + } + } + + commands = (Command *)TINYOBJ_MALLOC(sizeof(Command) * num_lines); + + create_hash_table(HASH_TABLE_DEFAULT_SIZE, &material_table); + + /* 2. parse each line */ + { + unsigned int i = 0; + for (i = 0; i < num_lines; i++) { + int ret = parseLine(&commands[i], &buf[line_infos[i].pos], + line_infos[i].len, flags & TINYOBJ_FLAG_TRIANGULATE); + if (ret) { + if (commands[i].type == COMMAND_V) { + num_v++; + } else if (commands[i].type == COMMAND_VN) { + num_vn++; + } else if (commands[i].type == COMMAND_VT) { + num_vt++; + } else if (commands[i].type == COMMAND_F) { + num_f += commands[i].num_f; + num_faces += commands[i].num_f_num_verts; + } + + if (commands[i].type == COMMAND_MTLLIB) { + mtllib_line_index = (int)i; + } + } + } + } + + /* line_infos are not used anymore. Release memory. */ + if (line_infos) { + TINYOBJ_FREE(line_infos); + } + + /* Load material(if exits) */ + if (mtllib_line_index >= 0 && commands[mtllib_line_index].mtllib_name && + commands[mtllib_line_index].mtllib_name_len > 0) { + char *filename = my_strndup(commands[mtllib_line_index].mtllib_name, + commands[mtllib_line_index].mtllib_name_len); + + int ret = tinyobj_parse_and_index_mtl_file(&materials, &num_materials, filename, &material_table); + + if (ret != TINYOBJ_SUCCESS) { + /* warning. */ + fprintf(stderr, "TINYOBJ: Failed to parse material file '%s': %d\n", filename, ret); + } + + TINYOBJ_FREE(filename); + + } + + /* Construct attributes */ + + { + unsigned int v_count = 0; + unsigned int n_count = 0; + unsigned int t_count = 0; + unsigned int f_count = 0; + unsigned int face_count = 0; + int material_id = -1; /* -1 = default unknown material. */ + unsigned int i = 0; + + attrib->vertices = (float *)TINYOBJ_MALLOC(sizeof(float) * num_v * 3); + attrib->num_vertices = (unsigned int)num_v; + attrib->normals = (float *)TINYOBJ_MALLOC(sizeof(float) * num_vn * 3); + attrib->num_normals = (unsigned int)num_vn; + attrib->texcoords = (float *)TINYOBJ_MALLOC(sizeof(float) * num_vt * 2); + attrib->num_texcoords = (unsigned int)num_vt; + attrib->faces = (tinyobj_vertex_index_t *)TINYOBJ_MALLOC(sizeof(tinyobj_vertex_index_t) * num_f); + attrib->face_num_verts = (int *)TINYOBJ_MALLOC(sizeof(int) * num_faces); + + attrib->num_faces = (unsigned int)num_faces; + attrib->num_face_num_verts = (unsigned int)num_f; + + attrib->material_ids = (int *)TINYOBJ_MALLOC(sizeof(int) * num_faces); + + for (i = 0; i < num_lines; i++) { + if (commands[i].type == COMMAND_EMPTY) { + continue; + } else if (commands[i].type == COMMAND_USEMTL) { + /* @todo + if (commands[t][i].material_name && + commands[t][i].material_name_len > 0) { + std::string material_name(commands[t][i].material_name, + commands[t][i].material_name_len); + + if (material_map.find(material_name) != material_map.end()) { + material_id = material_map[material_name]; + } else { + // Assign invalid material ID + material_id = -1; + } + } + */ + if (commands[i].material_name && + commands[i].material_name_len >0) + { + /* 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; + + if (hash_table_exists(material_name_null_term, &material_table)) + material_id = (int)hash_table_get(material_name_null_term, &material_table); + else + material_id = -1; + + TINYOBJ_FREE(material_name_null_term); + } + } else if (commands[i].type == COMMAND_V) { + attrib->vertices[3 * v_count + 0] = commands[i].vx; + attrib->vertices[3 * v_count + 1] = commands[i].vy; + attrib->vertices[3 * v_count + 2] = commands[i].vz; + v_count++; + } else if (commands[i].type == COMMAND_VN) { + attrib->normals[3 * n_count + 0] = commands[i].nx; + attrib->normals[3 * n_count + 1] = commands[i].ny; + attrib->normals[3 * n_count + 2] = commands[i].nz; + n_count++; + } else if (commands[i].type == COMMAND_VT) { + attrib->texcoords[2 * t_count + 0] = commands[i].tx; + attrib->texcoords[2 * t_count + 1] = commands[i].ty; + t_count++; + } else if (commands[i].type == COMMAND_F) { + unsigned int k = 0; + for (k = 0; k < commands[i].num_f; k++) { + tinyobj_vertex_index_t vi = commands[i].f[k]; + int v_idx = fixIndex(vi.v_idx, v_count); + int vn_idx = fixIndex(vi.vn_idx, n_count); + int vt_idx = fixIndex(vi.vt_idx, t_count); + attrib->faces[f_count + k].v_idx = v_idx; + attrib->faces[f_count + k].vn_idx = vn_idx; + attrib->faces[f_count + k].vt_idx = vt_idx; + } + + for (k = 0; k < commands[i].num_f_num_verts; k++) { + attrib->material_ids[face_count + k] = material_id; + attrib->face_num_verts[face_count + k] = commands[i].f_num_verts[k]; + } + + f_count += commands[i].num_f; + face_count += commands[i].num_f_num_verts; + } + } + } + + /* 5. Construct shape information. */ + { + unsigned int face_count = 0; + unsigned int i = 0; + unsigned int n = 0; + unsigned int shape_idx = 0; + + const char *shape_name = NULL; + unsigned int shape_name_len = 0; + const char *prev_shape_name = NULL; + unsigned int prev_shape_name_len = 0; + unsigned int prev_shape_face_offset = 0; + unsigned int prev_face_offset = 0; + tinyobj_shape_t prev_shape = {NULL, 0, 0}; + + /* Find the number of shapes in .obj */ + for (i = 0; i < num_lines; i++) { + if (commands[i].type == COMMAND_O || commands[i].type == COMMAND_G) { + n++; + } + } + + /* Allocate array of shapes with maximum possible size(+1 for unnamed + * group/object). + * Actual # of shapes found in .obj is determined in the later */ + (*shapes) = (tinyobj_shape_t*)TINYOBJ_MALLOC(sizeof(tinyobj_shape_t) * (n + 1)); + + for (i = 0; i < num_lines; i++) { + if (commands[i].type == COMMAND_O || commands[i].type == COMMAND_G) { + if (commands[i].type == COMMAND_O) { + shape_name = commands[i].object_name; + shape_name_len = commands[i].object_name_len; + } else { + shape_name = commands[i].group_name; + shape_name_len = commands[i].group_name_len; + } + + if (face_count == 0) { + /* 'o' or 'g' appears before any 'f' */ + prev_shape_name = shape_name; + prev_shape_name_len = shape_name_len; + prev_shape_face_offset = face_count; + prev_face_offset = face_count; + } else { + if (shape_idx == 0) { + /* 'o' or 'g' after some 'v' lines. */ + (*shapes)[shape_idx].name = my_strndup( + prev_shape_name, prev_shape_name_len); /* may be NULL */ + (*shapes)[shape_idx].face_offset = prev_shape.face_offset; + (*shapes)[shape_idx].length = face_count - prev_face_offset; + shape_idx++; + + prev_face_offset = face_count; + + } else { + if ((face_count - prev_face_offset) > 0) { + (*shapes)[shape_idx].name = + my_strndup(prev_shape_name, prev_shape_name_len); + (*shapes)[shape_idx].face_offset = prev_face_offset; + (*shapes)[shape_idx].length = face_count - prev_face_offset; + shape_idx++; + prev_face_offset = face_count; + } + } + + /* Record shape info for succeeding 'o' or 'g' command. */ + prev_shape_name = shape_name; + prev_shape_name_len = shape_name_len; + prev_shape_face_offset = face_count; + } + } + if (commands[i].type == COMMAND_F) { + face_count++; + } + } + + if ((face_count - prev_face_offset) > 0) { + unsigned int length = face_count - prev_shape_face_offset; + if (length > 0) { + (*shapes)[shape_idx].name = + my_strndup(prev_shape_name, prev_shape_name_len); + (*shapes)[shape_idx].face_offset = prev_face_offset; + (*shapes)[shape_idx].length = face_count - prev_face_offset; + shape_idx++; + } + } else { + /* Guess no 'v' line occurrence after 'o' or 'g', so discards current + * shape information. */ + } + + (*num_shapes) = shape_idx; + } + + if (commands) { + TINYOBJ_FREE(commands); + } + + destroy_hash_table(&material_table); + + (*materials_out) = materials; + (*num_materials_out) = num_materials; + + return TINYOBJ_SUCCESS; +} + +void tinyobj_attrib_init(tinyobj_attrib_t *attrib) { + attrib->vertices = NULL; + attrib->num_vertices = 0; + attrib->normals = NULL; + attrib->num_normals = 0; + attrib->texcoords = NULL; + attrib->num_texcoords = 0; + attrib->faces = NULL; + attrib->num_faces = 0; + attrib->face_num_verts = NULL; + attrib->num_face_num_verts = 0; + attrib->material_ids = NULL; +} + +void tinyobj_attrib_free(tinyobj_attrib_t *attrib) { + if (attrib->vertices) TINYOBJ_FREE(attrib->vertices); + if (attrib->normals) TINYOBJ_FREE(attrib->normals); + if (attrib->texcoords) TINYOBJ_FREE(attrib->texcoords); + if (attrib->faces) TINYOBJ_FREE(attrib->faces); + if (attrib->face_num_verts) TINYOBJ_FREE(attrib->face_num_verts); + if (attrib->material_ids) TINYOBJ_FREE(attrib->material_ids); +} + +void tinyobj_shapes_free(tinyobj_shape_t *shapes, unsigned int num_shapes) { + unsigned int i; + if (shapes == NULL) return; + + for (i = 0; i < num_shapes; i++) { + if (shapes[i].name) TINYOBJ_FREE(shapes[i].name); + } + + TINYOBJ_FREE(shapes); +} + +void tinyobj_materials_free(tinyobj_material_t *materials, + unsigned int num_materials) { + unsigned int i; + if (materials == NULL) return; + + for (i = 0; i < num_materials; i++) { + if (materials[i].name) TINYOBJ_FREE(materials[i].name); + if (materials[i].ambient_texname) TINYOBJ_FREE(materials[i].ambient_texname); + if (materials[i].diffuse_texname) TINYOBJ_FREE(materials[i].diffuse_texname); + if (materials[i].specular_texname) TINYOBJ_FREE(materials[i].specular_texname); + if (materials[i].specular_highlight_texname) + TINYOBJ_FREE(materials[i].specular_highlight_texname); + if (materials[i].bump_texname) TINYOBJ_FREE(materials[i].bump_texname); + if (materials[i].displacement_texname) + TINYOBJ_FREE(materials[i].displacement_texname); + if (materials[i].alpha_texname) TINYOBJ_FREE(materials[i].alpha_texname); + } + + TINYOBJ_FREE(materials); +} +#endif /* TINYOBJ_LOADER_C_IMPLEMENTATION */ + +#endif /* TINOBJ_LOADER_C_H_ */ diff --git a/raylib/gestures.h b/raylib/gestures.h index a4546eb..7b3d7f3 100644 --- a/raylib/gestures.h +++ b/raylib/gestures.h @@ -8,7 +8,7 @@ * * #define GESTURES_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 +* 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. * * #define GESTURES_STANDALONE @@ -24,7 +24,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2018 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2019 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. @@ -216,8 +216,8 @@ static float pinchDistance = 0.0f; // PINCH displacement distance ( static int currentGesture = GESTURE_NONE; // Current detected gesture -// Enabled gestures flags, all gestures enabled by default -static unsigned int enabledGestures = 0b0000001111111111; +// Enabled gestures flags, all gestures enabled by default +static unsigned int enabledGestures = 0b0000001111111111; //---------------------------------------------------------------------------------- // Module specific Functions Declaration @@ -251,13 +251,13 @@ void ProcessGestureEvent(GestureEvent event) { // Reset required variables pointCount = event.pointCount; // Required on UpdateGestures() - + if (pointCount < 2) { if (event.touchAction == TOUCH_DOWN) { tapCounter++; // Tap counter - + // Detect GESTURE_DOUBLE_TAP if ((currentGesture == GESTURE_NONE) && (tapCounter >= 2) && ((GetCurrentTime() - eventTime) < TAP_TIMEOUT) && (Vector2Distance(touchDownPosition, event.position[0]) < DOUBLETAP_RANGE)) { @@ -269,15 +269,15 @@ void ProcessGestureEvent(GestureEvent event) tapCounter = 1; currentGesture = GESTURE_TAP; } - + touchDownPosition = event.position[0]; touchDownDragPosition = event.position[0]; - + touchUpPosition = touchDownPosition; eventTime = GetCurrentTime(); - + firstTouchId = event.pointerId[0]; - + dragVector = (Vector2){ 0.0f, 0.0f }; } else if (event.touchAction == TOUCH_UP) @@ -287,15 +287,15 @@ void ProcessGestureEvent(GestureEvent event) // NOTE: dragIntensity dependend on the resolution of the screen dragDistance = Vector2Distance(touchDownPosition, touchUpPosition); dragIntensity = dragDistance/(float)((GetCurrentTime() - swipeTime)); - + startMoving = false; - + // Detect GESTURE_SWIPE if ((dragIntensity > FORCE_TO_SWIPE) && (firstTouchId == event.pointerId[0])) { // NOTE: Angle should be inverted in Y dragAngle = 360.0f - Vector2Angle(touchDownPosition, touchUpPosition); - + if ((dragAngle < 30) || (dragAngle > 330)) currentGesture = GESTURE_SWIPE_RIGHT; // Right else if ((dragAngle > 30) && (dragAngle < 120)) currentGesture = GESTURE_SWIPE_UP; // Up else if ((dragAngle > 120) && (dragAngle < 210)) currentGesture = GESTURE_SWIPE_LEFT; // Left @@ -307,31 +307,31 @@ void ProcessGestureEvent(GestureEvent event) dragDistance = 0.0f; dragIntensity = 0.0f; dragAngle = 0.0f; - + currentGesture = GESTURE_NONE; } - + touchDownDragPosition = (Vector2){ 0.0f, 0.0f }; pointCount = 0; } else if (event.touchAction == TOUCH_MOVE) { if (currentGesture == GESTURE_DRAG) eventTime = GetCurrentTime(); - + if (!startMoving) { swipeTime = GetCurrentTime(); startMoving = true; } - + moveDownPosition = event.position[0]; - + if (currentGesture == GESTURE_HOLD) { if (resetHold) touchDownPosition = event.position[0]; - + resetHold = false; - + // Detect GESTURE_DRAG if (Vector2Distance(touchDownPosition, moveDownPosition) >= MINIMUM_DRAG) { @@ -339,7 +339,7 @@ void ProcessGestureEvent(GestureEvent event) currentGesture = GESTURE_DRAG; } } - + dragVector.x = moveDownPosition.x - touchDownDragPosition.x; dragVector.y = moveDownPosition.y - touchDownDragPosition.y; } @@ -350,28 +350,28 @@ void ProcessGestureEvent(GestureEvent event) { touchDownPosition = event.position[0]; touchDownPosition2 = event.position[1]; - + //pinchDistance = Vector2Distance(touchDownPosition, touchDownPosition2); - + pinchVector.x = touchDownPosition2.x - touchDownPosition.x; pinchVector.y = touchDownPosition2.y - touchDownPosition.y; - + currentGesture = GESTURE_HOLD; timeHold = GetCurrentTime(); } else if (event.touchAction == TOUCH_MOVE) { pinchDistance = Vector2Distance(moveDownPosition, moveDownPosition2); - + touchDownPosition = moveDownPosition; touchDownPosition2 = moveDownPosition2; - + moveDownPosition = event.position[0]; moveDownPosition2 = event.position[1]; - + pinchVector.x = moveDownPosition2.x - moveDownPosition.x; pinchVector.y = moveDownPosition2.y - moveDownPosition.y; - + if ((Vector2Distance(touchDownPosition, moveDownPosition) >= MINIMUM_PINCH) || (Vector2Distance(touchDownPosition2, moveDownPosition2) >= MINIMUM_PINCH)) { if ((Vector2Distance(moveDownPosition, moveDownPosition2) - pinchDistance) < 0) currentGesture = GESTURE_PINCH_IN; @@ -382,7 +382,7 @@ void ProcessGestureEvent(GestureEvent event) currentGesture = GESTURE_HOLD; timeHold = GetCurrentTime(); } - + // NOTE: Angle should be inverted in Y pinchAngle = 360.0f - Vector2Angle(moveDownPosition, moveDownPosition2); } @@ -392,7 +392,7 @@ void ProcessGestureEvent(GestureEvent event) pinchAngle = 0.0f; pinchVector = (Vector2){ 0.0f, 0.0f }; pointCount = 0; - + currentGesture = GESTURE_NONE; } } @@ -409,14 +409,14 @@ void UpdateGestures(void) currentGesture = GESTURE_HOLD; timeHold = GetCurrentTime(); } - + if (((GetCurrentTime() - eventTime) > TAP_TIMEOUT) && (currentGesture == GESTURE_DRAG) && (pointCount < 2)) { currentGesture = GESTURE_HOLD; timeHold = GetCurrentTime(); resetHold = true; } - + // Detect GESTURE_NONE if ((currentGesture == GESTURE_SWIPE_RIGHT) || (currentGesture == GESTURE_SWIPE_UP) || (currentGesture == GESTURE_SWIPE_LEFT) || (currentGesture == GESTURE_SWIPE_DOWN)) { @@ -428,7 +428,7 @@ void UpdateGestures(void) int GetTouchPointsCount(void) { // NOTE: point count is calculated when ProcessGestureEvent(GestureEvent event) is called - + return pointCount; } @@ -443,11 +443,11 @@ int GetGestureDetected(void) float GetGestureHoldDuration(void) { // NOTE: time is calculated on current gesture HOLD - + double time = 0.0; - + if (currentGesture == GESTURE_HOLD) time = GetCurrentTime() - timeHold; - + return (float)time; } @@ -455,7 +455,7 @@ float GetGestureHoldDuration(void) Vector2 GetGestureDragVector(void) { // NOTE: drag vector is calculated on one touch points TOUCH_MOVE - + return dragVector; } @@ -464,7 +464,7 @@ Vector2 GetGestureDragVector(void) float GetGestureDragAngle(void) { // NOTE: drag angle is calculated on one touch points TOUCH_UP - + return dragAngle; } @@ -473,7 +473,7 @@ Vector2 GetGesturePinchVector(void) { // NOTE: The position values used for pinchDistance are not modified like the position values of [core.c]-->GetTouchPosition(int index) // NOTE: pinch distance is calculated on two touch points TOUCH_MOVE - + return pinchVector; } @@ -482,7 +482,7 @@ Vector2 GetGesturePinchVector(void) float GetGesturePinchAngle(void) { // NOTE: pinch angle is calculated on two touch points TOUCH_MOVE - + return pinchAngle; } @@ -494,7 +494,7 @@ float GetGesturePinchAngle(void) static float Vector2Angle(Vector2 v1, Vector2 v2) { float angle = atan2f(v2.y - v1.y, v2.x - v1.x)*(180.0f/PI); - + if (angle < 0) angle += 360.0f; return angle; @@ -518,13 +518,13 @@ static float Vector2Distance(Vector2 v1, Vector2 v2) static double GetCurrentTime(void) { double time = 0; - + #if defined(_WIN32) unsigned long long int clockFrequency, currentTime; - + QueryPerformanceFrequency(&clockFrequency); // BE CAREFUL: Costly operation! QueryPerformanceCounter(¤tTime); - + time = (double)currentTime/clockFrequency*1000.0f; // Time in miliseconds #endif @@ -533,24 +533,24 @@ static double GetCurrentTime(void) struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); uint64_t nowTime = (uint64_t)now.tv_sec*1000000000LLU + (uint64_t)now.tv_nsec; // Time in nanoseconds - + time = ((double)nowTime/1000000.0); // Time in miliseconds #endif #if defined(__APPLE__) //#define CLOCK_REALTIME CALENDAR_CLOCK // returns UTC time since 1970-01-01 //#define CLOCK_MONOTONIC SYSTEM_CLOCK // returns the time since boot time - + clock_serv_t cclock; mach_timespec_t now; host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); - + // NOTE: OS X does not have clock_gettime(), using clock_get_time() clock_get_time(cclock, &now); mach_port_deallocate(mach_task_self(), cclock); uint64_t nowTime = (uint64_t)now.tv_sec*1000000000LLU + (uint64_t)now.tv_nsec; // Time in nanoseconds - time = ((double)nowTime/1000000.0); // Time in miliseconds + time = ((double)nowTime/1000000.0); // Time in miliseconds #endif return time; diff --git a/raylib/mini_al.c b/raylib/miniaudio.c similarity index 86% rename from raylib/mini_al.c rename to raylib/miniaudio.c index 35cc002..54384c4 100644 --- a/raylib/mini_al.c +++ b/raylib/miniaudio.c @@ -1,4 +1,4 @@ // The implementation of mini_al needs to #include windows.h which means it needs to go into // it's own translation unit. Not doing this will cause conflicts with CloseWindow(), etc. #define MAL_IMPLEMENTATION -#include "external/mini_al.h" +#include "external/miniaudio.h" diff --git a/raylib/models.c b/raylib/models.c index 8a49813..d612f1c 100644 --- a/raylib/models.c +++ b/raylib/models.c @@ -17,7 +17,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2018 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2019 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. @@ -36,9 +36,13 @@ * **********************************************************************************************/ -#include "config.h" // Defines module configuration flags #include "raylib.h" // Declares module functions +// Check if config flags have been externally provided on compilation line +#if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags +#endif + #include "utils.h" // Required for: fopen() Android mapping #include // Required for: FILE, fopen(), fclose(), fscanf(), feof(), rewind(), fgets() @@ -48,14 +52,15 @@ #include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 2.1, 3.3+ or ES2 -#if defined(SUPPORT_FILEFORMAT_IQM) - #define RIQM_IMPLEMENTATION - #include "external/riqm.h" // IQM file format loading +#if defined(SUPPORT_FILEFORMAT_OBJ) || defined(SUPPORT_FILEFORMAT_MTL) + #define TINYOBJ_LOADER_C_IMPLEMENTATION + #include "external/tinyobj_loader_c.h" // OBJ/MTL file formats loading #endif #if defined(SUPPORT_FILEFORMAT_GLTF) #define CGLTF_IMPLEMENTATION #include "external/cgltf.h" // glTF file format loading + #include "external/stb_image.h" // glTF texture images loading #endif #if defined(SUPPORT_MESH_GENERATION) @@ -66,7 +71,7 @@ //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -// ... +#define MAX_MESH_VBO 7 // Maximum number of vbo per mesh //---------------------------------------------------------------------------------- // Types and Structures Definition @@ -82,16 +87,13 @@ // Module specific Functions Declaration //---------------------------------------------------------------------------------- #if defined(SUPPORT_FILEFORMAT_OBJ) -static Mesh LoadOBJ(const char *fileName); // Load OBJ mesh data +static Model LoadOBJ(const char *fileName); // Load OBJ mesh data #endif -#if defined(SUPPORT_FILEFORMAT_MTL) -static Material LoadMTL(const char *fileName); // Load MTL material data +#if defined(SUPPORT_FILEFORMAT_IQM) +static Model LoadIQM(const char *fileName); // Load IQM mesh data #endif #if defined(SUPPORT_FILEFORMAT_GLTF) -static Mesh LoadIQM(const char *fileName); // Load IQM mesh data -#endif -#if defined(SUPPORT_FILEFORMAT_GLTF) -static Mesh LoadGLTF(const char *fileName); // Load GLTF mesh data +static Model LoadGLTF(const char *fileName); // Load GLTF mesh data #endif //---------------------------------------------------------------------------------- @@ -108,9 +110,26 @@ void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color) rlEnd(); } +// Draw a point in 3D space--actually a small line. +void DrawPoint3D(Vector3 position, Color color) +{ + if (rlCheckBufferLimit(8)) rlglDraw(); + + rlPushMatrix(); + rlTranslatef(position.x, position.y, position.z); + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex3f(0.0,0.0,0.0); + rlVertex3f(0.0,0.0,0.1); + rlEnd(); + rlPopMatrix(); +} + // Draw a circle in 3D world space void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color) { + if (rlCheckBufferLimit(2*36)) rlglDraw(); + rlPushMatrix(); rlTranslatef(center.x, center.y, center.z); rlRotatef(rotationAngle, rotationAxis.x, rotationAxis.y, rotationAxis.z); @@ -135,6 +154,8 @@ void DrawCube(Vector3 position, float width, float height, float length, Color c float y = 0.0f; float z = 0.0f; + if (rlCheckBufferLimit(36)) rlglDraw(); + rlPushMatrix(); // NOTE: Transformation is applied in inverse order (scale -> rotate -> translate) rlTranslatef(position.x, position.y, position.z); @@ -214,6 +235,8 @@ void DrawCubeWires(Vector3 position, float width, float height, float length, Co float y = 0.0f; float z = 0.0f; + if (rlCheckBufferLimit(36)) rlglDraw(); + rlPushMatrix(); rlTranslatef(position.x, position.y, position.z); @@ -275,6 +298,12 @@ void DrawCubeWires(Vector3 position, float width, float height, float length, Co rlPopMatrix(); } +// Draw cube wires (vector version) +void DrawCubeWiresV(Vector3 position, Vector3 size, Color color) +{ + DrawCubeWires(position, size.x, size.y, size.z, color); +} + // Draw cube // NOTE: Cube position is the center position void DrawCubeTexture(Texture2D texture, Vector3 position, float width, float height, float length, Color color) @@ -283,6 +312,8 @@ void DrawCubeTexture(Texture2D texture, Vector3 position, float width, float hei float y = position.y; float z = position.z; + if (rlCheckBufferLimit(36)) rlglDraw(); + rlEnableTexture(texture.id); //rlPushMatrix(); @@ -344,6 +375,9 @@ void DrawSphere(Vector3 centerPos, float radius, Color color) // Draw sphere with extended parameters void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color color) { + int numVertex = (rings + 2)*slices*6; + if (rlCheckBufferLimit(numVertex)) rlglDraw(); + rlPushMatrix(); // NOTE: Transformation is applied in inverse order (scale -> translate) rlTranslatef(centerPos.x, centerPos.y, centerPos.z); @@ -384,6 +418,9 @@ void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color // Draw sphere wires void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color) { + int numVertex = (rings + 2)*slices*6; + if (rlCheckBufferLimit(numVertex)) rlglDraw(); + rlPushMatrix(); // NOTE: Transformation is applied in inverse order (scale -> translate) rlTranslatef(centerPos.x, centerPos.y, centerPos.z); @@ -428,6 +465,9 @@ void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float h { if (sides < 3) sides = 3; + int numVertex = sides*6; + if (rlCheckBufferLimit(numVertex)) rlglDraw(); + rlPushMatrix(); rlTranslatef(position.x, position.y, position.z); @@ -484,6 +524,9 @@ void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, fl { if (sides < 3) sides = 3; + int numVertex = sides*8; + if (rlCheckBufferLimit(numVertex)) rlglDraw(); + rlPushMatrix(); rlTranslatef(position.x, position.y, position.z); @@ -511,19 +554,18 @@ void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, fl // Draw a plane void DrawPlane(Vector3 centerPos, Vector2 size, Color color) { + if (rlCheckBufferLimit(4)) rlglDraw(); + // NOTE: Plane is always created on XZ ground rlPushMatrix(); rlTranslatef(centerPos.x, centerPos.y, centerPos.z); rlScalef(size.x, 1.0f, size.y); - rlBegin(RL_TRIANGLES); + rlBegin(RL_QUADS); rlColor4ub(color.r, color.g, color.b, color.a); rlNormal3f(0.0f, 1.0f, 0.0f); - rlVertex3f(0.5f, 0.0f, -0.5f); rlVertex3f(-0.5f, 0.0f, -0.5f); - rlVertex3f(-0.5f, 0.0f, 0.5f); - rlVertex3f(-0.5f, 0.0f, 0.5f); rlVertex3f(0.5f, 0.0f, 0.5f); rlVertex3f(0.5f, 0.0f, -0.5f); @@ -550,6 +592,8 @@ void DrawGrid(int slices, float spacing) { int halfSlices = slices/2; + if (rlCheckBufferLimit(slices*4)) rlglDraw(); + rlBegin(RL_LINES); for (int i = -halfSlices; i <= halfSlices; i++) { @@ -605,9 +649,43 @@ Model LoadModel(const char *fileName) { Model model = { 0 }; - model.mesh = LoadMesh(fileName); +#if defined(SUPPORT_FILEFORMAT_OBJ) + if (IsFileExtension(fileName, ".obj")) model = LoadOBJ(fileName); +#endif +#if defined(SUPPORT_FILEFORMAT_IQM) + if (IsFileExtension(fileName, ".iqm")) model = LoadIQM(fileName); +#endif +#if defined(SUPPORT_FILEFORMAT_GLTF) + if (IsFileExtension(fileName, ".gltf") || IsFileExtension(fileName, ".glb")) model = LoadGLTF(fileName); +#endif + + // Make sure model transform is set to identity matrix! model.transform = MatrixIdentity(); - model.material = LoadMaterialDefault(); + + if (model.meshCount == 0) + { + TraceLog(LOG_WARNING, "[%s] No meshes can be loaded, default to cube mesh", fileName); + + model.meshCount = 1; + model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); + model.meshes[0] = GenMeshCube(1.0f, 1.0f, 1.0f); + } + else + { + // Upload vertex data to GPU (static mesh) + for (int i = 0; i < model.meshCount; i++) rlLoadMesh(&model.meshes[i], false); + } + + if (model.materialCount == 0) + { + TraceLog(LOG_WARNING, "[%s] No materials can be loaded, default to white material", fileName); + + model.materialCount = 1; + model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); + model.materials[0] = LoadMaterialDefault(); + + if (model.meshMaterial == NULL) model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); + } return model; } @@ -619,10 +697,19 @@ Model LoadModel(const char *fileName) Model LoadModelFromMesh(Mesh mesh) { Model model = { 0 }; - - model.mesh = mesh; + model.transform = MatrixIdentity(); - model.material = LoadMaterialDefault(); + + model.meshCount = 1; + model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); + model.meshes[0] = mesh; + + model.materialCount = 1; + model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); + model.materials[0] = LoadMaterialDefault(); + + model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); + model.meshMaterial[0] = 0; // First material index return model; } @@ -630,82 +717,91 @@ Model LoadModelFromMesh(Mesh mesh) // Unload model from memory (RAM and/or VRAM) void UnloadModel(Model model) { - UnloadMesh(&model.mesh); - UnloadMaterial(model.material); + for (int i = 0; i < model.meshCount; i++) UnloadMesh(model.meshes[i]); - TraceLog(LOG_INFO, "Unloaded model data (mesh and material) from RAM and VRAM"); + // 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); + + RL_FREE(model.meshes); + RL_FREE(model.materials); + RL_FREE(model.meshMaterial); + + // Unload animation data + RL_FREE(model.bones); + RL_FREE(model.bindPose); + + TraceLog(LOG_INFO, "Unloaded model data from RAM and VRAM"); } -// Load mesh from file -// NOTE: Mesh data loaded in CPU and GPU -Mesh LoadMesh(const char *fileName) +// Load meshes from model file +Mesh *LoadMeshes(const char *fileName, int *meshCount) { - Mesh mesh = { 0 }; + Mesh *meshes = NULL; + int count = 0; -#if defined(SUPPORT_FILEFORMAT_OBJ) - if (IsFileExtension(fileName, ".obj")) mesh = LoadOBJ(fileName); -#else - TraceLog(LOG_WARNING, "[%s] Mesh fileformat not supported, it can't be loaded", fileName); -#endif + // TODO: Load meshes from file (OBJ, IQM, GLTF) - if (mesh.vertexCount == 0) - { - TraceLog(LOG_WARNING, "Mesh could not be loaded! Let's load a cube to replace it!"); - mesh = GenMeshCube(1.0f, 1.0f, 1.0f); - } - else rlLoadMesh(&mesh, false); // Upload vertex data to GPU (static mesh) - - return mesh; + *meshCount = count; + return meshes; } // Unload mesh from memory (RAM and/or VRAM) -void UnloadMesh(Mesh *mesh) +void UnloadMesh(Mesh mesh) { rlUnloadMesh(mesh); + RL_FREE(mesh.vboId); } // Export mesh data to file void ExportMesh(Mesh mesh, const char *fileName) { bool success = false; - + if (IsFileExtension(fileName, ".obj")) { FILE *objFile = fopen(fileName, "wt"); - - fprintf(objFile, "# raylib Mesh OBJ exporter v1.0\n\n"); - fprintf(objFile, "# Mesh exported as triangle faces and not optimized.\n"); - fprintf(objFile, "# Vertex Count: %i\n", mesh.vertexCount); - fprintf(objFile, "# Triangle Count: %i\n\n", mesh.triangleCount); - fprintf(objFile, "# LICENSE: zlib/libpng\n"); - fprintf(objFile, "# Copyright (c) 2018 Ramon Santamaria (@raysan5)\n\n"); - + + fprintf(objFile, "# //////////////////////////////////////////////////////////////////////////////////\n"); + fprintf(objFile, "# // //\n"); + fprintf(objFile, "# // rMeshOBJ exporter v1.0 - Mesh exported as triangle faces and not optimized //\n"); + fprintf(objFile, "# // //\n"); + fprintf(objFile, "# // more info and bugs-report: github.com/raysan5/raylib //\n"); + fprintf(objFile, "# // feedback and support: ray[at]raylib.com //\n"); + fprintf(objFile, "# // //\n"); + fprintf(objFile, "# // Copyright (c) 2018 Ramon Santamaria (@raysan5) //\n"); + fprintf(objFile, "# // //\n"); + fprintf(objFile, "# //////////////////////////////////////////////////////////////////////////////////\n\n"); + fprintf(objFile, "# Vertex Count: %i\n", mesh.vertexCount); + fprintf(objFile, "# Triangle Count: %i\n\n", mesh.triangleCount); + fprintf(objFile, "g mesh\n"); - + for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 3) { fprintf(objFile, "v %.2f %.2f %.2f\n", mesh.vertices[v], mesh.vertices[v + 1], mesh.vertices[v + 2]); } - + for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 2) { fprintf(objFile, "vt %.2f %.2f\n", mesh.texcoords[v], mesh.texcoords[v + 1]); } - + for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 3) { fprintf(objFile, "vn %.2f %.2f %.2f\n", mesh.normals[v], mesh.normals[v + 1], mesh.normals[v + 2]); } - + for (int i = 0; i < mesh.triangleCount; i += 3) { fprintf(objFile, "f %i/%i/%i %i/%i/%i %i/%i/%i\n", i, i, i, i + 1, i + 1, i + 1, i + 2, i + 2, i + 2); } - + fprintf(objFile, "\n"); - + fclose(objFile); - + success = true; } else if (IsFileExtension(fileName, ".raw")) { } // TODO: Support additional file formats to export mesh vertex data @@ -714,21 +810,471 @@ void ExportMesh(Mesh mesh, const char *fileName) else TraceLog(LOG_WARNING, "Mesh could not be exported."); } +// Load materials from model file +Material *LoadMaterials(const char *fileName, int *materialCount) +{ + Material *materials = NULL; + unsigned int count = 0; + + // TODO: Support IQM and GLTF for materials parsing + +#if defined(SUPPORT_FILEFORMAT_MTL) + if (IsFileExtension(fileName, ".mtl")) + { + tinyobj_material_t *mats; + + int result = tinyobj_parse_mtl_file(&mats, &count, fileName); + if (result != TINYOBJ_SUCCESS) { + TraceLog(LOG_WARNING, "[%s] Could not parse Materials file", fileName); + } + + // TODO: Process materials to return + + tinyobj_materials_free(mats, count); + } +#else + TraceLog(LOG_WARNING, "[%s] Materials file not supported", fileName); +#endif + + // Set materials shader to default (DIFFUSE, SPECULAR, NORMAL) + for (int i = 0; i < count; i++) materials[i].shader = GetShaderDefault(); + + *materialCount = count; + return materials; +} + +// Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) +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.maps[MAP_DIFFUSE].color = WHITE; // Diffuse color + material.maps[MAP_SPECULAR].color = WHITE; // Specular color + + return material; +} + +// Unload material from memory +void UnloadMaterial(Material material) +{ + // Unload material shader (avoid unloading default shader, managed by raylib) + if (material.shader.id != GetShaderDefault().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); + } + + RL_FREE(material.maps); +} + +// Set texture for a material map type (MAP_DIFFUSE, MAP_SPECULAR...) +// NOTE: Previous texture should be manually unloaded +void SetMaterialTexture(Material *material, int mapType, Texture2D texture) +{ + material->maps[mapType].texture = texture; +} + +// Set the material for a mesh +void SetModelMeshMaterial(Model *model, int meshId, int materialId) +{ + if (meshId >= model->meshCount) TraceLog(LOG_WARNING, "Mesh id greater than mesh count"); + else if (materialId >= model->materialCount) TraceLog(LOG_WARNING,"Material id greater than material count"); + else model->meshMaterial[meshId] = 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 + + 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; + + FILE *iqmFile; + IQMHeader iqm; + + iqmFile = fopen(filename,"rb"); + + if (!iqmFile) + { + TraceLog(LOG_ERROR, "[%s] Unable to open file", filename); + } + + // Read IQM header + fread(&iqm, sizeof(IQMHeader), 1, iqmFile); + + if (strncmp(iqm.magic, IQM_MAGIC, sizeof(IQM_MAGIC))) + { + TraceLog(LOG_ERROR, "Magic Number \"%s\"does not match.", iqm.magic); + fclose(iqmFile); + + return NULL; + } + + if (iqm.version != IQM_VERSION) + { + TraceLog(LOG_ERROR, "IQM version %i is incorrect.", iqm.version); + fclose(iqmFile); + + return NULL; + } + + // Get bones data + IQMPose *poses = RL_MALLOC(iqm.num_poses*sizeof(IQMPose)); + fseek(iqmFile, iqm.ofs_poses, SEEK_SET); + fread(poses, iqm.num_poses*sizeof(IQMPose), 1, iqmFile); + + // Get animations data + *animCount = iqm.num_anims; + IQMAnim *anim = RL_MALLOC(iqm.num_anims*sizeof(IQMAnim)); + fseek(iqmFile, iqm.ofs_anims, SEEK_SET); + fread(anim, iqm.num_anims*sizeof(IQMAnim), 1, iqmFile); + ModelAnimation *animations = RL_MALLOC(iqm.num_anims*sizeof(ModelAnimation)); + + // frameposes + unsigned short *framedata = RL_MALLOC(iqm.num_frames*iqm.num_framechannels*sizeof(unsigned short)); + fseek(iqmFile, iqm.ofs_frames, SEEK_SET); + fread(framedata, iqm.num_frames*iqm.num_framechannels*sizeof(unsigned short), 1, iqmFile); + + for (int a = 0; a < iqm.num_anims; a++) + { + animations[a].frameCount = anim[a].num_frames; + animations[a].boneCount = iqm.num_poses; + animations[a].bones = RL_MALLOC(iqm.num_poses*sizeof(BoneInfo)); + animations[a].framePoses = RL_MALLOC(anim[a].num_frames*sizeof(Transform *)); + //animations[a].framerate = anim.framerate; // TODO: Use framerate? + + for (int j = 0; j < iqm.num_poses; j++) + { + strcpy(animations[a].bones[j].name, "ANIMJOINTNAME"); + animations[a].bones[j].parent = poses[j].parent; + } + + for (int j = 0; j < anim[a].num_frames; j++) animations[a].framePoses[j] = RL_MALLOC(iqm.num_poses*sizeof(Transform)); + + int dcounter = anim[a].first_frame*iqm.num_framechannels; + + for (int frame = 0; frame < anim[a].num_frames; frame++) + { + for (int i = 0; i < iqm.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 (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 = Vector3MultiplyV(animations[a].framePoses[frame][i].scale, animations[a].framePoses[frame][animations[a].bones[i].parent].scale); + } + } + } + } + + RL_FREE(framedata); + RL_FREE(poses); + RL_FREE(anim); + + fclose(iqmFile); + + return animations; +} + +// Update model animated vertex data (positions and normals) for a given frame +// NOTE: Updated data is uploaded to GPU +void UpdateModelAnimation(Model model, ModelAnimation anim, int frame) +{ + if ((anim.frameCount > 0) && (anim.bones != NULL) && (anim.framePoses != NULL)) + { + if (frame >= anim.frameCount) frame = frame%anim.frameCount; + + for (int m = 0; m < model.meshCount; m++) + { + Vector3 animVertex = { 0 }; + Vector3 animNormal = { 0 }; + + Vector3 inTranslation = { 0 }; + Quaternion inRotation = { 0 }; + Vector3 inScale = { 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[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; + + // 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 = Vector3MultiplyV(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; + + // 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; + 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].animVertices, model.meshes[m].vertexCount*3*sizeof(float)); // Update vertex normals + } + } +} + +// Unload animation data +void UnloadModelAnimation(ModelAnimation anim) +{ + for (int i = 0; i < anim.frameCount; i++) RL_FREE(anim.framePoses[i]); + + RL_FREE(anim.bones); + RL_FREE(anim.framePoses); +} + +// Check model animation skeleton match +// NOTE: Only number of bones and parent connections are checked +bool IsModelAnimationValid(Model model, ModelAnimation anim) +{ + int result = true; + + if (model.boneCount != anim.boneCount) result = false; + else + { + for (int i = 0; i < model.boneCount; i++) + { + if (model.bones[i].parent != anim.bones[i].parent) { result = false; break; } + } + } + + return result; +} + #if defined(SUPPORT_MESH_GENERATION) +// Generate polygonal mesh +Mesh GenMeshPoly(int sides, float radius) +{ + Mesh mesh = { 0 }; + mesh.vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VBO, sizeof(unsigned int)); + int vertexCount = sides*3; + + // Vertices definition + Vector3 *vertices = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3)); + for (int i = 0, v = 0; i < 360; i += 360/sides, v += 3) + { + vertices[v] = (Vector3){ 0.0f, 0.0f, 0.0f }; + vertices[v + 1] = (Vector3){ sinf(DEG2RAD*i)*radius, 0.0f, cosf(DEG2RAD*i)*radius }; + vertices[v + 2] = (Vector3){ sinf(DEG2RAD*(i + 360/sides))*radius, 0.0f, cosf(DEG2RAD*(i + 360/sides))*radius }; + } + + // Normals definition + Vector3 *normals = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3)); + for (int n = 0; n < vertexCount; n++) normals[n] = (Vector3){ 0.0f, 1.0f, 0.0f }; // Vector3.up; + + // TexCoords definition + Vector2 *texcoords = (Vector2 *)RL_MALLOC(vertexCount*sizeof(Vector2)); + for (int n = 0; n < vertexCount; n++) texcoords[n] = (Vector2){ 0.0f, 0.0f }; + + mesh.vertexCount = vertexCount; + mesh.triangleCount = sides; + mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + + // Mesh vertices position array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.vertices[3*i] = vertices[i].x; + mesh.vertices[3*i + 1] = vertices[i].y; + mesh.vertices[3*i + 2] = vertices[i].z; + } + + // Mesh texcoords array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.texcoords[2*i] = texcoords[i].x; + mesh.texcoords[2*i + 1] = texcoords[i].y; + } + + // Mesh normals array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.normals[3*i] = normals[i].x; + mesh.normals[3*i + 1] = normals[i].y; + mesh.normals[3*i + 2] = normals[i].z; + } + + RL_FREE(vertices); + RL_FREE(normals); + RL_FREE(texcoords); + + // Upload vertex data to GPU (static mesh) + rlLoadMesh(&mesh, false); + + return mesh; +} + // Generate plane mesh (with subdivisions) Mesh GenMeshPlane(float width, float length, int resX, int resZ) { Mesh mesh = { 0 }; + mesh.vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VBO, sizeof(unsigned int)); #define CUSTOM_MESH_GEN_PLANE #if defined(CUSTOM_MESH_GEN_PLANE) resX++; resZ++; - + // Vertices definition int vertexCount = resX*resZ; // vertices get reused for the faces - Vector3 *vertices = (Vector3 *)malloc(vertexCount*sizeof(Vector3)); + Vector3 *vertices = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3)); for (int z = 0; z < resZ; z++) { // [-length/2, length/2] @@ -742,11 +1288,11 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ) } // Normals definition - Vector3 *normals = (Vector3 *)malloc(vertexCount*sizeof(Vector3)); + Vector3 *normals = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3)); for (int n = 0; n < vertexCount; n++) normals[n] = (Vector3){ 0.0f, 1.0f, 0.0f }; // Vector3.up; - // TexCoords definition - Vector2 *texcoords = (Vector2 *)malloc(vertexCount*sizeof(Vector2)); + // TexCoords definition + Vector2 *texcoords = (Vector2 *)RL_MALLOC(vertexCount*sizeof(Vector2)); for (int v = 0; v < resZ; v++) { for (int u = 0; u < resX; u++) @@ -757,7 +1303,7 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ) // Triangles definition (indices) int numFaces = (resX - 1)*(resZ - 1); - int *triangles = (int *)malloc(numFaces*6*sizeof(int)); + int *triangles = (int *)RL_MALLOC(numFaces*6*sizeof(int)); int t = 0; for (int face = 0; face < numFaces; face++) { @@ -768,18 +1314,18 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ) triangles[t++] = i + 1; triangles[t++] = i; - triangles[t++] = i + resX; + triangles[t++] = i + resX; triangles[t++] = i + resX + 1; triangles[t++] = i + 1; } mesh.vertexCount = vertexCount; mesh.triangleCount = numFaces*2; - mesh.vertices = (float *)malloc(mesh.vertexCount*3*sizeof(float)); - mesh.texcoords = (float *)malloc(mesh.vertexCount*2*sizeof(float)); - mesh.normals = (float *)malloc(mesh.vertexCount*3*sizeof(float)); - mesh.indices = (unsigned short *)malloc(mesh.triangleCount*3*sizeof(unsigned short)); - + mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.indices = (unsigned short *)RL_MALLOC(mesh.triangleCount*3*sizeof(unsigned short)); + // Mesh vertices position array for (int i = 0; i < mesh.vertexCount; i++) { @@ -787,14 +1333,14 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ) mesh.vertices[3*i + 1] = vertices[i].y; mesh.vertices[3*i + 2] = vertices[i].z; } - + // Mesh texcoords array for (int i = 0; i < mesh.vertexCount; i++) { mesh.texcoords[2*i] = texcoords[i].x; mesh.texcoords[2*i + 1] = texcoords[i].y; } - + // Mesh normals array for (int i = 0; i < mesh.vertexCount; i++) { @@ -802,25 +1348,26 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ) mesh.normals[3*i + 1] = normals[i].y; mesh.normals[3*i + 2] = normals[i].z; } - + // Mesh indices array initialization for (int i = 0; i < mesh.triangleCount*3; i++) mesh.indices[i] = triangles[i]; - - free(vertices); - free(normals); - free(texcoords); - free(triangles); - + + RL_FREE(vertices); + RL_FREE(normals); + RL_FREE(texcoords); + RL_FREE(triangles); + #else // Use par_shapes library to generate plane mesh par_shapes_mesh *plane = par_shapes_create_plane(resX, resZ); // No normals/texcoords generated!!! par_shapes_scale(plane, width, length, 1.0f); par_shapes_rotate(plane, -PI/2.0f, (float[]){ 1, 0, 0 }); par_shapes_translate(plane, -width/2, 0.0f, length/2); - - mesh.vertices = (float *)malloc(plane->ntriangles*3*3*sizeof(float)); - mesh.texcoords = (float *)malloc(plane->ntriangles*3*2*sizeof(float)); - mesh.normals = (float *)malloc(plane->ntriangles*3*3*sizeof(float)); + + 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(MAX_MESH_VBO, sizeof(unsigned int)); mesh.vertexCount = plane->ntriangles*3; mesh.triangleCount = plane->ntriangles; @@ -830,11 +1377,11 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ) mesh.vertices[k*3] = plane->points[plane->triangles[k]*3]; mesh.vertices[k*3 + 1] = plane->points[plane->triangles[k]*3 + 1]; mesh.vertices[k*3 + 2] = plane->points[plane->triangles[k]*3 + 2]; - + mesh.normals[k*3] = plane->normals[plane->triangles[k]*3]; mesh.normals[k*3 + 1] = plane->normals[plane->triangles[k]*3 + 1]; mesh.normals[k*3 + 2] = plane->normals[plane->triangles[k]*3 + 2]; - + mesh.texcoords[k*2] = plane->tcoords[plane->triangles[k]*2]; mesh.texcoords[k*2 + 1] = plane->tcoords[plane->triangles[k]*2 + 1]; } @@ -843,7 +1390,7 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ) #endif // Upload vertex data to GPU (static mesh) - rlLoadMesh(&mesh, false); + rlLoadMesh(&mesh, false); return mesh; } @@ -852,6 +1399,7 @@ 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(MAX_MESH_VBO, sizeof(unsigned int)); #define CUSTOM_MESH_GEN_CUBE #if defined(CUSTOM_MESH_GEN_CUBE) @@ -881,7 +1429,7 @@ Mesh GenMeshCube(float width, float height, float length) -width/2, height/2, length/2, -width/2, height/2, -length/2 }; - + float texcoords[] = { 0.0f, 0.0f, 1.0f, 0.0f, @@ -908,7 +1456,7 @@ Mesh GenMeshCube(float width, float height, float length) 1.0f, 1.0f, 0.0f, 1.0f }; - + float normals[] = { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, @@ -936,17 +1484,17 @@ Mesh GenMeshCube(float width, float height, float length) -1.0f, 0.0f, 0.0f }; - mesh.vertices = (float *)malloc(24*3*sizeof(float)); + mesh.vertices = (float *)RL_MALLOC(24*3*sizeof(float)); memcpy(mesh.vertices, vertices, 24*3*sizeof(float)); - - mesh.texcoords = (float *)malloc(24*2*sizeof(float)); + + mesh.texcoords = (float *)RL_MALLOC(24*2*sizeof(float)); memcpy(mesh.texcoords, texcoords, 24*2*sizeof(float)); - - mesh.normals = (float *)malloc(24*3*sizeof(float)); + + mesh.normals = (float *)RL_MALLOC(24*3*sizeof(float)); memcpy(mesh.normals, normals, 24*3*sizeof(float)); - - mesh.indices = (unsigned short *)malloc(36*sizeof(unsigned short)); - + + mesh.indices = (unsigned short *)RL_MALLOC(36*sizeof(unsigned short)); + int k = 0; // Indices can be initialized right now @@ -961,10 +1509,10 @@ Mesh GenMeshCube(float width, float height, float length) k++; } - + mesh.vertexCount = 24; mesh.triangleCount = 12; - + #else // Use par_shapes library to generate cube mesh /* // Platonic solids: @@ -978,14 +1526,14 @@ par_shapes_mesh* par_shapes_create_icosahedron(); // 20 sides polyhedron // NOTE: No normals/texcoords generated by default par_shapes_mesh *cube = par_shapes_create_cube(); cube->tcoords = PAR_MALLOC(float, 2*cube->npoints); - for (int i = 0; i < 2*cube->npoints; i++) cube->tcoords[i] = 0.0f; + for (int i = 0; i < 2*cube->npoints; i++) cube->tcoords[i] = 0.0f; par_shapes_scale(cube, width, height, length); par_shapes_translate(cube, -width/2, 0.0f, -length/2); par_shapes_compute_normals(cube); - - mesh.vertices = (float *)malloc(cube->ntriangles*3*3*sizeof(float)); - mesh.texcoords = (float *)malloc(cube->ntriangles*3*2*sizeof(float)); - mesh.normals = (float *)malloc(cube->ntriangles*3*3*sizeof(float)); + + mesh.vertices = (float *)RL_MALLOC(cube->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(cube->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(cube->ntriangles*3*3*sizeof(float)); mesh.vertexCount = cube->ntriangles*3; mesh.triangleCount = cube->ntriangles; @@ -995,11 +1543,11 @@ par_shapes_mesh* par_shapes_create_icosahedron(); // 20 sides polyhedron mesh.vertices[k*3] = cube->points[cube->triangles[k]*3]; mesh.vertices[k*3 + 1] = cube->points[cube->triangles[k]*3 + 1]; mesh.vertices[k*3 + 2] = cube->points[cube->triangles[k]*3 + 2]; - + mesh.normals[k*3] = cube->normals[cube->triangles[k]*3]; mesh.normals[k*3 + 1] = cube->normals[cube->triangles[k]*3 + 1]; mesh.normals[k*3 + 2] = cube->normals[cube->triangles[k]*3 + 2]; - + mesh.texcoords[k*2] = cube->tcoords[cube->triangles[k]*2]; mesh.texcoords[k*2 + 1] = cube->tcoords[cube->triangles[k]*2 + 1]; } @@ -1008,7 +1556,7 @@ par_shapes_mesh* par_shapes_create_icosahedron(); // 20 sides polyhedron #endif // Upload vertex data to GPU (static mesh) - rlLoadMesh(&mesh, false); + rlLoadMesh(&mesh, false); return mesh; } @@ -1017,14 +1565,15 @@ par_shapes_mesh* par_shapes_create_icosahedron(); // 20 sides polyhedron RLAPI Mesh GenMeshSphere(float radius, int rings, int slices) { Mesh mesh = { 0 }; + mesh.vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VBO, 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 *)malloc(sphere->ntriangles*3*3*sizeof(float)); - mesh.texcoords = (float *)malloc(sphere->ntriangles*3*2*sizeof(float)); - mesh.normals = (float *)malloc(sphere->ntriangles*3*3*sizeof(float)); + // 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; @@ -1034,19 +1583,19 @@ RLAPI Mesh GenMeshSphere(float radius, int rings, int slices) 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) - rlLoadMesh(&mesh, false); + rlLoadMesh(&mesh, false); return mesh; } @@ -1055,14 +1604,15 @@ RLAPI Mesh GenMeshSphere(float radius, int rings, int slices) RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices) { Mesh mesh = { 0 }; + mesh.vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VBO, 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 *)malloc(sphere->ntriangles*3*3*sizeof(float)); - mesh.texcoords = (float *)malloc(sphere->ntriangles*3*2*sizeof(float)); - mesh.normals = (float *)malloc(sphere->ntriangles*3*3*sizeof(float)); + // 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; @@ -1072,19 +1622,19 @@ RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices) 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) - rlLoadMesh(&mesh, false); + rlLoadMesh(&mesh, false); return mesh; } @@ -1093,10 +1643,11 @@ 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(MAX_MESH_VBO, 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. + // 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); @@ -1108,19 +1659,19 @@ Mesh GenMeshCylinder(float radius, float height, int slices) 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 *)malloc(cylinder->ntriangles*3*3*sizeof(float)); - mesh.texcoords = (float *)malloc(cylinder->ntriangles*3*2*sizeof(float)); - mesh.normals = (float *)malloc(cylinder->ntriangles*3*3*sizeof(float)); + + 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; @@ -1130,19 +1681,19 @@ Mesh GenMeshCylinder(float radius, float height, int slices) 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) - rlLoadMesh(&mesh, false); + rlLoadMesh(&mesh, false); return mesh; } @@ -1151,18 +1702,19 @@ 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(MAX_MESH_VBO, 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 *)malloc(torus->ntriangles*3*3*sizeof(float)); - mesh.texcoords = (float *)malloc(torus->ntriangles*3*2*sizeof(float)); - mesh.normals = (float *)malloc(torus->ntriangles*3*3*sizeof(float)); + 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; @@ -1172,19 +1724,19 @@ Mesh GenMeshTorus(float radius, float size, int radSeg, int sides) 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) - rlLoadMesh(&mesh, false); + rlLoadMesh(&mesh, false); return mesh; } @@ -1193,16 +1745,17 @@ 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(MAX_MESH_VBO, 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 *)malloc(knot->ntriangles*3*3*sizeof(float)); - mesh.texcoords = (float *)malloc(knot->ntriangles*3*2*sizeof(float)); - mesh.normals = (float *)malloc(knot->ntriangles*3*3*sizeof(float)); + 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; @@ -1212,19 +1765,19 @@ Mesh GenMeshKnot(float radius, float size, int radSeg, int sides) 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) - rlLoadMesh(&mesh, false); + rlLoadMesh(&mesh, false); return mesh; } @@ -1236,6 +1789,7 @@ 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(MAX_MESH_VBO, sizeof(unsigned int)); int mapX = heightmap.width; int mapZ = heightmap.height; @@ -1247,9 +1801,9 @@ Mesh GenMeshHeightmap(Image heightmap, Vector3 size) mesh.vertexCount = mesh.triangleCount*3; - mesh.vertices = (float *)malloc(mesh.vertexCount*3*sizeof(float)); - mesh.normals = (float *)malloc(mesh.vertexCount*3*sizeof(float)); - mesh.texcoords = (float *)malloc(mesh.vertexCount*2*sizeof(float)); + mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float)); mesh.colors = NULL; int vCounter = 0; // Used to count vertices float by float @@ -1331,8 +1885,8 @@ Mesh GenMeshHeightmap(Image heightmap, Vector3 size) } } - free(pixels); - + RL_FREE(pixels); + // Upload vertex data to GPU (static mesh) rlLoadMesh(&mesh, false); @@ -1344,13 +1898,14 @@ Mesh GenMeshHeightmap(Image heightmap, Vector3 size) Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) { Mesh mesh = { 0 }; + mesh.vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VBO, sizeof(unsigned int)); Color *cubicmapPixels = GetImageData(cubicmap); int mapWidth = cubicmap.width; int mapHeight = cubicmap.height; - // NOTE: Max possible number of triangles numCubes * (12 triangles by cube) + // NOTE: Max possible number of triangles numCubes*(12 triangles by cube) int maxTriangles = cubicmap.width*cubicmap.height*12; int vCounter = 0; // Used to count vertices @@ -1361,9 +1916,9 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) float h = cubeSize.z; float h2 = cubeSize.y; - Vector3 *mapVertices = (Vector3 *)malloc(maxTriangles*3*sizeof(Vector3)); - Vector2 *mapTexcoords = (Vector2 *)malloc(maxTriangles*3*sizeof(Vector2)); - Vector3 *mapNormals = (Vector3 *)malloc(maxTriangles*3*sizeof(Vector3)); + Vector3 *mapVertices = (Vector3 *)RL_MALLOC(maxTriangles*3*sizeof(Vector3)); + Vector2 *mapTexcoords = (Vector2 *)RL_MALLOC(maxTriangles*3*sizeof(Vector2)); + Vector3 *mapNormals = (Vector3 *)RL_MALLOC(maxTriangles*3*sizeof(Vector3)); // Define the 6 normals of the cube, we will combine them accordingly later... Vector3 n1 = { 1.0f, 0.0f, 0.0f }; @@ -1650,9 +2205,9 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) mesh.vertexCount = vCounter; mesh.triangleCount = vCounter/3; - mesh.vertices = (float *)malloc(mesh.vertexCount*3*sizeof(float)); - mesh.normals = (float *)malloc(mesh.vertexCount*3*sizeof(float)); - mesh.texcoords = (float *)malloc(mesh.vertexCount*2*sizeof(float)); + mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float)); mesh.colors = NULL; int fCounter = 0; @@ -1687,62 +2242,140 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) fCounter += 2; } - free(mapVertices); - free(mapNormals); - free(mapTexcoords); + RL_FREE(mapVertices); + RL_FREE(mapNormals); + RL_FREE(mapTexcoords); + + RL_FREE(cubicmapPixels); // Free image pixel data - free(cubicmapPixels); // Free image pixel data - // Upload vertex data to GPU (static mesh) - rlLoadMesh(&mesh, false); + rlLoadMesh(&mesh, false); return mesh; } #endif // SUPPORT_MESH_GENERATION -// Load material data (from file) -Material LoadMaterial(const char *fileName) +// Compute mesh bounding box limits +// NOTE: minVertex and maxVertex should be transformed by model transform matrix +BoundingBox MeshBoundingBox(Mesh mesh) { - Material material = { 0 }; + // Get min and max vertex to construct bounds (AABB) + Vector3 minVertex = { 0 }; + Vector3 maxVertex = { 0 }; -#if defined(SUPPORT_FILEFORMAT_MTL) - if (IsFileExtension(fileName, ".mtl")) material = LoadMTL(fileName); -#else - TraceLog(LOG_WARNING, "[%s] Material fileformat not supported, it can't be loaded", fileName); -#endif - - // Our material uses the default shader (DIFFUSE, SPECULAR, NORMAL) - material.shader = GetShaderDefault(); - - return material; -} - -// Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) -Material LoadMaterialDefault(void) -{ - Material material = { 0 }; - - 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.maps[MAP_DIFFUSE].color = WHITE; // Diffuse color - material.maps[MAP_SPECULAR].color = WHITE; // Specular color - - return material; -} - -// Unload material from memory -void UnloadMaterial(Material material) -{ - // Unload material shader (avoid unloading default shader, managed by raylib) - if (material.shader.id != GetShaderDefault().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 (mesh.vertices != NULL) { - if (material.maps[i].texture.id != GetTextureDefault().id) rlDeleteTextures(material.maps[i].texture.id); + minVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] }; + maxVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] }; + + for (int i = 1; i < mesh.vertexCount; i++) + { + minVertex = Vector3Min(minVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] }); + maxVertex = Vector3Max(maxVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] }); + } + } + + // Create the bounding box + BoundingBox box = { 0 }; + box.min = minVertex; + box.max = maxVertex; + + return box; +} + +// Compute mesh tangents +// NOTE: To calculate mesh tangents and binormals we need mesh vertex positions and texture coordinates +// Implementation base don: https://answers.unity.com/questions/7789/calculating-tangents-vector4.html +void MeshTangents(Mesh *mesh) +{ + if (mesh->tangents == NULL) mesh->tangents = (float *)RL_MALLOC(mesh->vertexCount*4*sizeof(float)); + else TraceLog(LOG_WARNING, "Mesh tangents already exist"); + + Vector3 *tan1 = (Vector3 *)RL_MALLOC(mesh->vertexCount*sizeof(Vector3)); + Vector3 *tan2 = (Vector3 *)RL_MALLOC(mesh->vertexCount*sizeof(Vector3)); + + for (int i = 0; i < mesh->vertexCount; i += 3) + { + // Get triangle vertices + Vector3 v1 = { mesh->vertices[(i + 0)*3 + 0], mesh->vertices[(i + 0)*3 + 1], mesh->vertices[(i + 0)*3 + 2] }; + Vector3 v2 = { mesh->vertices[(i + 1)*3 + 0], mesh->vertices[(i + 1)*3 + 1], mesh->vertices[(i + 1)*3 + 2] }; + Vector3 v3 = { mesh->vertices[(i + 2)*3 + 0], mesh->vertices[(i + 2)*3 + 1], mesh->vertices[(i + 2)*3 + 2] }; + + // Get triangle texcoords + Vector2 uv1 = { mesh->texcoords[(i + 0)*2 + 0], mesh->texcoords[(i + 0)*2 + 1] }; + Vector2 uv2 = { mesh->texcoords[(i + 1)*2 + 0], mesh->texcoords[(i + 1)*2 + 1] }; + Vector2 uv3 = { mesh->texcoords[(i + 2)*2 + 0], mesh->texcoords[(i + 2)*2 + 1] }; + + float x1 = v2.x - v1.x; + float y1 = v2.y - v1.y; + float z1 = v2.z - v1.z; + float x2 = v3.x - v1.x; + float y2 = v3.y - v1.y; + float z2 = v3.z - v1.z; + + float s1 = uv2.x - uv1.x; + float t1 = uv2.y - uv1.y; + float s2 = uv3.x - uv1.x; + float t2 = uv3.y - uv1.y; + + float div = s1*t2 - s2*t1; + float r = (div == 0.0f)? 0.0f : 1.0f/div; + + Vector3 sdir = { (t2*x1 - t1*x2)*r, (t2*y1 - t1*y2)*r, (t2*z1 - t1*z2)*r }; + Vector3 tdir = { (s1*x2 - s2*x1)*r, (s1*y2 - s2*y1)*r, (s1*z2 - s2*z1)*r }; + + tan1[i + 0] = sdir; + tan1[i + 1] = sdir; + tan1[i + 2] = sdir; + + tan2[i + 0] = tdir; + tan2[i + 1] = tdir; + tan2[i + 2] = tdir; + } + + // Compute tangents considering normals + for (int i = 0; i < mesh->vertexCount; ++i) + { + Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] }; + Vector3 tangent = tan1[i]; + + // TODO: Review, not sure if tangent computation is right, just used reference proposed maths... + #if defined(COMPUTE_TANGENTS_METHOD_01) + Vector3 tmp = Vector3Subtract(tangent, Vector3Multiply(normal, Vector3DotProduct(normal, tangent))); + tmp = Vector3Normalize(tmp); + mesh->tangents[i*4 + 0] = tmp.x; + mesh->tangents[i*4 + 1] = tmp.y; + mesh->tangents[i*4 + 2] = tmp.z; + mesh->tangents[i*4 + 3] = 1.0f; + #else + Vector3OrthoNormalize(&normal, &tangent); + mesh->tangents[i*4 + 0] = tangent.x; + mesh->tangents[i*4 + 1] = tangent.y; + mesh->tangents[i*4 + 2] = tangent.z; + mesh->tangents[i*4 + 3] = (Vector3DotProduct(Vector3CrossProduct(normal, tangent), tan2[i]) < 0.0f)? -1.0f : 1.0f; + #endif + } + + RL_FREE(tan1); + 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); + + TraceLog(LOG_INFO, "Tangents computed for mesh"); +} + +// Compute mesh binormals (aka bitangent) +void MeshBinormals(Mesh *mesh) +{ + for (int i = 0; i < mesh->vertexCount; i++) + { + Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] }; + Vector3 tangent = { mesh->tangents[i*4 + 0], mesh->tangents[i*4 + 1], mesh->tangents[i*4 + 2] }; + float tangentW = mesh->tangents[i*4 + 3]; + + // TODO: Register computed binormal in mesh->binormal? + // Vector3 binormal = Vector3Multiply(Vector3CrossProduct(normal, tangent), tangentW); } } @@ -1750,7 +2383,7 @@ void UnloadMaterial(Material material) void DrawModel(Model model, Vector3 position, float scale, Color tint) { Vector3 vScale = { scale, scale, scale }; - Vector3 rotationAxis = { 0.0f, 0.0f, 0.0f }; + Vector3 rotationAxis = { 0.0f, 1.0f, 0.0f }; DrawModelEx(model, position, rotationAxis, 0.0f, vScale, tint); } @@ -1763,16 +2396,27 @@ void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rota Matrix matScale = MatrixScale(scale.x, scale.y, scale.z); Matrix matRotation = MatrixRotate(rotationAxis, rotationAngle*DEG2RAD); Matrix matTranslation = MatrixTranslate(position.x, position.y, position.z); - + Matrix matTransform = MatrixMultiply(MatrixMultiply(matScale, matRotation), matTranslation); // Combine model transformation matrix (model.transform) with matrix generated by function parameters (matTransform) - //Matrix matModel = MatrixMultiply(model.transform, matTransform); // Transform to world-space coordinates - model.transform = MatrixMultiply(model.transform, matTransform); - model.material.maps[MAP_DIFFUSE].color = tint; // TODO: Multiply tint color by diffuse color? - rlDrawMesh(model.mesh, model.material, model.transform); + 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 colorTint = WHITE; + colorTint.r = (((float)color.r/255.0)*((float)tint.r/255.0))*255; + colorTint.g = (((float)color.g/255.0)*((float)tint.g/255.0))*255; + colorTint.b = (((float)color.b/255.0)*((float)tint.b/255.0))*255; + colorTint.a = (((float)color.a/255.0)*((float)tint.a/255.0))*255; + + 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; + } } // Draw a model wires (with texture if set) @@ -1834,6 +2478,8 @@ void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle sourceRec, Vec Vector3 c = Vector3Add(center, p2); Vector3 d = Vector3Subtract(center, p1); + if (rlCheckBufferLimit(4)) rlglDraw(); + rlEnableTexture(texture.id); rlBegin(RL_QUADS); @@ -1878,13 +2524,20 @@ bool CheckCollisionSpheres(Vector3 centerA, float radiusA, Vector3 centerB, floa { 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; // Y distance between centers + float dz = centerA.z - centerB.z; // Z distance between centers float distance = sqrtf(dx*dx + dy*dy + dz*dz); // Distance between centers if (distance <= (radiusA + radiusB)) collision = true; + */ + + // Check for distances squared to avoid sqrtf() + if (Vector3DotProduct(Vector3Subtract(centerB, centerA), Vector3Subtract(centerB, centerA)) <= (radiusA + radiusB)*(radiusA + radiusB)) collision = true; return collision; } @@ -1906,35 +2559,35 @@ bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2) } // Detect collision between box and sphere -bool CheckCollisionBoxSphere(BoundingBox box, Vector3 centerSphere, float radiusSphere) +bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius) { bool collision = false; float dmin = 0; - if (centerSphere.x < box.min.x) dmin += powf(centerSphere.x - box.min.x, 2); - else if (centerSphere.x > box.max.x) dmin += powf(centerSphere.x - box.max.x, 2); + if (center.x < box.min.x) dmin += powf(center.x - box.min.x, 2); + else if (center.x > box.max.x) dmin += powf(center.x - box.max.x, 2); - if (centerSphere.y < box.min.y) dmin += powf(centerSphere.y - box.min.y, 2); - else if (centerSphere.y > box.max.y) dmin += powf(centerSphere.y - box.max.y, 2); + if (center.y < box.min.y) dmin += powf(center.y - box.min.y, 2); + else if (center.y > box.max.y) dmin += powf(center.y - box.max.y, 2); - if (centerSphere.z < box.min.z) dmin += powf(centerSphere.z - box.min.z, 2); - else if (centerSphere.z > box.max.z) dmin += powf(centerSphere.z - box.max.z, 2); + if (center.z < box.min.z) dmin += powf(center.z - box.min.z, 2); + else if (center.z > box.max.z) dmin += powf(center.z - box.max.z, 2); - if (dmin <= (radiusSphere*radiusSphere)) collision = true; + if (dmin <= (radius*radius)) collision = true; return collision; } // Detect collision between ray and sphere -bool CheckCollisionRaySphere(Ray ray, Vector3 spherePosition, float sphereRadius) +bool CheckCollisionRaySphere(Ray ray, Vector3 center, float radius) { bool collision = false; - Vector3 raySpherePos = Vector3Subtract(spherePosition, ray.position); + Vector3 raySpherePos = Vector3Subtract(center, ray.position); float distance = Vector3Length(raySpherePos); float vector = Vector3DotProduct(raySpherePos, ray.direction); - float d = sphereRadius*sphereRadius - (distance*distance - vector*vector); + float d = radius*radius - (distance*distance - vector*vector); if (d >= 0.0f) collision = true; @@ -1942,23 +2595,23 @@ bool CheckCollisionRaySphere(Ray ray, Vector3 spherePosition, float sphereRadius } // Detect collision between ray and sphere with extended parameters and collision point detection -bool CheckCollisionRaySphereEx(Ray ray, Vector3 spherePosition, float sphereRadius, Vector3 *collisionPoint) +bool CheckCollisionRaySphereEx(Ray ray, Vector3 center, float radius, Vector3 *collisionPoint) { bool collision = false; - Vector3 raySpherePos = Vector3Subtract(spherePosition, ray.position); + Vector3 raySpherePos = Vector3Subtract(center, ray.position); float distance = Vector3Length(raySpherePos); float vector = Vector3DotProduct(raySpherePos, ray.direction); - float d = sphereRadius*sphereRadius - (distance*distance - vector*vector); + float d = radius*radius - (distance*distance - vector*vector); if (d >= 0.0f) collision = true; // Check if ray origin is inside the sphere to calculate the correct collision point float collisionDistance = 0; - if (distance < sphereRadius) collisionDistance = vector + sqrtf(d); + if (distance < radius) collisionDistance = vector + sqrtf(d); else collisionDistance = vector - sqrtf(d); - + // Calculate collision point Vector3 cPoint = Vector3Add(ray.position, Vector3Scale(ray.direction, collisionDistance)); @@ -1990,45 +2643,49 @@ bool CheckCollisionRayBox(Ray ray, BoundingBox box) } // Get collision info between ray and model -RayHitInfo GetCollisionRayModel(Ray ray, Model *model) +RayHitInfo GetCollisionRayModel(Ray ray, Model model) { RayHitInfo result = { 0 }; - // If mesh doesn't have vertex data on CPU, can't test it. - if (!model->mesh.vertices) return result; - - // model->mesh.triangleCount may not be set, vertexCount is more reliable - int triangleCount = model->mesh.vertexCount/3; - - // Test against all triangles in mesh - for (int i = 0; i < triangleCount; i++) + for (int m = 0; m < model.meshCount; m++) { - Vector3 a, b, c; - Vector3 *vertdata = (Vector3 *)model->mesh.vertices; - - if (model->mesh.indices) + // Check if meshhas vertex data on CPU for testing + if (model.meshes[m].vertices != NULL) { - a = vertdata[model->mesh.indices[i*3 + 0]]; - b = vertdata[model->mesh.indices[i*3 + 1]]; - c = vertdata[model->mesh.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); + // model->mesh.triangleCount may not be set, vertexCount is more reliable + int triangleCount = model.meshes[m].vertexCount/3; - RayHitInfo triHitInfo = GetCollisionRayTriangle(ray, a, b, c); + // 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 (triHitInfo.hit) - { - // Save the closest hit triangle - if ((!result.hit) || (result.distance > triHitInfo.distance)) result = triHitInfo; + 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; + } + } } } @@ -2117,599 +2774,903 @@ RayHitInfo GetCollisionRayGround(Ray ray, float groundHeight) return result; } -// Compute mesh bounding box limits -// NOTE: minVertex and maxVertex should be transformed by model transform matrix -BoundingBox MeshBoundingBox(Mesh mesh) -{ - // Get min and max vertex to construct bounds (AABB) - Vector3 minVertex = { 0 }; - Vector3 maxVertex = { 0 }; - - if (mesh.vertices != NULL) - { - minVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] }; - maxVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] }; - - for (int i = 1; i < mesh.vertexCount; i++) - { - minVertex = Vector3Min(minVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] }); - maxVertex = Vector3Max(maxVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] }); - } - } - - // Create the bounding box - BoundingBox box; - box.min = minVertex; - box.max = maxVertex; - - return box; -} - -// Compute mesh tangents -// NOTE: To calculate mesh tangents and binormals we need mesh vertex positions and texture coordinates -// Implementation base don: https://answers.unity.com/questions/7789/calculating-tangents-vector4.html -void MeshTangents(Mesh *mesh) -{ - if (mesh->tangents == NULL) mesh->tangents = (float *)malloc(mesh->vertexCount*4*sizeof(float)); - else TraceLog(LOG_WARNING, "Mesh tangents already exist"); - - Vector3 *tan1 = (Vector3 *)malloc(mesh->vertexCount*sizeof(Vector3)); - Vector3 *tan2 = (Vector3 *)malloc(mesh->vertexCount*sizeof(Vector3)); - - for (int i = 0; i < mesh->vertexCount; i += 3) - { - // Get triangle vertices - Vector3 v1 = { mesh->vertices[(i + 0)*3 + 0], mesh->vertices[(i + 0)*3 + 1], mesh->vertices[(i + 0)*3 + 2] }; - Vector3 v2 = { mesh->vertices[(i + 1)*3 + 0], mesh->vertices[(i + 1)*3 + 1], mesh->vertices[(i + 1)*3 + 2] }; - Vector3 v3 = { mesh->vertices[(i + 2)*3 + 0], mesh->vertices[(i + 2)*3 + 1], mesh->vertices[(i + 2)*3 + 2] }; - - // Get triangle texcoords - Vector2 uv1 = { mesh->texcoords[(i + 0)*2 + 0], mesh->texcoords[(i + 0)*2 + 1] }; - Vector2 uv2 = { mesh->texcoords[(i + 1)*2 + 0], mesh->texcoords[(i + 1)*2 + 1] }; - Vector2 uv3 = { mesh->texcoords[(i + 2)*2 + 0], mesh->texcoords[(i + 2)*2 + 1] }; - - float x1 = v2.x - v1.x; - float y1 = v2.y - v1.y; - float z1 = v2.z - v1.z; - float x2 = v3.x - v1.x; - float y2 = v3.y - v1.y; - float z2 = v3.z - v1.z; - - float s1 = uv2.x - uv1.x; - float t1 = uv2.y - uv1.y; - float s2 = uv3.x - uv1.x; - float t2 = uv3.y - uv1.y; - - float div = s1*t2 - s2*t1; - float r = (div == 0.0f) ? 0.0f : 1.0f/div; - - Vector3 sdir = { (t2*x1 - t1*x2)*r, (t2*y1 - t1*y2)*r, (t2*z1 - t1*z2)*r }; - Vector3 tdir = { (s1*x2 - s2*x1)*r, (s1*y2 - s2*y1)*r, (s1*z2 - s2*z1)*r }; - - tan1[i + 0] = sdir; - tan1[i + 1] = sdir; - tan1[i + 2] = sdir; - - tan2[i + 0] = tdir; - tan2[i + 1] = tdir; - tan2[i + 2] = tdir; - } - - // Compute tangents considering normals - for (int i = 0; i < mesh->vertexCount; ++i) - { - Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] }; - Vector3 tangent = tan1[i]; - - // TODO: Review, not sure if tangent computation is right, just used reference proposed maths... - #if defined(COMPUTE_TANGENTS_METHOD_01) - Vector3 tmp = Vector3Subtract(tangent, Vector3Multiply(normal, Vector3DotProduct(normal, tangent))); - tmp = Vector3Normalize(tmp); - mesh->tangents[i*4 + 0] = tmp.x; - mesh->tangents[i*4 + 1] = tmp.y; - mesh->tangents[i*4 + 2] = tmp.z; - mesh->tangents[i*4 + 3] = 1.0f; - #else - Vector3OrthoNormalize(&normal, &tangent); - mesh->tangents[i*4 + 0] = tangent.x; - mesh->tangents[i*4 + 1] = tangent.y; - mesh->tangents[i*4 + 2] = tangent.z; - mesh->tangents[i*4 + 3] = (Vector3DotProduct(Vector3CrossProduct(normal, tangent), tan2[i]) < 0.0f) ? -1.0f : 1.0f; - #endif - } - - free(tan1); - free(tan2); - - TraceLog(LOG_INFO, "Tangents computed for mesh"); -} - -// Compute mesh binormals (aka bitangent) -void MeshBinormals(Mesh *mesh) -{ - for (int i = 0; i < mesh->vertexCount; i++) - { - Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] }; - Vector3 tangent = { mesh->tangents[i*4 + 0], mesh->tangents[i*4 + 1], mesh->tangents[i*4 + 2] }; - float tangentW = mesh->tangents[i*4 + 3]; - - // TODO: Register computed binormal in mesh->binormal ? - // Vector3 binormal = Vector3Multiply(Vector3CrossProduct(normal, tangent), tangentW); - } -} - //---------------------------------------------------------------------------------- // Module specific Functions Definition //---------------------------------------------------------------------------------- #if defined(SUPPORT_FILEFORMAT_OBJ) // Load OBJ mesh data -static Mesh LoadOBJ(const char *fileName) +static Model LoadOBJ(const char *fileName) { - Mesh mesh = { 0 }; + Model model = { 0 }; - char dataType = 0; - char comments[200]; + tinyobj_attrib_t attrib; + tinyobj_shape_t *meshes = NULL; + unsigned int meshCount = 0; - int vertexCount = 0; - int normalCount = 0; - int texcoordCount = 0; - int triangleCount = 0; + tinyobj_material_t *materials = NULL; + unsigned int materialCount = 0; - FILE *objFile; + int dataLength = 0; + char *data = NULL; - objFile = fopen(fileName, "rt"); + // Load model data + FILE *objFile = fopen(fileName, "rb"); - if (objFile == NULL) + if (objFile != NULL) { - TraceLog(LOG_WARNING, "[%s] OBJ file could not be opened", fileName); - return mesh; + fseek(objFile, 0, SEEK_END); + long length = ftell(objFile); // Get file size + fseek(objFile, 0, SEEK_SET); // Reset file pointer + + data = (char *)RL_MALLOC(length); + + fread(data, length, 1, objFile); + dataLength = length; + fclose(objFile); } - // First reading pass: Get vertexCount, normalCount, texcoordCount, triangleCount - // NOTE: vertex, texcoords and normals could be optimized (to be used indexed on faces definition) - // NOTE: faces MUST be defined as TRIANGLES (3 vertex per face) - while (!feof(objFile)) + if (data != NULL) { - dataType = 0; - fscanf(objFile, "%c", &dataType); + unsigned int flags = TINYOBJ_FLAG_TRIANGULATE; + int ret = tinyobj_parse_obj(&attrib, &meshes, &meshCount, &materials, &materialCount, data, dataLength, flags); - switch (dataType) + if (ret != TINYOBJ_SUCCESS) TraceLog(LOG_WARNING, "[%s] Model data could not be loaded", fileName); + else TraceLog(LOG_INFO, "[%s] Model 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)); + + // Init model materials array + if (materialCount > 0) { - case '#': // Comments - case 'o': // Object name (One OBJ file can contain multible named meshes) - case 'g': // Group name - case 's': // Smoothing level - case 'm': // mtllib [external .mtl file name] - case 'u': // usemtl [material name] - { - fgets(comments, 200, objFile); - } break; - case 'v': - { - fscanf(objFile, "%c", &dataType); - - if (dataType == 't') // Read texCoord - { - texcoordCount++; - fgets(comments, 200, objFile); - } - else if (dataType == 'n') // Read normals - { - normalCount++; - fgets(comments, 200, objFile); - } - else // Read vertex - { - vertexCount++; - fgets(comments, 200, objFile); - } - } break; - case 'f': - { - triangleCount++; - fgets(comments, 200, objFile); - } break; - default: break; + model.materialCount = materialCount; + model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); } - } - TraceLog(LOG_DEBUG, "[%s] Model vertices: %i", fileName, vertexCount); - TraceLog(LOG_DEBUG, "[%s] Model texcoords: %i", fileName, texcoordCount); - TraceLog(LOG_DEBUG, "[%s] Model normals: %i", fileName, normalCount); - TraceLog(LOG_DEBUG, "[%s] Model triangles: %i", fileName, triangleCount); + model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); - // Once we know the number of vertices to store, we create required arrays - Vector3 *midVertices = (Vector3 *)malloc(vertexCount*sizeof(Vector3)); - Vector3 *midNormals = NULL; - if (normalCount > 0) midNormals = (Vector3 *)malloc(normalCount*sizeof(Vector3)); - Vector2 *midTexCoords = NULL; - if (texcoordCount > 0) midTexCoords = (Vector2 *)malloc(texcoordCount*sizeof(Vector2)); + /* + // 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; + */ - int countVertex = 0; - int countNormals = 0; - int countTexCoords = 0; - - rewind(objFile); // Return to the beginning of the file, to read again - - // Second reading pass: Get vertex data to fill intermediate arrays - // NOTE: This second pass is required in case of multiple meshes defined in same OBJ - // TODO: Consider that different meshes can have different vertex data available (position, texcoords, normals) - while (!feof(objFile)) - { - fscanf(objFile, "%c", &dataType); - - switch (dataType) + // Init model meshes + for (int m = 0; m < 1; m++) { - case '#': case 'o': case 'g': case 's': case 'm': case 'u': case 'f': fgets(comments, 200, objFile); break; - case 'v': + 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(MAX_MESH_VBO, sizeof(unsigned int)); + + int vCount = 0; + int vtCount = 0; + int vnCount = 0; + + for (int f = 0; f < attrib.num_faces; f++) { - fscanf(objFile, "%c", &dataType); + // 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]; - if (dataType == 't') // Read texCoord - { - fscanf(objFile, "%f %f%*[^\n]s\n", &midTexCoords[countTexCoords].x, &midTexCoords[countTexCoords].y); - countTexCoords++; + // TraceLog(LOG_DEBUG, "Face %i index: v %i/%i/%i . vt %i/%i/%i . vn %i/%i/%i\n", f, idx0.v_idx, idx1.v_idx, idx2.v_idx, idx0.vt_idx, idx1.vt_idx, idx2.vt_idx, idx0.vn_idx, idx1.vn_idx, idx2.vn_idx); - fscanf(objFile, "%c", &dataType); - } - else if (dataType == 'n') // Read normals - { - fscanf(objFile, "%f %f %f", &midNormals[countNormals].x, &midNormals[countNormals].y, &midNormals[countNormals].z); - countNormals++; + // 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; - fscanf(objFile, "%c", &dataType); - } - else // Read vertex - { - fscanf(objFile, "%f %f %f", &midVertices[countVertex].x, &midVertices[countVertex].y, &midVertices[countVertex].z); - countVertex++; + // 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; - fscanf(objFile, "%c", &dataType); - } - } break; - default: break; + // 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; + } + + 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; } - } - // At this point all vertex data (v, vt, vn) has been gathered on midVertices, midTexCoords, midNormals - // Now we can organize that data into our Mesh struct - - mesh.vertexCount = triangleCount*3; - - // Additional arrays to store vertex data as floats - mesh.vertices = (float *)malloc(mesh.vertexCount*3*sizeof(float)); - mesh.texcoords = (float *)malloc(mesh.vertexCount*2*sizeof(float)); - mesh.normals = (float *)malloc(mesh.vertexCount*3*sizeof(float)); - mesh.colors = NULL; - - int vCounter = 0; // Used to count vertices float by float - int tcCounter = 0; // Used to count texcoords float by float - int nCounter = 0; // Used to count normals float by float - - int vCount[3], vtCount[3], vnCount[3]; // Used to store triangle indices for v, vt, vn - - rewind(objFile); // Return to the beginning of the file, to read again - - if (normalCount == 0) TraceLog(LOG_INFO, "[%s] No normals data on OBJ, normals will be generated from faces data", fileName); - - // Third reading pass: Get faces (triangles) data and fill VertexArray - while (!feof(objFile)) - { - fscanf(objFile, "%c", &dataType); - - switch (dataType) + // Init model materials + for (int m = 0; m < materialCount; m++) { - case '#': case 'o': case 'g': case 's': case 'm': case 'u': case 'v': fgets(comments, 200, objFile); break; - case 'f': - { - // NOTE: It could be that OBJ does not have normals or texcoords defined! + // Init material to default + // NOTE: Uses default shader, only MAP_DIFFUSE supported + model.materials[m] = LoadMaterialDefault(); - if ((normalCount == 0) && (texcoordCount == 0)) fscanf(objFile, "%i %i %i", &vCount[0], &vCount[1], &vCount[2]); - else if (normalCount == 0) fscanf(objFile, "%i/%i %i/%i %i/%i", &vCount[0], &vtCount[0], &vCount[1], &vtCount[1], &vCount[2], &vtCount[2]); - else if (texcoordCount == 0) fscanf(objFile, "%i//%i %i//%i %i//%i", &vCount[0], &vnCount[0], &vCount[1], &vnCount[1], &vCount[2], &vnCount[2]); - else fscanf(objFile, "%i/%i/%i %i/%i/%i %i/%i/%i", &vCount[0], &vtCount[0], &vnCount[0], &vCount[1], &vtCount[1], &vnCount[1], &vCount[2], &vtCount[2], &vnCount[2]); + /* + typedef struct { + char *name; - mesh.vertices[vCounter] = midVertices[vCount[0]-1].x; - mesh.vertices[vCounter + 1] = midVertices[vCount[0]-1].y; - mesh.vertices[vCounter + 2] = midVertices[vCount[0]-1].z; - vCounter += 3; - mesh.vertices[vCounter] = midVertices[vCount[1]-1].x; - mesh.vertices[vCounter + 1] = midVertices[vCount[1]-1].y; - mesh.vertices[vCounter + 2] = midVertices[vCount[1]-1].z; - vCounter += 3; - mesh.vertices[vCounter] = midVertices[vCount[2]-1].x; - mesh.vertices[vCounter + 1] = midVertices[vCount[2]-1].y; - mesh.vertices[vCounter + 2] = midVertices[vCount[2]-1].z; - vCounter += 3; + 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 (see http://www.fileformat.info/format/material/) + int illum; - if (normalCount > 0) - { - mesh.normals[nCounter] = midNormals[vnCount[0]-1].x; - mesh.normals[nCounter + 1] = midNormals[vnCount[0]-1].y; - mesh.normals[nCounter + 2] = midNormals[vnCount[0]-1].z; - nCounter += 3; - mesh.normals[nCounter] = midNormals[vnCount[1]-1].x; - mesh.normals[nCounter + 1] = midNormals[vnCount[1]-1].y; - mesh.normals[nCounter + 2] = midNormals[vnCount[1]-1].z; - nCounter += 3; - mesh.normals[nCounter] = midNormals[vnCount[2]-1].x; - mesh.normals[nCounter + 1] = midNormals[vnCount[2]-1].y; - mesh.normals[nCounter + 2] = midNormals[vnCount[2]-1].z; - nCounter += 3; - } - else - { - // If normals not defined, they are calculated from the 3 vertices [N = (V2 - V1) x (V3 - V1)] - Vector3 norm = Vector3CrossProduct(Vector3Subtract(midVertices[vCount[1]-1], midVertices[vCount[0]-1]), Vector3Subtract(midVertices[vCount[2]-1], midVertices[vCount[0]-1])); - norm = Vector3Normalize(norm); + int pad0; - mesh.normals[nCounter] = norm.x; - mesh.normals[nCounter + 1] = norm.y; - mesh.normals[nCounter + 2] = norm.z; - nCounter += 3; - mesh.normals[nCounter] = norm.x; - mesh.normals[nCounter + 1] = norm.y; - mesh.normals[nCounter + 2] = norm.z; - nCounter += 3; - mesh.normals[nCounter] = norm.x; - mesh.normals[nCounter + 1] = norm.y; - mesh.normals[nCounter + 2] = norm.z; - nCounter += 3; - } + 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 (texcoordCount > 0) - { - // NOTE: If using negative texture coordinates with a texture filter of GL_CLAMP_TO_EDGE doesn't work! - // NOTE: Texture coordinates are Y flipped upside-down - mesh.texcoords[tcCounter] = midTexCoords[vtCount[0]-1].x; - mesh.texcoords[tcCounter + 1] = 1.0f - midTexCoords[vtCount[0]-1].y; - tcCounter += 2; - mesh.texcoords[tcCounter] = midTexCoords[vtCount[1]-1].x; - mesh.texcoords[tcCounter + 1] = 1.0f - midTexCoords[vtCount[1]-1].y; - tcCounter += 2; - mesh.texcoords[tcCounter] = midTexCoords[vtCount[2]-1].x; - mesh.texcoords[tcCounter + 1] = 1.0f - midTexCoords[vtCount[2]-1].y; - tcCounter += 2; - } - } break; - default: break; + model.materials[m].maps[MAP_DIFFUSE].texture = GetTextureDefault(); // Get default texture, in case no texture is defined + + 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){ (float)(materials[m].diffuse[0]*255.0f), (float)(materials[m].diffuse[1]*255.0f), (float)(materials[m].diffuse[2]*255.0f), 255 }; //float diffuse[3]; + model.materials[m].maps[MAP_DIFFUSE].value = 0.0f; + + 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){ (float)(materials[m].specular[0]*255.0f), (float)(materials[m].specular[1]*255.0f), (float)(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){ (float)(materials[m].emission[0]*255.0f), (float)(materials[m].emission[1]*255.0f), (float)(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 } + + tinyobj_attrib_free(&attrib); + tinyobj_shapes_free(meshes, meshCount); + tinyobj_materials_free(materials, materialCount); + + RL_FREE(data); } - fclose(objFile); - - // Now we can free temp mid* arrays - free(midVertices); - free(midNormals); - free(midTexCoords); - - // NOTE: At this point we have all vertex, texcoord, normal data for the model in mesh struct + // NOTE: At this point we have all model data loaded TraceLog(LOG_INFO, "[%s] Model loaded successfully in RAM (CPU)", fileName); - return mesh; + return model; } #endif -#if defined(SUPPORT_FILEFORMAT_MTL) -// Load MTL material data (specs: http://paulbourke.net/dataformats/mtl/) -// NOTE: Texture map parameters are not supported -static Material LoadMTL(const char *fileName) +#if defined(SUPPORT_FILEFORMAT_IQM) +// Load IQM mesh data +static Model LoadIQM(const char *fileName) { - #define MAX_BUFFER_SIZE 128 + #define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number + #define IQM_VERSION 2 // only IQM version 2 supported - Material material = { 0 }; + #define BONE_NAME_LENGTH 32 // BoneInfo name string length + #define MESH_NAME_LENGTH 32 // Mesh name string length - char buffer[MAX_BUFFER_SIZE]; - Vector3 color = { 1.0f, 1.0f, 1.0f }; - char mapFileName[128]; - int result = 0; + // IQM file structs + //----------------------------------------------------------------------------------- + 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; - FILE *mtlFile; + typedef struct IQMMesh { + unsigned int name; + unsigned int material; + unsigned int first_vertex, num_vertexes; + unsigned int first_triangle, num_triangles; + } IQMMesh; - mtlFile = fopen(fileName, "rt"); + typedef struct IQMTriangle { + unsigned int vertex[3]; + } IQMTriangle; - if (mtlFile == NULL) + typedef struct IQMJoint { + unsigned int name; + int parent; + float translate[3], rotate[4], scale[3]; + } IQMJoint; + + typedef struct IQMVertexArray { + unsigned int type; + unsigned int flags; + unsigned int format; + unsigned int size; + unsigned int offset; + } IQMVertexArray; + + // NOTE: Below IQM structures are not used but listed for reference + /* + typedef struct IQMAdjacency { + unsigned int triangle[3]; + } IQMAdjacency; + + 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; + + typedef struct IQMBounds { + float bbmin[3], bbmax[3]; + float xyradius, radius; + } IQMBounds; + */ + //----------------------------------------------------------------------------------- + + // IQM vertex data types + typedef enum { + IQM_POSITION = 0, + IQM_TEXCOORD = 1, + IQM_NORMAL = 2, + IQM_TANGENT = 3, // NOTE: Tangents unused by default + IQM_BLENDINDEXES = 4, + IQM_BLENDWEIGHTS = 5, + IQM_COLOR = 6, // NOTE: Vertex colors unused by default + IQM_CUSTOM = 0x10 // NOTE: Custom vertex values unused by default + } IQMVertexType; + + Model model = { 0 }; + + FILE *iqmFile; + IQMHeader iqm; + + IQMMesh *imesh; + IQMTriangle *tri; + IQMVertexArray *va; + IQMJoint *ijoint; + + float *vertex = NULL; + float *normal = NULL; + float *text = NULL; + char *blendi = NULL; + unsigned char *blendw = NULL; + + iqmFile = fopen(fileName, "rb"); + + if (iqmFile == NULL) { - TraceLog(LOG_WARNING, "[%s] MTL file could not be opened", fileName); - return material; + TraceLog(LOG_WARNING, "[%s] IQM file could not be opened", fileName); + return model; } - while (!feof(mtlFile)) + fread(&iqm,sizeof(IQMHeader), 1, iqmFile); // Read IQM header + + if (strncmp(iqm.magic, IQM_MAGIC, sizeof(IQM_MAGIC))) { - fgets(buffer, MAX_BUFFER_SIZE, mtlFile); + TraceLog(LOG_WARNING, "[%s] IQM file does not seem to be valid", fileName); + fclose(iqmFile); + return model; + } - switch (buffer[0]) + if (iqm.version != IQM_VERSION) + { + TraceLog(LOG_WARNING, "[%s] IQM file version is not supported (%i).", fileName, iqm.version); + fclose(iqmFile); + return model; + } + + // Meshes data processing + imesh = RL_MALLOC(sizeof(IQMMesh)*iqm.num_meshes); + fseek(iqmFile, iqm.ofs_meshes, SEEK_SET); + fread(imesh, sizeof(IQMMesh)*iqm.num_meshes, 1, iqmFile); + + model.meshCount = iqm.num_meshes; + model.meshes = RL_CALLOC(model.meshCount, sizeof(Mesh)); + + char name[MESH_NAME_LENGTH] = { 0 }; + + for (int i = 0; i < model.meshCount; i++) + { + fseek(iqmFile, iqm.ofs_text + imesh[i].name, SEEK_SET); + fread(name, sizeof(char)*MESH_NAME_LENGTH, 1, iqmFile); // Mesh name not used... + model.meshes[i].vertexCount = imesh[i].num_vertexes; + + model.meshes[i].vertices = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); // Default vertex positions + model.meshes[i].normals = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); // Default vertex normals + model.meshes[i].texcoords = RL_CALLOC(model.meshes[i].vertexCount*2, sizeof(float)); // Default vertex texcoords + + model.meshes[i].boneIds = RL_CALLOC(model.meshes[i].vertexCount*4, sizeof(float)); // Up-to 4 bones supported! + model.meshes[i].boneWeights = RL_CALLOC(model.meshes[i].vertexCount*4, sizeof(float)); // Up-to 4 bones supported! + + model.meshes[i].triangleCount = imesh[i].num_triangles; + model.meshes[i].indices = RL_CALLOC(model.meshes[i].triangleCount*3, sizeof(unsigned short)); + + // Animated verted data, what we actually process for rendering + // 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(MAX_MESH_VBO, sizeof(unsigned int)); + } + + // Triangles data processing + tri = RL_MALLOC(iqm.num_triangles*sizeof(IQMTriangle)); + fseek(iqmFile, iqm.ofs_triangles, SEEK_SET); + fread(tri, iqm.num_triangles*sizeof(IQMTriangle), 1, iqmFile); + + for (int m = 0; m < model.meshCount; m++) + { + int tcounter = 0; + + for (int i = imesh[m].first_triangle; i < (imesh[m].first_triangle + imesh[m].num_triangles); i++) { - case 'n': // newmtl string Material name. Begins a new material description. - { - // TODO: Support multiple materials in a single .mtl - sscanf(buffer, "newmtl %127s", mapFileName); - - TraceLog(LOG_INFO, "[%s] Loading material...", mapFileName); - } - case 'i': // illum int Illumination model - { - // illum = 1 if specular disabled - // illum = 2 if specular enabled (lambertian model) - // ... - } - case 'K': // Ka, Kd, Ks, Ke - { - switch (buffer[1]) - { - case 'a': // Ka float float float Ambient color (RGB) - { - sscanf(buffer, "Ka %f %f %f", &color.x, &color.y, &color.z); - // TODO: Support ambient color - //material.colAmbient.r = (unsigned char)(color.x*255); - //material.colAmbient.g = (unsigned char)(color.y*255); - //material.colAmbient.b = (unsigned char)(color.z*255); - } break; - case 'd': // Kd float float float Diffuse color (RGB) - { - sscanf(buffer, "Kd %f %f %f", &color.x, &color.y, &color.z); - material.maps[MAP_DIFFUSE].color.r = (unsigned char)(color.x*255); - material.maps[MAP_DIFFUSE].color.g = (unsigned char)(color.y*255); - material.maps[MAP_DIFFUSE].color.b = (unsigned char)(color.z*255); - } break; - case 's': // Ks float float float Specular color (RGB) - { - sscanf(buffer, "Ks %f %f %f", &color.x, &color.y, &color.z); - material.maps[MAP_SPECULAR].color.r = (unsigned char)(color.x*255); - material.maps[MAP_SPECULAR].color.g = (unsigned char)(color.y*255); - material.maps[MAP_SPECULAR].color.b = (unsigned char)(color.z*255); - } break; - case 'e': // Ke float float float Emmisive color (RGB) - { - // TODO: Support Ke ? - } break; - default: break; - } - } break; - case 'N': // Ns, Ni - { - if (buffer[1] == 's') // Ns int Shininess (specular exponent). Ranges from 0 to 1000. - { - int shininess = 0; - sscanf(buffer, "Ns %i", &shininess); - - //material.params[PARAM_GLOSSINES] = (float)shininess; - } - else if (buffer[1] == 'i') // Ni int Refraction index. - { - // Not supported... - } - } break; - case 'm': // map_Kd, map_Ks, map_Ka, map_Bump, map_d - { - switch (buffer[4]) - { - case 'K': // Color texture maps - { - if (buffer[5] == 'd') // map_Kd string Diffuse color texture map. - { - result = sscanf(buffer, "map_Kd %127s", mapFileName); - if (result != EOF) material.maps[MAP_DIFFUSE].texture = LoadTexture(mapFileName); - } - else if (buffer[5] == 's') // map_Ks string Specular color texture map. - { - result = sscanf(buffer, "map_Ks %127s", mapFileName); - if (result != EOF) material.maps[MAP_SPECULAR].texture = LoadTexture(mapFileName); - } - else if (buffer[5] == 'a') // map_Ka string Ambient color texture map. - { - // Not supported... - } - } break; - case 'B': // map_Bump string Bump texture map. - { - result = sscanf(buffer, "map_Bump %127s", mapFileName); - if (result != EOF) material.maps[MAP_NORMAL].texture = LoadTexture(mapFileName); - } break; - case 'b': // map_bump string Bump texture map. - { - result = sscanf(buffer, "map_bump %127s", mapFileName); - if (result != EOF) material.maps[MAP_NORMAL].texture = LoadTexture(mapFileName); - } break; - case 'd': // map_d string Opacity texture map. - { - // Not supported... - } break; - default: break; - } - } break; - case 'd': // d, disp - { - if (buffer[1] == ' ') // d float Dissolve factor. d is inverse of Tr - { - float alpha = 1.0f; - sscanf(buffer, "d %f", &alpha); - material.maps[MAP_DIFFUSE].color.a = (unsigned char)(alpha*255); - } - else if (buffer[1] == 'i') // disp string Displacement map - { - // Not supported... - } - } break; - case 'b': // bump string Bump texture map - { - result = sscanf(buffer, "bump %127s", mapFileName); - if (result != EOF) material.maps[MAP_NORMAL].texture = LoadTexture(mapFileName); - } break; - case 'T': // Tr float Transparency Tr (alpha). Tr is inverse of d - { - float ialpha = 0.0f; - sscanf(buffer, "Tr %f", &ialpha); - material.maps[MAP_DIFFUSE].color.a = (unsigned char)((1.0f - ialpha)*255); - - } break; - case 'r': // refl string Reflection texture map - default: break; + // IQM triangles are stored counter clockwise, but raylib sets opengl to clockwise drawing, so we swap them around + 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; + model.meshes[m].indices[tcounter] = tri[i].vertex[2] - imesh[m].first_vertex; + tcounter += 3; } } - fclose(mtlFile); + // Vertex arrays data processing + va = RL_MALLOC(iqm.num_vertexarrays*sizeof(IQMVertexArray)); + fseek(iqmFile, iqm.ofs_vertexarrays, SEEK_SET); + fread(va, iqm.num_vertexarrays*sizeof(IQMVertexArray), 1, iqmFile); - // NOTE: At this point we have all material data - TraceLog(LOG_INFO, "[%s] Material loaded successfully", fileName); + for (int i = 0; i < iqm.num_vertexarrays; i++) + { + switch (va[i].type) + { + case IQM_POSITION: + { + vertex = RL_MALLOC(iqm.num_vertexes*3*sizeof(float)); + fseek(iqmFile, va[i].offset, SEEK_SET); + fread(vertex, iqm.num_vertexes*3*sizeof(float), 1, iqmFile); - return material; + for (int m = 0; m < iqm.num_meshes; m++) + { + int vCounter = 0; + for (int i = imesh[m].first_vertex*3; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*3; i++) + { + model.meshes[m].vertices[vCounter] = vertex[i]; + model.meshes[m].animVertices[vCounter] = vertex[i]; + vCounter++; + } + } + } break; + case IQM_NORMAL: + { + normal = RL_MALLOC(iqm.num_vertexes*3*sizeof(float)); + fseek(iqmFile, va[i].offset, SEEK_SET); + fread(normal, iqm.num_vertexes*3*sizeof(float), 1, iqmFile); + + for (int m = 0; m < iqm.num_meshes; m++) + { + int vCounter = 0; + for (int i = imesh[m].first_vertex*3; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*3; i++) + { + model.meshes[m].normals[vCounter] = normal[i]; + model.meshes[m].animNormals[vCounter] = normal[i]; + vCounter++; + } + } + } break; + case IQM_TEXCOORD: + { + text = RL_MALLOC(iqm.num_vertexes*2*sizeof(float)); + fseek(iqmFile, va[i].offset, SEEK_SET); + fread(text, iqm.num_vertexes*2*sizeof(float), 1, iqmFile); + + for (int m = 0; m < iqm.num_meshes; m++) + { + int vCounter = 0; + for (int i = imesh[m].first_vertex*2; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*2; i++) + { + model.meshes[m].texcoords[vCounter] = text[i]; + vCounter++; + } + } + } break; + case IQM_BLENDINDEXES: + { + blendi = RL_MALLOC(iqm.num_vertexes*4*sizeof(char)); + fseek(iqmFile, va[i].offset, SEEK_SET); + fread(blendi, iqm.num_vertexes*4*sizeof(char), 1, iqmFile); + + for (int m = 0; m < iqm.num_meshes; m++) + { + int boneCounter = 0; + for (int i = imesh[m].first_vertex*4; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*4; i++) + { + model.meshes[m].boneIds[boneCounter] = blendi[i]; + boneCounter++; + } + } + } break; + case IQM_BLENDWEIGHTS: + { + blendw = RL_MALLOC(iqm.num_vertexes*4*sizeof(unsigned char)); + fseek(iqmFile, va[i].offset, SEEK_SET); + fread(blendw, iqm.num_vertexes*4*sizeof(unsigned char), 1, iqmFile); + + for (int m = 0; m < iqm.num_meshes; m++) + { + int boneCounter = 0; + for (int i = imesh[m].first_vertex*4; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*4; i++) + { + model.meshes[m].boneWeights[boneCounter] = blendw[i]/255.0f; + boneCounter++; + } + } + } break; + } + } + + // Bones (joints) data processing + ijoint = RL_MALLOC(iqm.num_joints*sizeof(IQMJoint)); + fseek(iqmFile, iqm.ofs_joints, SEEK_SET); + fread(ijoint, iqm.num_joints*sizeof(IQMJoint), 1, iqmFile); + + model.boneCount = iqm.num_joints; + model.bones = RL_MALLOC(iqm.num_joints*sizeof(BoneInfo)); + model.bindPose = RL_MALLOC(iqm.num_joints*sizeof(Transform)); + + for (int i = 0; i < iqm.num_joints; i++) + { + // Bones + model.bones[i].parent = ijoint[i].parent; + fseek(iqmFile, iqm.ofs_text + ijoint[i].name, SEEK_SET); + fread(model.bones[i].name, BONE_NAME_LENGTH*sizeof(char), 1, iqmFile); + + // Bind pose (base pose) + model.bindPose[i].translation.x = ijoint[i].translate[0]; + model.bindPose[i].translation.y = ijoint[i].translate[1]; + model.bindPose[i].translation.z = ijoint[i].translate[2]; + + model.bindPose[i].rotation.x = ijoint[i].rotate[0]; + model.bindPose[i].rotation.y = ijoint[i].rotate[1]; + model.bindPose[i].rotation.z = ijoint[i].rotate[2]; + model.bindPose[i].rotation.w = ijoint[i].rotate[3]; + + model.bindPose[i].scale.x = ijoint[i].scale[0]; + model.bindPose[i].scale.y = ijoint[i].scale[1]; + model.bindPose[i].scale.z = ijoint[i].scale[2]; + } + + // Build bind pose from parent joints + for (int i = 0; i < model.boneCount; i++) + { + if (model.bones[i].parent >= 0) + { + model.bindPose[i].rotation = QuaternionMultiply(model.bindPose[model.bones[i].parent].rotation, model.bindPose[i].rotation); + model.bindPose[i].translation = Vector3RotateByQuaternion(model.bindPose[i].translation, model.bindPose[model.bones[i].parent].rotation); + model.bindPose[i].translation = Vector3Add(model.bindPose[i].translation, model.bindPose[model.bones[i].parent].translation); + model.bindPose[i].scale = Vector3MultiplyV(model.bindPose[i].scale, model.bindPose[model.bones[i].parent].scale); + } + } + + fclose(iqmFile); + RL_FREE(imesh); + RL_FREE(tri); + RL_FREE(va); + RL_FREE(vertex); + RL_FREE(normal); + RL_FREE(text); + RL_FREE(blendi); + RL_FREE(blendw); + RL_FREE(ijoint); + + return model; } #endif #if defined(SUPPORT_FILEFORMAT_GLTF) -// Load IQM mesh data -static Mesh LoadIQM(const char *fileName) -{ - Mesh mesh = { 0 }; - - // TODO: Load IQM file - - return mesh; -} -#endif -#if defined(SUPPORT_FILEFORMAT_GLTF) -// Load GLTF mesh data -static Mesh LoadGLTF(const char *fileName) +static const unsigned char base64Table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 62, 0, 0, 0, 63, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51 +}; + +static int GetSizeBase64(char *input) { - Mesh mesh = { 0 }; - - // GLTF file loading + int size = 0; + + for (int i = 0; input[4*i] != 0; i++) + { + if (input[4*i + 3] == '=') + { + if (input[4*i + 2] == '=') size += 1; + else size += 2; + } + else size += 3; + } + + return size; +} + +static unsigned char *DecodeBase64(char *input, int *size) +{ + *size = GetSizeBase64(input); + + unsigned char *buf = (unsigned char *)RL_MALLOC(*size); + for (int i = 0; i < *size/3; i++) + { + unsigned char a = base64Table[(int)input[4*i]]; + unsigned char b = base64Table[(int)input[4*i + 1]]; + unsigned char c = base64Table[(int)input[4*i + 2]]; + unsigned char d = base64Table[(int)input[4*i + 3]]; + + buf[3*i] = (a << 2) | (b >> 4); + buf[3*i + 1] = (b << 4) | (c >> 2); + buf[3*i + 2] = (c << 6) | d; + } + + if (*size%3 == 1) + { + int n = *size/3; + unsigned char a = base64Table[(int)input[4*n]]; + unsigned char b = base64Table[(int)input[4*n + 1]]; + buf[*size - 1] = (a << 2) | (b >> 4); + } + else if (*size%3 == 2) + { + int n = *size/3; + unsigned char a = base64Table[(int)input[4*n]]; + unsigned char b = base64Table[(int)input[4*n + 1]]; + unsigned char c = base64Table[(int)input[4*n + 2]]; + buf[*size - 2] = (a << 2) | (b >> 4); + buf[*size - 1] = (b << 4) | (c >> 2); + } + return buf; +} + +// Load texture from cgltf_image +static Texture LoadTextureFromCgltfImage(cgltf_image *image, const char *texPath, Color tint) +{ + Texture texture = { 0 }; + + if (image->uri) + { + if ((strlen(image->uri) > 5) && + (image->uri[0] == 'd') && + (image->uri[1] == 'a') && + (image->uri[2] == 't') && + (image->uri[3] == 'a') && + (image->uri[4] == ':')) + { + // Data URI + // Format: data:;base64, + + // Find the comma + int i = 0; + while ((image->uri[i] != ',') && (image->uri[i] != 0)) i++; + + if (image->uri[i] == 0) TraceLog(LOG_WARNING, "CGLTF Image: Invalid data URI"); + else + { + int size; + unsigned char *data = DecodeBase64(image->uri + i + 1, &size); + + int w, h; + unsigned char *raw = stbi_load_from_memory(data, size, &w, &h, NULL, 4); + + Image rimage = LoadImagePro(raw, w, h, UNCOMPRESSED_R8G8B8A8); + + // TODO: Tint shouldn't be applied here! + ImageColorTint(&rimage, tint); + texture = LoadTextureFromImage(rimage); + UnloadImage(rimage); + } + } + else + { + Image rimage = LoadImage(TextFormat("%s/%s", texPath, image->uri)); + + // TODO: Tint shouldn't be applied here! + ImageColorTint(&rimage, tint); + texture = LoadTextureFromImage(rimage); + UnloadImage(rimage); + } + } + else if (image->buffer_view) + { + unsigned char *data = RL_MALLOC(image->buffer_view->size); + int n = image->buffer_view->offset; + int stride = image->buffer_view->stride ? image->buffer_view->stride : 1; + + for (int i = 0; i < image->buffer_view->size; i++) + { + data[i] = ((unsigned char *)image->buffer_view->buffer->data)[n]; + n += stride; + } + + int w, h; + unsigned char *raw = stbi_load_from_memory(data, image->buffer_view->size, &w, &h, NULL, 4); + free(data); + + Image rimage = LoadImagePro(raw, w, h, UNCOMPRESSED_R8G8B8A8); + free(raw); + + // TODO: Tint shouldn't be applied here! + ImageColorTint(&rimage, tint); + texture = LoadTextureFromImage(rimage); + UnloadImage(rimage); + } + else + { + Image rimage = LoadImageEx(&tint, 1, 1); + texture = LoadTextureFromImage(rimage); + UnloadImage(rimage); + } + + return texture; +} + +// Load glTF mesh data +static Model LoadGLTF(const char *fileName) +{ + /*********************************************************************************** + + Function implemented by Wilhem Barbier (@wbrbr) + + Features: + - Supports .gltf and .glb files + - Supports embedded (base64) or external textures + - Loads the albedo/diffuse texture (other maps could be added) + - Supports multiple mesh per model and multiple primitives per model + + Some restrictions (not exhaustive): + - Triangle-only meshes + - Not supported node hierarchies or transforms + - Only loads the diffuse texture... but not too hard to support other maps (normal, roughness/metalness...) + - Only supports unsigned short indices (no byte/unsigned int) + - Only supports float for texture coordinates (no byte/unsigned short) + + *************************************************************************************/ + + #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 (int k = 0; k < acc->count; k++) {\ + for (int l = 0; l < nbcomp; l++) {\ + dst[nbcomp*k+l] = buf[n+l];\ + }\ + n += acc->stride/sizeof(type);\ + }\ + } + + Model model = { 0 }; + + // glTF file loading FILE *gltfFile = fopen(fileName, "rb"); - + if (gltfFile == NULL) { - TraceLog(LOG_WARNING, "[%s] GLTF file could not be opened", fileName); - return mesh; + TraceLog(LOG_WARNING, "[%s] glTF file could not be opened", fileName); + return model; } fseek(gltfFile, 0, SEEK_END); int size = ftell(gltfFile); fseek(gltfFile, 0, SEEK_SET); - void *buffer = malloc(size); + void *buffer = RL_MALLOC(size); fread(buffer, size, 1, gltfFile); - + fclose(gltfFile); - // GLTF data loading - cgltf_options options = {0}; - cgltf_data data; + // glTF data loading + cgltf_options options = { 0 }; + cgltf_data *data = NULL; cgltf_result result = cgltf_parse(&options, buffer, size, &data); if (result == cgltf_result_success) { - printf("Type: %u\n", data.file_type); - printf("Version: %d\n", data.version); - printf("Meshes: %lu\n", data.meshes_count); - } - else TraceLog(LOG_WARNING, "[%s] GLTF data could not be loaded", fileName); + TraceLog(LOG_INFO, "[%s][%s] Model meshes/materials: %i/%i", fileName, (data->file_type == 2)? "glb" : "gltf", data->meshes_count, data->materials_count); - free(buffer); - cgltf_free(&data); - - return mesh; + // Read data buffers + result = cgltf_load_buffers(&options, data, fileName); + if (result != cgltf_result_success) TraceLog(LOG_INFO, "[%s][%s] Error loading mesh/material buffers", fileName, (data->file_type == 2)? "glb" : "gltf"); + + int primitivesCount = 0; + + for (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; + model.meshes = RL_CALLOC(model.meshCount, sizeof(Mesh)); + model.materialCount = data->materials_count + 1; + model.materials = RL_MALLOC(model.materialCount*sizeof(Material)); + model.meshMaterial = RL_MALLOC(model.meshCount*sizeof(int)); + + for (int i = 0; i < model.meshCount; i++) model.meshes[i].vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VBO, sizeof(unsigned int)); + + //For each material + 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) + { + float roughness = data->materials[i].pbr_metallic_roughness.roughness_factor; + float metallic = data->materials[i].pbr_metallic_roughness.metallic_factor; + + // NOTE: Material name not used for the moment + //if (model.materials[i].name && data->materials[i].name) strcpy(model.materials[i].name, data->materials[i].name); + + // TODO: REview: shouldn't these be *255 ??? + 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_ROUGHNESS].color = tint; + + if (data->materials[i].pbr_metallic_roughness.base_color_texture.texture) + { + model.materials[i].maps[MAP_ALBEDO].texture = LoadTextureFromCgltfImage(data->materials[i].pbr_metallic_roughness.base_color_texture.texture->image, texPath, tint); + } + + // NOTE: Tint isn't need for other textures.. pass null or clear? + // Just set as white, multiplying by white has no effect + tint = WHITE; + + if (data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture) + { + model.materials[i].maps[MAP_ROUGHNESS].texture = LoadTextureFromCgltfImage(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture->image, texPath, tint); + } + model.materials[i].maps[MAP_ROUGHNESS].value = roughness; + model.materials[i].maps[MAP_METALNESS].value = metallic; + + if (data->materials[i].normal_texture.texture) + { + model.materials[i].maps[MAP_NORMAL].texture = LoadTextureFromCgltfImage(data->materials[i].normal_texture.texture->image, texPath, tint); + } + + if (data->materials[i].occlusion_texture.texture) + { + model.materials[i].maps[MAP_OCCLUSION].texture = LoadTextureFromCgltfImage(data->materials[i].occlusion_texture.texture->image, texPath, tint); + } + } + } + + model.materials[model.materialCount - 1] = LoadMaterialDefault(); + + int primitiveIndex = 0; + + for (int i = 0; i < data->meshes_count; i++) + { + for (int p = 0; p < data->meshes[i].primitives_count; p++) + { + for (int j = 0; j < data->meshes[i].primitives[p].attributes_count; j++) + { + if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_position) + { + cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; + model.meshes[primitiveIndex].vertexCount = acc->count; + model.meshes[primitiveIndex].vertices = RL_MALLOC(sizeof(float)*model.meshes[primitiveIndex].vertexCount*3); + + LOAD_ACCESSOR(float, 3, acc, model.meshes[primitiveIndex].vertices) + } + 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(sizeof(float)*acc->count*3); + + LOAD_ACCESSOR(float, 3, acc, model.meshes[primitiveIndex].normals) + } + else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_texcoord) + { + cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; + + if (acc->component_type == cgltf_component_type_r_32f) + { + model.meshes[primitiveIndex].texcoords = RL_MALLOC(sizeof(float)*acc->count*2); + LOAD_ACCESSOR(float, 2, acc, model.meshes[primitiveIndex].texcoords) + } + else + { + // TODO: Support normalized unsigned byte/unsigned short texture coordinates + TraceLog(LOG_WARNING, "[%s] Texture coordinates must be float", 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 = acc->count/3; + model.meshes[primitiveIndex].indices = RL_MALLOC(sizeof(unsigned short)*model.meshes[primitiveIndex].triangleCount*3); + LOAD_ACCESSOR(unsigned short, 1, acc, model.meshes[primitiveIndex].indices) + } + else + { + // TODO: Support unsigned byte/unsigned int + TraceLog(LOG_WARNING, "[%s] Indices must be unsigned short", fileName); + } + } + else + { + // Unindexed mesh + model.meshes[primitiveIndex].triangleCount = model.meshes[primitiveIndex].vertexCount/3; + } + + if (data->meshes[i].primitives[p].material) + { + // Compute the offset + model.meshMaterial[primitiveIndex] = data->meshes[i].primitives[p].material - data->materials; + } + else + { + model.meshMaterial[primitiveIndex] = model.materialCount - 1;; + } + + primitiveIndex++; + } + } + + cgltf_free(data); + } + else TraceLog(LOG_WARNING, "[%s] glTF data could not be loaded", fileName); + + RL_FREE(buffer); + + return model; } #endif diff --git a/raylib/models.go b/raylib/models.go index ae7fe8b..f2aa3c8 100644 --- a/raylib/models.go +++ b/raylib/models.go @@ -162,11 +162,12 @@ func DrawGizmo(position Vector3) { C.DrawGizmo(*cposition) } -// LoadMesh - Load mesh from file -func LoadMesh(fileName string) Mesh { +// LoadMeshes - Load mesh from file +func LoadMeshes(fileName string) Mesh { cfileName := C.CString(fileName) defer C.free(unsafe.Pointer(cfileName)) - ret := C.LoadMesh(cfileName) + ccount := C.int(0) + ret := C.LoadMeshes(cfileName, &ccount) v := newMeshFromPointer(unsafe.Pointer(&ret)) return v } @@ -197,7 +198,7 @@ func UnloadModel(model Model) { // UnloadMesh - Unload mesh from memory (RAM and/or VRAM) func UnloadMesh(mesh *Mesh) { cmesh := mesh.cptr() - C.UnloadMesh(cmesh) + C.UnloadMesh(*cmesh) } // ExportMesh - Export mesh as an OBJ file @@ -308,11 +309,12 @@ func GenMeshCubicmap(cubicmap Image, size Vector3) Mesh { return v } -// LoadMaterial - Load material data (.MTL) -func LoadMaterial(fileName string) Material { +// LoadMaterials - Load material data (.MTL) +func LoadMaterials(fileName string) Material { cfileName := C.CString(fileName) defer C.free(unsafe.Pointer(cfileName)) - ret := C.LoadMaterial(cfileName) + ccount := C.int(0) + ret := C.LoadMaterials(cfileName, &ccount) v := newMaterialFromPointer(unsafe.Pointer(&ret)) return v } diff --git a/raylib/audio.c b/raylib/raudio.c similarity index 50% rename from raylib/audio.c rename to raylib/raudio.c index 22da816..9e781fb 100644 --- a/raylib/audio.c +++ b/raylib/raudio.c @@ -2,7 +2,7 @@ /********************************************************************************************** * -* raylib.audio - Basic funtionality to work with audio +* raudio - A simple and easy-to-use audio library based on miniaudio * * FEATURES: * - Manage audio device (init/close) @@ -14,7 +14,7 @@ * * CONFIGURATION: * -* #define AUDIO_STANDALONE +* #define RAUDIO_STANDALONE * Define to use the module as standalone library (independently of raylib). * Required types and functions are defined in the same module. * @@ -27,22 +27,17 @@ * Selected desired fileformats to be supported for loading. Some of those formats are * supported by default, to remove support, just comment unrequired #define in this module * -* LIMITATIONS (only OpenAL Soft): -* Only up to two channels supported: MONO and STEREO (for additional channels, use AL_EXT_MCFORMATS) -* Only the following sample sizes supported: 8bit PCM, 16bit PCM, 32-bit float PCM (using AL_EXT_FLOAT32) -* * DEPENDENCIES: -* mini_al - Audio device/context management (https://github.com/dr-soft/mini_al) -* stb_vorbis - OGG audio files loading (http://www.nothings.org/stb_vorbis/) -* jar_xm - XM module file loading -* jar_mod - MOD audio file loading -* dr_flac - FLAC audio file loading -* -* *OpenAL Soft - Audio device management, still used on HTML5 and OSX platforms +* miniaudio.h - Audio device management lib (https://github.com/dr-soft/miniaudio) +* stb_vorbis.h - Ogg audio files loading (http://www.nothings.org/stb_vorbis/) +* dr_mp3.h - MP3 audio file loading (https://github.com/mackron/dr_libs) +* dr_flac.h - FLAC audio file loading (https://github.com/mackron/dr_libs) +* jar_xm.h - XM module file loading +* jar_mod.h - MOD audio file loading * * CONTRIBUTORS: * David Reid (github: @mackron) (Nov. 2017): -* - Complete port to mini_al library +* - Complete port to miniaudio library * * Joshua Reisenauer (github: @kd7tck) (2015) * - XM audio module support (jar_xm) @@ -53,7 +48,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2018 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2019 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. @@ -72,25 +67,30 @@ * **********************************************************************************************/ -#if defined(AUDIO_STANDALONE) - #include "audio.h" +#if defined(RAUDIO_STANDALONE) + #include "raudio.h" #include // Required for: va_list, va_start(), vfprintf(), va_end() #else - #include "config.h" // Defines module configuration flags #include "raylib.h" // Declares module functions + +// Check if config flags have been externally provided on compilation line +#if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags +#endif #include "utils.h" // Required for: fopen() Android mapping #endif -#include "external/mini_al.h" // mini_al audio library - // NOTE: Cannot be implement here because it conflicts with - // Win32 APIs: Rectangle, CloseWindow(), ShowCursor(), PlaySoundA() +#define MA_NO_JACK +#define MINIAUDIO_IMPLEMENTATION +#include "external/miniaudio.h" // miniaudio library +#undef PlaySound // Win32 API: windows.h > mmsystem.h defines PlaySound macro #include // Required for: malloc(), free() #include // Required for: strcmp(), strncmp() #include // Required for: FILE, fopen(), fclose(), fread() #if defined(SUPPORT_FILEFORMAT_OGG) - //#define STB_VORBIS_HEADER_ONLY + #define STB_VORBIS_IMPLEMENTATION #include "external/stb_vorbis.h" // OGG loading functions #endif @@ -112,7 +112,7 @@ #if defined(SUPPORT_FILEFORMAT_MP3) #define DR_MP3_IMPLEMENTATION - #include "external/dr_mp3.h" // MP3 loading functions + #include "external/dr_mp3.h" // MP3 loading functions #endif #if defined(_MSC_VER) @@ -122,59 +122,38 @@ //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -#define MAX_STREAM_BUFFERS 2 // Number of buffers for each audio stream - // NOTE: Music buffer size is defined by number of samples, independent of sample size and channels number -// After some math, considering a sampleRate of 48000, a buffer refill rate of 1/60 seconds -// and double-buffering system, I concluded that a 4096 samples buffer should be enough +// 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 -#define AUDIO_BUFFER_SIZE 4096 // PCM data samples (i.e. 16bit, Mono: 8Kb) +#define AUDIO_BUFFER_SIZE 4096 // PCM data samples (i.e. 16bit, Mono: 8Kb) //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- +// Music context type +// NOTE: Depends on data structure provided by the library +// in charge of reading the different file types typedef enum { - MUSIC_AUDIO_OGG = 0, + MUSIC_AUDIO_WAV = 0, + MUSIC_AUDIO_OGG, MUSIC_AUDIO_FLAC, MUSIC_AUDIO_MP3, MUSIC_MODULE_XM, MUSIC_MODULE_MOD } MusicContextType; -// Music type (file streaming from memory) -typedef struct MusicData { - MusicContextType ctxType; // Type of music context (OGG, XM, MOD) -#if defined(SUPPORT_FILEFORMAT_OGG) - stb_vorbis *ctxOgg; // OGG audio context -#endif -#if defined(SUPPORT_FILEFORMAT_FLAC) - drflac *ctxFlac; // FLAC audio context -#endif -#if defined(SUPPORT_FILEFORMAT_MP3) - drmp3 ctxMp3; // MP3 audio context -#endif -#if defined(SUPPORT_FILEFORMAT_XM) - jar_xm_context_t *ctxXm; // XM chiptune context -#endif -#if defined(SUPPORT_FILEFORMAT_MOD) - jar_mod_context_t ctxMod; // MOD chiptune context -#endif - - AudioStream stream; // Audio stream (double buffering) - - int loopCount; // Loops count (times music repeats), -1 means infinite loop - unsigned int totalSamples; // Total number of samples - unsigned int samplesLeft; // Number of samples left to end -} MusicData; - -#if defined(AUDIO_STANDALONE) +#if defined(RAUDIO_STANDALONE) typedef enum { - LOG_INFO = 0, - LOG_ERROR, - LOG_WARNING, + LOG_ALL, + LOG_TRACE, LOG_DEBUG, - LOG_OTHER + LOG_INFO, + LOG_WARNING, + LOG_ERROR, + LOG_FATAL, + LOG_NONE } TraceLogType; #endif @@ -191,109 +170,129 @@ static Wave LoadWAV(const char *fileName); // 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 char *fileName); // Load OGG file #endif #if defined(SUPPORT_FILEFORMAT_FLAC) -static Wave LoadFLAC(const char *fileName); // Load FLAC file +static Wave LoadFLAC(const char *fileName); // Load FLAC file #endif #if defined(SUPPORT_FILEFORMAT_MP3) -static Wave LoadMP3(const char *fileName); // Load MP3 file +static Wave LoadMP3(const char *fileName); // Load MP3 file #endif -#if defined(AUDIO_STANDALONE) +#if defined(RAUDIO_STANDALONE) bool IsFileExtension(const char *fileName, const char *ext); // Check file extension void TraceLog(int msgType, const char *text, ...); // Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG) #endif //---------------------------------------------------------------------------------- -// mini_al AudioBuffer Functionality +// AudioBuffer Functionality //---------------------------------------------------------------------------------- -#define DEVICE_FORMAT mal_format_f32 +#define DEVICE_FORMAT ma_format_f32 #define DEVICE_CHANNELS 2 #define DEVICE_SAMPLE_RATE 44100 +#define MAX_AUDIO_BUFFER_POOL_CHANNELS 16 + typedef enum { AUDIO_BUFFER_USAGE_STATIC = 0, AUDIO_BUFFER_USAGE_STREAM } AudioBufferUsage; // Audio buffer structure -// NOTE: Slightly different logic is used when feeding data to the playback device depending on whether or not data is streamed -typedef struct AudioBuffer AudioBuffer; -struct AudioBuffer { - mal_dsp dsp; // Required for format conversion - float volume; - float pitch; - bool playing; - bool paused; - bool looping; // Always true for AudioStreams - int usage; // AudioBufferUsage type - bool isSubBufferProcessed[2]; - unsigned int frameCursorPos; - unsigned int bufferSizeInFrames; - AudioBuffer *next; - AudioBuffer *prev; - unsigned char buffer[1]; +// NOTE: Slightly different logic is used when feeding data to the +// playback device depending on whether or not data is streamed +struct rAudioBuffer { + ma_pcm_converter dsp; // PCM data converter + + float volume; // Audio buffer volume + float pitch; // Audio buffer pitch + + bool playing; // Audio buffer state: AUDIO_PLAYING + bool paused; // Audio buffer state: AUDIO_PAUSED + bool looping; // Audio buffer looping, always true for AudioStreams + int usage; // Audio buffer usage mode: STATIC or STREAM + + bool isSubBufferProcessed[2]; // SubBuffer processed (virtual double buffer) + unsigned int frameCursorPos; // Frame cursor position + unsigned int bufferSizeInFrames; // Total buffer size in frames + unsigned int totalFramesProcessed; // Total frames processed in this buffer (required for play timming) + + unsigned char *buffer; // Data buffer, on music stream keeps filling + + rAudioBuffer *next; // Next audio buffer on the list + rAudioBuffer *prev; // Previous audio buffer on the list }; -// mini_al global variables -static mal_context context; -static mal_device device; -static mal_mutex audioLock; -static bool isAudioInitialized = MAL_FALSE; -static float masterVolume = 1.0f; +#define AudioBuffer rAudioBuffer // HACK: To avoid CoreAudio (macOS) symbol collision // Audio buffers are tracked in a linked list static AudioBuffer *firstAudioBuffer = NULL; static AudioBuffer *lastAudioBuffer = NULL; -// mini_al functions declaration -static void OnLog(mal_context *pContext, mal_device *pDevice, const char *message); -static mal_uint32 OnSendAudioDataToDevice(mal_device *pDevice, mal_uint32 frameCount, void *pFramesOut); -static mal_uint32 OnAudioBufferDSPRead(mal_dsp *pDSP, mal_uint32 frameCount, void *pFramesOut, void *pUserData); -static void MixAudioFrames(float *framesOut, const float *framesIn, mal_uint32 frameCount, float localVolume); +// miniaudio global variables +static ma_context context; +static ma_device device; +static ma_mutex audioLock; +static bool isAudioInitialized = false; +static float masterVolume = 1.0f; + +// Multi channel playback global variables +AudioBuffer *audioBufferPool[MAX_AUDIO_BUFFER_POOL_CHANNELS] = { 0 }; +unsigned int audioBufferPoolCounter = 0; +unsigned int audioBufferPoolChannels[MAX_AUDIO_BUFFER_POOL_CHANNELS] = { 0 }; + +// miniaudio functions declaration +static void OnLog(ma_context *pContext, ma_device *pDevice, ma_uint32 logLevel, const char *message); +static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount); +static ma_uint32 OnAudioBufferDSPRead(ma_pcm_converter *pDSP, void *pFramesOut, ma_uint32 frameCount, void *pUserData); +static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, float localVolume); // AudioBuffer management functions declaration // NOTE: Those functions are not exposed by raylib... for the moment -AudioBuffer *CreateAudioBuffer(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 bufferSizeInFrames, AudioBufferUsage usage); -void DeleteAudioBuffer(AudioBuffer *audioBuffer); -bool IsAudioBufferPlaying(AudioBuffer *audioBuffer); -void PlayAudioBuffer(AudioBuffer *audioBuffer); -void StopAudioBuffer(AudioBuffer *audioBuffer); -void PauseAudioBuffer(AudioBuffer *audioBuffer); -void ResumeAudioBuffer(AudioBuffer *audioBuffer); -void SetAudioBufferVolume(AudioBuffer *audioBuffer, float volume); -void SetAudioBufferPitch(AudioBuffer *audioBuffer, float pitch); -void TrackAudioBuffer(AudioBuffer *audioBuffer); -void UntrackAudioBuffer(AudioBuffer *audioBuffer); +AudioBuffer *InitAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 bufferSizeInFrames, int usage); +void CloseAudioBuffer(AudioBuffer *buffer); +bool IsAudioBufferPlaying(AudioBuffer *buffer); +void PlayAudioBuffer(AudioBuffer *buffer); +void StopAudioBuffer(AudioBuffer *buffer); +void PauseAudioBuffer(AudioBuffer *buffer); +void ResumeAudioBuffer(AudioBuffer *buffer); +void SetAudioBufferVolume(AudioBuffer *buffer, float volume); +void SetAudioBufferPitch(AudioBuffer *buffer, float pitch); +void TrackAudioBuffer(AudioBuffer *buffer); +void UntrackAudioBuffer(AudioBuffer *buffer); +//---------------------------------------------------------------------------------- +// miniaudio functions definitions +//---------------------------------------------------------------------------------- + // Log callback function -static void OnLog(mal_context *pContext, mal_device *pDevice, const char *message) +static void OnLog(ma_context *pContext, ma_device *pDevice, ma_uint32 logLevel, const char *message) { (void)pContext; (void)pDevice; - TraceLog(LOG_ERROR, message); // All log messages from mini_al are errors + TraceLog(LOG_ERROR, message); // All log messages from miniaudio are errors } // Sending audio data to device callback function -static mal_uint32 OnSendAudioDataToDevice(mal_device *pDevice, mal_uint32 frameCount, void *pFramesOut) +// NOTE: All the mixing takes place here +static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount) { - // This is where all of the mixing takes place. (void)pDevice; - // Mixing is basically just an accumulation. We need to initialize the output buffer to 0. - memset(pFramesOut, 0, frameCount*pDevice->channels*mal_get_bytes_per_sample(pDevice->format)); + // Mixing is basically just an accumulation, we need to initialize the output buffer to 0 + memset(pFramesOut, 0, frameCount*pDevice->playback.channels*ma_get_bytes_per_sample(pDevice->playback.format)); - // Using a mutex here for thread-safety which makes things not real-time. This is unlikely to be necessary for this project, but may - // want to consider how you might want to avoid this. - mal_mutex_lock(&audioLock); + // Using a mutex here for thread-safety which makes things not real-time + // This is unlikely to be necessary for this project, but may want to consider how you might want to avoid this + ma_mutex_lock(&audioLock); { for (AudioBuffer *audioBuffer = firstAudioBuffer; audioBuffer != NULL; audioBuffer = audioBuffer->next) { - // Ignore stopped or paused sounds. + // Ignore stopped or paused sounds if (!audioBuffer->playing || audioBuffer->paused) continue; - mal_uint32 framesRead = 0; - for (;;) + ma_uint32 framesRead = 0; + + while (1) { if (framesRead > frameCount) { @@ -303,30 +302,32 @@ static mal_uint32 OnSendAudioDataToDevice(mal_device *pDevice, mal_uint32 frameC if (framesRead == frameCount) break; - // Just read as much data as we can from the stream. - mal_uint32 framesToRead = (frameCount - framesRead); + // Just read as much data as we can from the stream + ma_uint32 framesToRead = (frameCount - framesRead); + while (framesToRead > 0) { - float tempBuffer[1024]; // 512 frames for stereo. + float tempBuffer[1024]; // 512 frames for stereo - mal_uint32 framesToReadRightNow = framesToRead; + ma_uint32 framesToReadRightNow = framesToRead; if (framesToReadRightNow > sizeof(tempBuffer)/sizeof(tempBuffer[0])/DEVICE_CHANNELS) { framesToReadRightNow = sizeof(tempBuffer)/sizeof(tempBuffer[0])/DEVICE_CHANNELS; } - mal_uint32 framesJustRead = (mal_uint32)mal_dsp_read(&audioBuffer->dsp, framesToReadRightNow, tempBuffer, audioBuffer->dsp.pUserData); + ma_uint32 framesJustRead = (ma_uint32)ma_pcm_converter_read(&audioBuffer->dsp, tempBuffer, framesToReadRightNow); if (framesJustRead > 0) { - float *framesOut = (float *)pFramesOut + (framesRead*device.channels); + float *framesOut = (float *)pFramesOut + (framesRead*device.playback.channels); float *framesIn = tempBuffer; + MixAudioFrames(framesOut, framesIn, framesJustRead, audioBuffer->volume); framesToRead -= framesJustRead; framesRead += framesJustRead; } - // If we weren't able to read all the frames we requested, break. + // If we weren't able to read all the frames we requested, break if (framesJustRead < framesToReadRightNow) { if (!audioBuffer->looping) @@ -337,32 +338,30 @@ static mal_uint32 OnSendAudioDataToDevice(mal_device *pDevice, mal_uint32 frameC else { // Should never get here, but just for safety, - // move the cursor position back to the start and continue the loop. + // move the cursor position back to the start and continue the loop audioBuffer->frameCursorPos = 0; continue; } } } - // If for some reason we weren't able to read every frame we'll need to break from the loop. - // Not doing this could theoretically put us into an infinite loop. + // If for some reason we weren't able to read every frame we'll need to break from the loop + // Not doing this could theoretically put us into an infinite loop if (framesToRead > 0) break; } } } - mal_mutex_unlock(&audioLock); - - return frameCount; // We always output the same number of frames that were originally requested. + ma_mutex_unlock(&audioLock); } // DSP read from audio buffer callback function -static mal_uint32 OnAudioBufferDSPRead(mal_dsp *pDSP, mal_uint32 frameCount, void *pFramesOut, void *pUserData) +static ma_uint32 OnAudioBufferDSPRead(ma_pcm_converter *pDSP, void *pFramesOut, ma_uint32 frameCount, void *pUserData) { AudioBuffer *audioBuffer = (AudioBuffer *)pUserData; - mal_uint32 subBufferSizeInFrames = audioBuffer->bufferSizeInFrames/2; - mal_uint32 currentSubBufferIndex = audioBuffer->frameCursorPos/subBufferSizeInFrames; + ma_uint32 subBufferSizeInFrames = (audioBuffer->bufferSizeInFrames > 1)? audioBuffer->bufferSizeInFrames/2 : audioBuffer->bufferSizeInFrames; + ma_uint32 currentSubBufferIndex = audioBuffer->frameCursorPos/subBufferSizeInFrames; if (currentSubBufferIndex > 1) { @@ -370,19 +369,22 @@ static mal_uint32 OnAudioBufferDSPRead(mal_dsp *pDSP, mal_uint32 frameCount, voi return 0; } - // Another thread can update the processed state of buffers so we just take a copy here to try and avoid potential synchronization problems. + // Another thread can update the processed state of buffers so + // we just take a copy here to try and avoid potential synchronization problems bool isSubBufferProcessed[2]; isSubBufferProcessed[0] = audioBuffer->isSubBufferProcessed[0]; isSubBufferProcessed[1] = audioBuffer->isSubBufferProcessed[1]; - mal_uint32 frameSizeInBytes = mal_get_bytes_per_sample(audioBuffer->dsp.formatConverterIn.config.formatIn)*audioBuffer->dsp.formatConverterIn.config.channels; + ma_uint32 frameSizeInBytes = ma_get_bytes_per_sample(audioBuffer->dsp.formatConverterIn.config.formatIn)*audioBuffer->dsp.formatConverterIn.config.channels; - // Fill out every frame until we find a buffer that's marked as processed. Then fill the remainder with 0. - mal_uint32 framesRead = 0; - for (;;) + // Fill out every frame until we find a buffer that's marked as processed. Then fill the remainder with 0 + ma_uint32 framesRead = 0; + while (1) { - // We break from this loop differently depending on the buffer's usage. For static buffers, we simply fill as much data as we can. For - // streaming buffers we only fill the halves of the buffer that are processed. Unprocessed halves must keep their audio data in-tact. + // We break from this loop differently depending on the buffer's usage + // - For static buffers, we simply fill as much data as we can + // - For streaming buffers we only fill the halves of the buffer that are processed + // Unprocessed halves must keep their audio data in-tact if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC) { if (framesRead >= frameCount) break; @@ -392,28 +394,28 @@ static mal_uint32 OnAudioBufferDSPRead(mal_dsp *pDSP, mal_uint32 frameCount, voi if (isSubBufferProcessed[currentSubBufferIndex]) break; } - mal_uint32 totalFramesRemaining = (frameCount - framesRead); + ma_uint32 totalFramesRemaining = (frameCount - framesRead); if (totalFramesRemaining == 0) break; - mal_uint32 framesRemainingInOutputBuffer; + ma_uint32 framesRemainingInOutputBuffer; if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC) { framesRemainingInOutputBuffer = audioBuffer->bufferSizeInFrames - audioBuffer->frameCursorPos; } else { - mal_uint32 firstFrameIndexOfThisSubBuffer = subBufferSizeInFrames * currentSubBufferIndex; + ma_uint32 firstFrameIndexOfThisSubBuffer = subBufferSizeInFrames*currentSubBufferIndex; framesRemainingInOutputBuffer = subBufferSizeInFrames - (audioBuffer->frameCursorPos - firstFrameIndexOfThisSubBuffer); } - mal_uint32 framesToRead = totalFramesRemaining; + ma_uint32 framesToRead = totalFramesRemaining; if (framesToRead > framesRemainingInOutputBuffer) framesToRead = framesRemainingInOutputBuffer; memcpy((unsigned char *)pFramesOut + (framesRead*frameSizeInBytes), audioBuffer->buffer + (audioBuffer->frameCursorPos*frameSizeInBytes), framesToRead*frameSizeInBytes); - audioBuffer->frameCursorPos = (audioBuffer->frameCursorPos + framesToRead) % audioBuffer->bufferSizeInFrames; + audioBuffer->frameCursorPos = (audioBuffer->frameCursorPos + framesToRead)%audioBuffer->bufferSizeInFrames; framesRead += framesToRead; - // If we've read to the end of the buffer, mark it as processed. + // If we've read to the end of the buffer, mark it as processed if (framesToRead == framesRemainingInOutputBuffer) { audioBuffer->isSubBufferProcessed[currentSubBufferIndex] = true; @@ -421,7 +423,7 @@ static mal_uint32 OnAudioBufferDSPRead(mal_dsp *pDSP, mal_uint32 frameCount, voi currentSubBufferIndex = (currentSubBufferIndex + 1)%2; - // We need to break from this loop if we're not looping. + // We need to break from this loop if we're not looping if (!audioBuffer->looping) { StopAudioBuffer(audioBuffer); @@ -430,8 +432,8 @@ static mal_uint32 OnAudioBufferDSPRead(mal_dsp *pDSP, mal_uint32 frameCount, voi } } - // Zero-fill excess. - mal_uint32 totalFramesRemaining = (frameCount - framesRead); + // Zero-fill excess + ma_uint32 totalFramesRemaining = (frameCount - framesRead); if (totalFramesRemaining > 0) { memset((unsigned char *)pFramesOut + (framesRead*frameSizeInBytes), 0, totalFramesRemaining*frameSizeInBytes); @@ -447,91 +449,126 @@ static mal_uint32 OnAudioBufferDSPRead(mal_dsp *pDSP, mal_uint32 frameCount, voi // This is the main mixing function. Mixing is pretty simple in this project - it's just an accumulation. // NOTE: framesOut is both an input and an output. It will be initially filled with zeros outside of this function. -static void MixAudioFrames(float *framesOut, const float *framesIn, mal_uint32 frameCount, float localVolume) +static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, float localVolume) { - for (mal_uint32 iFrame = 0; iFrame < frameCount; ++iFrame) + for (ma_uint32 iFrame = 0; iFrame < frameCount; ++iFrame) { - for (mal_uint32 iChannel = 0; iChannel < device.channels; ++iChannel) + for (ma_uint32 iChannel = 0; iChannel < device.playback.channels; ++iChannel) { - float *frameOut = framesOut + (iFrame*device.channels); - const float *frameIn = framesIn + (iFrame*device.channels); + float *frameOut = framesOut + (iFrame*device.playback.channels); + const float *frameIn = framesIn + (iFrame*device.playback.channels); - frameOut[iChannel] += frameIn[iChannel]*masterVolume*localVolume; + frameOut[iChannel] += (frameIn[iChannel]*masterVolume*localVolume); } } } +// Initialise the multichannel buffer pool +static void InitAudioBufferPool() +{ + // Dummy buffers + for (int i = 0; i < MAX_AUDIO_BUFFER_POOL_CHANNELS; i++) + { + audioBufferPool[i] = InitAudioBuffer(DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, 0, AUDIO_BUFFER_USAGE_STATIC); + } +} + +// Close the audio buffers pool +static void CloseAudioBufferPool() +{ + for (int i = 0; i < MAX_AUDIO_BUFFER_POOL_CHANNELS; i++) + { + RL_FREE(audioBufferPool[i]->buffer); + RL_FREE(audioBufferPool[i]); + } +} + //---------------------------------------------------------------------------------- // Module Functions Definition - Audio Device initialization and Closing //---------------------------------------------------------------------------------- // Initialize audio device void InitAudioDevice(void) { - // Context. - mal_context_config contextConfig = mal_context_config_init(OnLog); - mal_result result = mal_context_init(NULL, 0, &contextConfig, &context); - if (result != MAL_SUCCESS) + // Init audio context + ma_context_config contextConfig = ma_context_config_init(); + contextConfig.logCallback = OnLog; + + ma_result result = ma_context_init(NULL, 0, &contextConfig, &context); + if (result != MA_SUCCESS) { TraceLog(LOG_ERROR, "Failed to initialize audio context"); return; } - // Device. Using the default device. Format is floating point because it simplifies mixing. - mal_device_config deviceConfig = mal_device_config_init(DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, NULL, OnSendAudioDataToDevice); + // Init audio device + // NOTE: Using the default device. Format is floating point because it simplifies mixing. + ma_device_config config = ma_device_config_init(ma_device_type_playback); + config.playback.pDeviceID = NULL; // NULL for the default playback device. + config.playback.format = DEVICE_FORMAT; + config.playback.channels = DEVICE_CHANNELS; + config.capture.pDeviceID = NULL; // NULL for the default capture device. + config.capture.format = ma_format_s16; + config.capture.channels = 1; + config.sampleRate = DEVICE_SAMPLE_RATE; + config.dataCallback = OnSendAudioDataToDevice; + config.pUserData = NULL; - result = mal_device_init(&context, mal_device_type_playback, NULL, &deviceConfig, NULL, &device); - if (result != MAL_SUCCESS) + result = ma_device_init(&context, &config, &device); + if (result != MA_SUCCESS) { TraceLog(LOG_ERROR, "Failed to initialize audio playback device"); - mal_context_uninit(&context); + ma_context_uninit(&context); return; } // Keep the device running the whole time. May want to consider doing something a bit smarter and only have the device running // while there's at least one sound being played. - result = mal_device_start(&device); - if (result != MAL_SUCCESS) + result = ma_device_start(&device); + if (result != MA_SUCCESS) { TraceLog(LOG_ERROR, "Failed to start audio playback device"); - mal_device_uninit(&device); - mal_context_uninit(&context); + ma_device_uninit(&device); + ma_context_uninit(&context); return; } // Mixing happens on a seperate thread which means we need to synchronize. I'm using a mutex here to make things simple, but may // want to look at something a bit smarter later on to keep everything real-time, if that's necessary. - if (mal_mutex_init(&context, &audioLock) != MAL_SUCCESS) + if (ma_mutex_init(&context, &audioLock) != MA_SUCCESS) { TraceLog(LOG_ERROR, "Failed to create mutex for audio mixing"); - mal_device_uninit(&device); - mal_context_uninit(&context); + ma_device_uninit(&device); + ma_context_uninit(&context); return; } - TraceLog(LOG_INFO, "Audio device initialized successfully: %s", device.name); - TraceLog(LOG_INFO, "Audio backend: mini_al / %s", mal_get_backend_name(context.backend)); - TraceLog(LOG_INFO, "Audio format: %s -> %s", mal_get_format_name(device.format), mal_get_format_name(device.internalFormat)); - TraceLog(LOG_INFO, "Audio channels: %d -> %d", device.channels, device.internalChannels); - TraceLog(LOG_INFO, "Audio sample rate: %d -> %d", device.sampleRate, device.internalSampleRate); - TraceLog(LOG_INFO, "Audio buffer size: %d", device.bufferSizeInFrames); + TraceLog(LOG_INFO, "Audio device initialized successfully"); + TraceLog(LOG_INFO, "Audio backend: miniaudio / %s", ma_get_backend_name(context.backend)); + TraceLog(LOG_INFO, "Audio format: %s -> %s", ma_get_format_name(device.playback.format), ma_get_format_name(device.playback.internalFormat)); + TraceLog(LOG_INFO, "Audio channels: %d -> %d", device.playback.channels, device.playback.internalChannels); + TraceLog(LOG_INFO, "Audio sample rate: %d -> %d", device.sampleRate, device.playback.internalSampleRate); + TraceLog(LOG_INFO, "Audio buffer size: %d", device.playback.internalBufferSizeInFrames); - isAudioInitialized = MAL_TRUE; + InitAudioBufferPool(); + TraceLog(LOG_INFO, "Audio multichannel pool size: %i", MAX_AUDIO_BUFFER_POOL_CHANNELS); + + isAudioInitialized = true; } // Close the audio device for all contexts void CloseAudioDevice(void) { - if (!isAudioInitialized) + if (isAudioInitialized) { - TraceLog(LOG_WARNING, "Could not close audio device because it is not currently initialized"); - return; + ma_mutex_uninit(&audioLock); + ma_device_uninit(&device); + ma_context_uninit(&context); + + CloseAudioBufferPool(); + + TraceLog(LOG_INFO, "Audio device closed successfully"); } - - mal_mutex_uninit(&audioLock); - mal_device_uninit(&device); - mal_context_uninit(&context); - - TraceLog(LOG_INFO, "Audio device closed successfully"); + else TraceLog(LOG_WARNING, "Could not close audio device because it is not currently initialized"); } // Check if device has been initialized successfully @@ -553,18 +590,21 @@ void SetMasterVolume(float volume) // Module Functions Definition - Audio Buffer management //---------------------------------------------------------------------------------- -// Create a new audio buffer. Initially filled with silence -AudioBuffer *CreateAudioBuffer(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 bufferSizeInFrames, AudioBufferUsage usage) +// Initialize a new audio buffer (filled with silence) +AudioBuffer *InitAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 bufferSizeInFrames, int usage) { - AudioBuffer *audioBuffer = (AudioBuffer *)calloc(sizeof(*audioBuffer) + (bufferSizeInFrames*channels*mal_get_bytes_per_sample(format)), 1); + AudioBuffer *audioBuffer = (AudioBuffer *)RL_CALLOC(1, sizeof(AudioBuffer)); + if (audioBuffer == NULL) { - TraceLog(LOG_ERROR, "CreateAudioBuffer() : Failed to allocate memory for audio buffer"); + TraceLog(LOG_ERROR, "InitAudioBuffer() : Failed to allocate memory for audio buffer"); return NULL; } + + audioBuffer->buffer = RL_CALLOC(bufferSizeInFrames*channels*ma_get_bytes_per_sample(format), 1); - // We run audio data through a format converter. - mal_dsp_config dspConfig; + // Audio data runs through a format converter + ma_pcm_converter_config dspConfig; memset(&dspConfig, 0, sizeof(dspConfig)); dspConfig.formatIn = format; dspConfig.formatOut = DEVICE_FORMAT; @@ -572,184 +612,167 @@ AudioBuffer *CreateAudioBuffer(mal_format format, mal_uint32 channels, mal_uint3 dspConfig.channelsOut = DEVICE_CHANNELS; dspConfig.sampleRateIn = sampleRate; dspConfig.sampleRateOut = DEVICE_SAMPLE_RATE; - dspConfig.onRead = OnAudioBufferDSPRead; - dspConfig.pUserData = audioBuffer; - dspConfig.allowDynamicSampleRate = MAL_TRUE; // <-- Required for pitch shifting. - mal_result resultMAL = mal_dsp_init(&dspConfig, &audioBuffer->dsp); - if (resultMAL != MAL_SUCCESS) + dspConfig.onRead = OnAudioBufferDSPRead; // Callback on data reading + dspConfig.pUserData = audioBuffer; // Audio data pointer + dspConfig.allowDynamicSampleRate = true; // Required for pitch shifting + + ma_result result = ma_pcm_converter_init(&dspConfig, &audioBuffer->dsp); + + if (result != MA_SUCCESS) { - TraceLog(LOG_ERROR, "CreateAudioBuffer() : Failed to create data conversion pipeline"); - free(audioBuffer); + TraceLog(LOG_ERROR, "InitAudioBuffer() : Failed to create data conversion pipeline"); + RL_FREE(audioBuffer); return NULL; } - audioBuffer->volume = 1; - audioBuffer->pitch = 1; - audioBuffer->playing = 0; - audioBuffer->paused = 0; - audioBuffer->looping = 0; + // Init audio buffer values + audioBuffer->volume = 1.0f; + audioBuffer->pitch = 1.0f; + audioBuffer->playing = false; + audioBuffer->paused = false; + audioBuffer->looping = false; audioBuffer->usage = usage; - audioBuffer->bufferSizeInFrames = bufferSizeInFrames; audioBuffer->frameCursorPos = 0; + audioBuffer->bufferSizeInFrames = bufferSizeInFrames; - // Buffers should be marked as processed by default so that a call to UpdateAudioStream() immediately after initialization works correctly. + // Buffers should be marked as processed by default so that a call to + // UpdateAudioStream() immediately after initialization works correctly audioBuffer->isSubBufferProcessed[0] = true; audioBuffer->isSubBufferProcessed[1] = true; + // Track audio buffer to linked list next position TrackAudioBuffer(audioBuffer); return audioBuffer; } // Delete an audio buffer -void DeleteAudioBuffer(AudioBuffer *audioBuffer) +void CloseAudioBuffer(AudioBuffer *buffer) { - if (audioBuffer == NULL) + if (buffer != NULL) { - TraceLog(LOG_ERROR, "DeleteAudioBuffer() : No audio buffer"); - return; + UntrackAudioBuffer(buffer); + RL_FREE(buffer->buffer); + RL_FREE(buffer); } - - UntrackAudioBuffer(audioBuffer); - free(audioBuffer); + else TraceLog(LOG_ERROR, "CloseAudioBuffer() : No audio buffer"); } // Check if an audio buffer is playing -bool IsAudioBufferPlaying(AudioBuffer *audioBuffer) +bool IsAudioBufferPlaying(AudioBuffer *buffer) { - if (audioBuffer == NULL) - { - TraceLog(LOG_ERROR, "IsAudioBufferPlaying() : No audio buffer"); - return false; - } + bool result = false; - return audioBuffer->playing && !audioBuffer->paused; + if (buffer != NULL) result = (buffer->playing && !buffer->paused); + else TraceLog(LOG_ERROR, "IsAudioBufferPlaying() : No audio buffer"); + + return result; } // Play an audio buffer // NOTE: Buffer is restarted to the start. // Use PauseAudioBuffer() and ResumeAudioBuffer() if the playback position should be maintained. -void PlayAudioBuffer(AudioBuffer *audioBuffer) +void PlayAudioBuffer(AudioBuffer *buffer) { - if (audioBuffer == NULL) + if (buffer != NULL) { - TraceLog(LOG_ERROR, "PlayAudioBuffer() : No audio buffer"); - return; + buffer->playing = true; + buffer->paused = false; + buffer->frameCursorPos = 0; } - - audioBuffer->playing = true; - audioBuffer->paused = false; - audioBuffer->frameCursorPos = 0; + else TraceLog(LOG_ERROR, "PlayAudioBuffer() : No audio buffer"); } // Stop an audio buffer -void StopAudioBuffer(AudioBuffer *audioBuffer) +void StopAudioBuffer(AudioBuffer *buffer) { - if (audioBuffer == NULL) + if (buffer != NULL) { - TraceLog(LOG_ERROR, "StopAudioBuffer() : No audio buffer"); - return; + if (IsAudioBufferPlaying(buffer)) + { + buffer->playing = false; + buffer->paused = false; + buffer->frameCursorPos = 0; + buffer->totalFramesProcessed = 0; + buffer->isSubBufferProcessed[0] = true; + buffer->isSubBufferProcessed[1] = true; + } } - - // Don't do anything if the audio buffer is already stopped. - if (!IsAudioBufferPlaying(audioBuffer)) return; - - audioBuffer->playing = false; - audioBuffer->paused = false; - audioBuffer->frameCursorPos = 0; - audioBuffer->isSubBufferProcessed[0] = true; - audioBuffer->isSubBufferProcessed[1] = true; + else TraceLog(LOG_ERROR, "StopAudioBuffer() : No audio buffer"); } // Pause an audio buffer -void PauseAudioBuffer(AudioBuffer *audioBuffer) +void PauseAudioBuffer(AudioBuffer *buffer) { - if (audioBuffer == NULL) - { - TraceLog(LOG_ERROR, "PauseAudioBuffer() : No audio buffer"); - return; - } - - audioBuffer->paused = true; + if (buffer != NULL) buffer->paused = true; + else TraceLog(LOG_ERROR, "PauseAudioBuffer() : No audio buffer"); } // Resume an audio buffer -void ResumeAudioBuffer(AudioBuffer *audioBuffer) +void ResumeAudioBuffer(AudioBuffer *buffer) { - if (audioBuffer == NULL) - { - TraceLog(LOG_ERROR, "ResumeAudioBuffer() : No audio buffer"); - return; - } - - audioBuffer->paused = false; + if (buffer != NULL) buffer->paused = false; + else TraceLog(LOG_ERROR, "ResumeAudioBuffer() : No audio buffer"); } // Set volume for an audio buffer -void SetAudioBufferVolume(AudioBuffer *audioBuffer, float volume) +void SetAudioBufferVolume(AudioBuffer *buffer, float volume) { - if (audioBuffer == NULL) - { - TraceLog(LOG_ERROR, "SetAudioBufferVolume() : No audio buffer"); - return; - } - - audioBuffer->volume = volume; + if (buffer != NULL) buffer->volume = volume; + else TraceLog(LOG_WARNING, "SetAudioBufferVolume() : No audio buffer"); } // Set pitch for an audio buffer -void SetAudioBufferPitch(AudioBuffer *audioBuffer, float pitch) +void SetAudioBufferPitch(AudioBuffer *buffer, float pitch) { - if (audioBuffer == NULL) + if (buffer != NULL) { - TraceLog(LOG_ERROR, "SetAudioBufferPitch() : No audio buffer"); - return; + 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->dsp.src.config.sampleRateOut/pitchMul); + buffer->pitch *= (float)buffer->dsp.src.config.sampleRateOut/newOutputSampleRate; + + ma_pcm_converter_set_output_sample_rate(&buffer->dsp, newOutputSampleRate); } - - audioBuffer->pitch = pitch; - - // Pitching is just an adjustment of the sample rate. Note that this changes the duration of the sound - higher pitches - // will make the sound faster; lower pitches make it slower. - mal_uint32 newOutputSampleRate = (mal_uint32)((((float)audioBuffer->dsp.src.config.sampleRateOut / (float)audioBuffer->dsp.src.config.sampleRateIn) / pitch) * audioBuffer->dsp.src.config.sampleRateIn); - mal_dsp_set_output_sample_rate(&audioBuffer->dsp, newOutputSampleRate); + else TraceLog(LOG_WARNING, "SetAudioBufferPitch() : No audio buffer"); } // Track audio buffer to linked list next position -void TrackAudioBuffer(AudioBuffer *audioBuffer) +void TrackAudioBuffer(AudioBuffer *buffer) { - mal_mutex_lock(&audioLock); - + ma_mutex_lock(&audioLock); { - if (firstAudioBuffer == NULL) firstAudioBuffer = audioBuffer; + if (firstAudioBuffer == NULL) firstAudioBuffer = buffer; else { - lastAudioBuffer->next = audioBuffer; - audioBuffer->prev = lastAudioBuffer; + lastAudioBuffer->next = buffer; + buffer->prev = lastAudioBuffer; } - lastAudioBuffer = audioBuffer; + lastAudioBuffer = buffer; } - - mal_mutex_unlock(&audioLock); + ma_mutex_unlock(&audioLock); } // Untrack audio buffer from linked list -void UntrackAudioBuffer(AudioBuffer *audioBuffer) +void UntrackAudioBuffer(AudioBuffer *buffer) { - mal_mutex_lock(&audioLock); - + ma_mutex_lock(&audioLock); { - if (audioBuffer->prev == NULL) firstAudioBuffer = audioBuffer->next; - else audioBuffer->prev->next = audioBuffer->next; + if (buffer->prev == NULL) firstAudioBuffer = buffer->next; + else buffer->prev->next = buffer->next; - if (audioBuffer->next == NULL) lastAudioBuffer = audioBuffer->prev; - else audioBuffer->next->prev = audioBuffer->prev; + if (buffer->next == NULL) lastAudioBuffer = buffer->prev; + else buffer->next->prev = buffer->prev; - audioBuffer->prev = NULL; - audioBuffer->next = NULL; + buffer->prev = NULL; + buffer->next = NULL; } - - mal_mutex_unlock(&audioLock); + ma_mutex_unlock(&audioLock); } //---------------------------------------------------------------------------------- @@ -761,7 +784,10 @@ Wave LoadWave(const char *fileName) { Wave wave = { 0 }; - if (IsFileExtension(fileName, ".wav")) wave = LoadWAV(fileName); + if (false) { } +#if defined(SUPPORT_FILEFORMAT_WAV) + else if (IsFileExtension(fileName, ".wav")) wave = LoadWAV(fileName); +#endif #if defined(SUPPORT_FILEFORMAT_OGG) else if (IsFileExtension(fileName, ".ogg")) wave = LoadOGG(fileName); #endif @@ -776,25 +802,6 @@ Wave LoadWave(const char *fileName) return wave; } -// Load wave data from raw array data -Wave LoadWaveEx(void *data, int sampleCount, int sampleRate, int sampleSize, int channels) -{ - Wave wave; - - wave.data = data; - wave.sampleCount = sampleCount; - wave.sampleRate = sampleRate; - wave.sampleSize = sampleSize; - wave.channels = channels; - - // NOTE: Copy wave data to work with, user is responsible of input data to free - Wave cwave = WaveCopy(wave); - - WaveFormat(&cwave, sampleRate, sampleSize, channels); - - return cwave; -} - // Load sound from file // NOTE: The entire file is loaded to memory to be played (no-streaming) Sound LoadSound(const char *fileName) @@ -816,27 +823,32 @@ Sound LoadSoundFromWave(Wave wave) if (wave.data != NULL) { - // When using mini_al we need to do our own mixing. To simplify this we need convert the format of each sound to be consistent with + // When using miniaudio we need to do our own mixing. + // To simplify this we need convert the format of each sound to be consistent with // the format used to open the playback device. We can do this two ways: // // 1) Convert the whole sound in one go at load time (here). // 2) Convert the audio data in chunks at mixing time. // - // I have decided on the first option because it offloads work required for the format conversion to the to the loading stage. - // The downside to this is that it uses more memory if the original sound is u8 or s16. - mal_format formatIn = ((wave.sampleSize == 8) ? mal_format_u8 : ((wave.sampleSize == 16) ? mal_format_s16 : mal_format_f32)); - mal_uint32 frameCountIn = wave.sampleCount/wave.channels; + // First option has been selected, format conversion is done on the loading stage. + // The downside is that it uses more memory if the original sound is u8 or s16. + 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; - mal_uint32 frameCount = (mal_uint32)mal_convert_frames(NULL, DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, NULL, formatIn, wave.channels, wave.sampleRate, frameCountIn); + ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, NULL, formatIn, wave.channels, wave.sampleRate, frameCountIn); if (frameCount == 0) TraceLog(LOG_WARNING, "LoadSoundFromWave() : Failed to get frame count for format conversion"); - AudioBuffer* audioBuffer = CreateAudioBuffer(DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, frameCount, AUDIO_BUFFER_USAGE_STATIC); + AudioBuffer *audioBuffer = InitAudioBuffer(DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, frameCount, AUDIO_BUFFER_USAGE_STATIC); if (audioBuffer == NULL) TraceLog(LOG_WARNING, "LoadSoundFromWave() : Failed to create audio buffer"); - frameCount = (mal_uint32)mal_convert_frames(audioBuffer->buffer, audioBuffer->dsp.formatConverterIn.config.formatIn, audioBuffer->dsp.formatConverterIn.config.channels, audioBuffer->dsp.src.config.sampleRateIn, wave.data, formatIn, wave.channels, wave.sampleRate, frameCountIn); + frameCount = (ma_uint32)ma_convert_frames(audioBuffer->buffer, audioBuffer->dsp.formatConverterIn.config.formatIn, audioBuffer->dsp.formatConverterIn.config.channels, audioBuffer->dsp.src.config.sampleRateIn, wave.data, formatIn, wave.channels, wave.sampleRate, frameCountIn); if (frameCount == 0) TraceLog(LOG_WARNING, "LoadSoundFromWave() : Format conversion failed"); - sound.audioBuffer = audioBuffer; + sound.sampleCount = frameCount*DEVICE_CHANNELS; + sound.stream.sampleRate = DEVICE_SAMPLE_RATE; + sound.stream.sampleSize = 32; + sound.stream.channels = DEVICE_CHANNELS; + sound.stream.buffer = audioBuffer; } return sound; @@ -845,7 +857,7 @@ Sound LoadSoundFromWave(Wave wave) // Unload wave data void UnloadWave(Wave wave) { - if (wave.data != NULL) free(wave.data); + if (wave.data != NULL) RL_FREE(wave.data); TraceLog(LOG_INFO, "Unloaded wave data from RAM"); } @@ -853,27 +865,24 @@ void UnloadWave(Wave wave) // Unload sound void UnloadSound(Sound sound) { - DeleteAudioBuffer((AudioBuffer *)sound.audioBuffer); + CloseAudioBuffer(sound.stream.buffer); - TraceLog(LOG_INFO, "[SND ID %i][BUFR ID %i] Unloaded sound data from RAM", sound.source, sound.buffer); + TraceLog(LOG_INFO, "Unloaded sound data from RAM"); } // Update sound buffer with new data -// NOTE: data must match sound.format void UpdateSound(Sound sound, const void *data, int samplesCount) { - AudioBuffer *audioBuffer = (AudioBuffer *)sound.audioBuffer; + AudioBuffer *audioBuffer = sound.stream.buffer; - if (audioBuffer == NULL) + if (audioBuffer != NULL) { - TraceLog(LOG_ERROR, "UpdateSound() : Invalid sound - no audio buffer"); - return; + StopAudioBuffer(audioBuffer); + + // TODO: May want to lock/unlock this since this data buffer is read at mixing time + memcpy(audioBuffer->buffer, data, samplesCount*audioBuffer->dsp.formatConverterIn.config.channels*ma_get_bytes_per_sample(audioBuffer->dsp.formatConverterIn.config.formatIn)); } - - StopAudioBuffer(audioBuffer); - - // TODO: May want to lock/unlock this since this data buffer is read at mixing time. - memcpy(audioBuffer->buffer, data, samplesCount*audioBuffer->dsp.formatConverterIn.config.channels*mal_get_bytes_per_sample(audioBuffer->dsp.formatConverterIn.config.formatIn)); + else TraceLog(LOG_ERROR, "UpdateSound() : Invalid sound - no audio buffer"); } // Export wave data to file @@ -881,7 +890,10 @@ void ExportWave(Wave wave, const char *fileName) { bool success = false; - if (IsFileExtension(fileName, ".wav")) success = SaveWAV(wave, fileName); + if (false) { } +#if defined(SUPPORT_FILEFORMAT_WAV) + else if (IsFileExtension(fileName, ".wav")) success = SaveWAV(wave, fileName); +#endif else if (IsFileExtension(fileName, ".raw")) { // Export raw sample data (without header) @@ -905,95 +917,182 @@ void ExportWaveAsCode(Wave wave, const char *fileName) FILE *txtFile = fopen(fileName, "wt"); - fprintf(txtFile, "\n//////////////////////////////////////////////////////////////////////////////////\n"); - fprintf(txtFile, "// //\n"); - fprintf(txtFile, "// WaveAsCode exporter v1.0 - Wave data exported as an array of bytes //\n"); - fprintf(txtFile, "// //\n"); - fprintf(txtFile, "// more info and bugs-report: github.com/raysan5/raylib //\n"); - fprintf(txtFile, "// feedback and support: ray[at]raylib.com //\n"); - fprintf(txtFile, "// //\n"); - fprintf(txtFile, "// Copyright (c) 2018 Ramon Santamaria (@raysan5) //\n"); - fprintf(txtFile, "// //\n"); - fprintf(txtFile, "//////////////////////////////////////////////////////////////////////////////////\n\n"); + if (txtFile != NULL) + { + fprintf(txtFile, "\n//////////////////////////////////////////////////////////////////////////////////\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "// WaveAsCode exporter v1.0 - Wave data exported as an array of bytes //\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "// more info and bugs-report: github.com/raysan5/raylib //\n"); + fprintf(txtFile, "// feedback and support: ray[at]raylib.com //\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "// Copyright (c) 2018 Ramon Santamaria (@raysan5) //\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "//////////////////////////////////////////////////////////////////////////////////\n\n"); - // Get file name from path and convert variable name to uppercase - strcpy(varFileName, GetFileNameWithoutExt(fileName)); - for (int i = 0; varFileName[i] != '\0'; i++) if (varFileName[i] >= 'a' && varFileName[i] <= 'z') { varFileName[i] = varFileName[i] - 32; } +#if !defined(RAUDIO_STANDALONE) + // Get file name from path and convert variable name to uppercase + strcpy(varFileName, GetFileNameWithoutExt(fileName)); + for (int i = 0; varFileName[i] != '\0'; i++) if (varFileName[i] >= 'a' && varFileName[i] <= 'z') { varFileName[i] = varFileName[i] - 32; } +#else + strcpy(varFileName, fileName); +#endif - fprintf(txtFile, "// Wave data information\n"); - fprintf(txtFile, "#define %s_SAMPLE_COUNT %i\n", varFileName, wave.sampleCount); - fprintf(txtFile, "#define %s_SAMPLE_RATE %i\n", varFileName, wave.sampleRate); - fprintf(txtFile, "#define %s_SAMPLE_SIZE %i\n", varFileName, wave.sampleSize); - fprintf(txtFile, "#define %s_CHANNELS %i\n\n", varFileName, wave.channels); + fprintf(txtFile, "// Wave data information\n"); + fprintf(txtFile, "#define %s_SAMPLE_COUNT %i\n", varFileName, wave.sampleCount); + fprintf(txtFile, "#define %s_SAMPLE_RATE %i\n", varFileName, wave.sampleRate); + fprintf(txtFile, "#define %s_SAMPLE_SIZE %i\n", varFileName, wave.sampleSize); + fprintf(txtFile, "#define %s_CHANNELS %i\n\n", varFileName, wave.channels); - // Write byte data as hexadecimal text - fprintf(txtFile, "static unsigned char %s_DATA[%i] = { ", varFileName, dataSize); - for (int i = 0; i < dataSize - 1; i++) fprintf(txtFile, ((i%BYTES_TEXT_PER_LINE == 0) ? "0x%x,\n" : "0x%x, "), ((unsigned char *)wave.data)[i]); - fprintf(txtFile, "0x%x };\n", ((unsigned char *)wave.data)[dataSize - 1]); + // Write byte data as hexadecimal text + fprintf(txtFile, "static unsigned char %s_DATA[%i] = { ", varFileName, dataSize); + for (int i = 0; i < dataSize - 1; i++) fprintf(txtFile, ((i%BYTES_TEXT_PER_LINE == 0)? "0x%x,\n" : "0x%x, "), ((unsigned char *)wave.data)[i]); + fprintf(txtFile, "0x%x };\n", ((unsigned char *)wave.data)[dataSize - 1]); - fclose(txtFile); + fclose(txtFile); + } } // Play a sound void PlaySound(Sound sound) { - PlayAudioBuffer((AudioBuffer *)sound.audioBuffer); + PlayAudioBuffer(sound.stream.buffer); +} + +// Play a sound in the multichannel buffer pool +void PlaySoundMulti(Sound sound) +{ + int index = -1; + unsigned int oldAge = 0; + int oldIndex = -1; + + // find the first non playing pool entry + for (int i = 0; i < MAX_AUDIO_BUFFER_POOL_CHANNELS; i++) + { + if (audioBufferPoolChannels[i] > oldAge) + { + oldAge = audioBufferPoolChannels[i]; + oldIndex = i; + } + + if (!IsAudioBufferPlaying(audioBufferPool[i])) + { + index = i; + break; + } + } + + // If no none playing pool members can be index choose the oldest + if (index == -1) + { + TraceLog(LOG_WARNING,"pool age %i ended a sound early no room in buffer pool", audioBufferPoolCounter); + + if (oldIndex == -1) + { + // Shouldn't be able to get here... but just in case something odd happens! + TraceLog(LOG_ERROR,"sound buffer pool couldn't determine oldest buffer not playing sound"); + + return; + } + + index = oldIndex; + + // Just in case... + StopAudioBuffer(audioBufferPool[index]); + } + + // Experimentally mutex lock doesn't seem to be needed this makes sense + // as audioBufferPool[index] isn't playing and the only stuff we're copying + // shouldn't be changing... + + audioBufferPoolChannels[index] = audioBufferPoolCounter; + audioBufferPoolCounter++; + + audioBufferPool[index]->volume = sound.stream.buffer->volume; + audioBufferPool[index]->pitch = sound.stream.buffer->pitch; + audioBufferPool[index]->looping = sound.stream.buffer->looping; + audioBufferPool[index]->usage = sound.stream.buffer->usage; + audioBufferPool[index]->isSubBufferProcessed[0] = false; + audioBufferPool[index]->isSubBufferProcessed[1] = false; + audioBufferPool[index]->bufferSizeInFrames = sound.stream.buffer->bufferSizeInFrames; + audioBufferPool[index]->buffer = sound.stream.buffer->buffer; + + PlayAudioBuffer(audioBufferPool[index]); +} + +// Stop any sound played with PlaySoundMulti() +void StopSoundMulti(void) +{ + for (int i = 0; i < MAX_AUDIO_BUFFER_POOL_CHANNELS; i++) StopAudioBuffer(audioBufferPool[i]); +} + +// Get number of sounds playing in the multichannel buffer pool +int GetSoundsPlaying(void) +{ + int counter = 0; + + for (int i = 0; i < MAX_AUDIO_BUFFER_POOL_CHANNELS; i++) + { + if (IsAudioBufferPlaying(audioBufferPool[i])) counter++; + } + + return counter; } // Pause a sound void PauseSound(Sound sound) { - PauseAudioBuffer((AudioBuffer *)sound.audioBuffer); + PauseAudioBuffer(sound.stream.buffer); } // Resume a paused sound void ResumeSound(Sound sound) { - ResumeAudioBuffer((AudioBuffer *)sound.audioBuffer); + ResumeAudioBuffer(sound.stream.buffer); } // Stop reproducing a sound void StopSound(Sound sound) { - StopAudioBuffer((AudioBuffer *)sound.audioBuffer); + StopAudioBuffer(sound.stream.buffer); } // Check if a sound is playing bool IsSoundPlaying(Sound sound) { - return IsAudioBufferPlaying((AudioBuffer *)sound.audioBuffer); + return IsAudioBufferPlaying(sound.stream.buffer); } // Set volume for a sound void SetSoundVolume(Sound sound, float volume) { - SetAudioBufferVolume((AudioBuffer *)sound.audioBuffer, volume); + SetAudioBufferVolume(sound.stream.buffer, volume); } // Set pitch for a sound void SetSoundPitch(Sound sound, float pitch) { - SetAudioBufferPitch((AudioBuffer *)sound.audioBuffer, pitch); + SetAudioBufferPitch(sound.stream.buffer, pitch); } // Convert wave data to desired format void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) { - mal_format formatIn = ((wave->sampleSize == 8) ? mal_format_u8 : ((wave->sampleSize == 16) ? mal_format_s16 : mal_format_f32)); - mal_format formatOut = (( sampleSize == 8) ? mal_format_u8 : (( sampleSize == 16) ? mal_format_s16 : mal_format_f32)); + 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)); - mal_uint32 frameCountIn = wave->sampleCount; // Is wave->sampleCount actually the frame count? That terminology needs to change, if so. + ma_uint32 frameCountIn = wave->sampleCount; // Is wave->sampleCount actually the frame count? That terminology needs to change, if so. - mal_uint32 frameCount = (mal_uint32)mal_convert_frames(NULL, formatOut, channels, sampleRate, NULL, formatIn, wave->channels, wave->sampleRate, frameCountIn); + ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, formatOut, channels, sampleRate, NULL, formatIn, wave->channels, wave->sampleRate, frameCountIn); if (frameCount == 0) { TraceLog(LOG_ERROR, "WaveFormat() : Failed to get frame count for format conversion."); return; } - void *data = malloc(frameCount*channels*(sampleSize/8)); + void *data = RL_MALLOC(frameCount*channels*(sampleSize/8)); - frameCount = (mal_uint32)mal_convert_frames(data, formatOut, channels, sampleRate, wave->data, formatIn, wave->channels, wave->sampleRate, frameCountIn); + frameCount = (ma_uint32)ma_convert_frames(data, formatOut, channels, sampleRate, wave->data, formatIn, wave->channels, wave->sampleRate, frameCountIn); if (frameCount == 0) { TraceLog(LOG_ERROR, "WaveFormat() : Format conversion failed."); @@ -1004,7 +1103,7 @@ void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) wave->sampleSize = sampleSize; wave->sampleRate = sampleRate; wave->channels = channels; - free(wave->data); + RL_FREE(wave->data); wave->data = data; } @@ -1013,7 +1112,7 @@ Wave WaveCopy(Wave wave) { Wave newWave = { 0 }; - newWave.data = malloc(wave.sampleCount*wave.sampleSize/8*wave.channels); + newWave.data = RL_MALLOC(wave.sampleCount*wave.sampleSize/8*wave.channels); if (newWave.data != NULL) { @@ -1038,11 +1137,11 @@ void WaveCrop(Wave *wave, int initSample, int finalSample) { int sampleCount = finalSample - initSample; - void *data = malloc(sampleCount*wave->sampleSize/8*wave->channels); + void *data = RL_MALLOC(sampleCount*wave->sampleSize/8*wave->channels); memcpy(data, (unsigned char *)wave->data + (initSample*wave->channels*wave->sampleSize/8), sampleCount*wave->channels*wave->sampleSize/8); - free(wave->data); + RL_FREE(wave->data); wave->data = data; } else TraceLog(LOG_WARNING, "Wave crop range out of bounds"); @@ -1052,7 +1151,7 @@ void WaveCrop(Wave *wave, int initSample, int finalSample) // NOTE: Returned sample values are normalized to range [-1..1] float *GetWaveData(Wave wave) { - float *samples = (float *)malloc(wave.sampleCount*wave.channels*sizeof(float)); + float *samples = (float *)RL_MALLOC(wave.sampleCount*wave.channels*sizeof(float)); for (unsigned int i = 0; i < wave.sampleCount; i++) { @@ -1074,142 +1173,139 @@ float *GetWaveData(Wave wave) // Load music stream from file Music LoadMusicStream(const char *fileName) { - Music music = (MusicData *)malloc(sizeof(MusicData)); - bool musicLoaded = true; + Music music = { 0 }; + bool musicLoaded = false; - if (IsFileExtension(fileName, ".ogg")) + if (false) { } +#if defined(SUPPORT_FILEFORMAT_OGG) + else if (IsFileExtension(fileName, ".ogg")) { // Open ogg audio stream - music->ctxOgg = stb_vorbis_open_filename(fileName, NULL, NULL); + music.ctxData = stb_vorbis_open_filename(fileName, NULL, NULL); - if (music->ctxOgg == NULL) musicLoaded = false; - else + if (music.ctxData != NULL) { - stb_vorbis_info info = stb_vorbis_get_info(music->ctxOgg); // Get Ogg file info + 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); - music->totalSamples = (unsigned int)stb_vorbis_stream_length_in_samples(music->ctxOgg); // Independent by channel - music->samplesLeft = music->totalSamples; - music->ctxType = MUSIC_AUDIO_OGG; - music->loopCount = -1; // Infinite loop by default - - TraceLog(LOG_DEBUG, "[%s] OGG total samples: %i", fileName, music->totalSamples); - TraceLog(LOG_DEBUG, "[%s] OGG sample rate: %i", fileName, info.sample_rate); - TraceLog(LOG_DEBUG, "[%s] OGG channels: %i", fileName, info.channels); - TraceLog(LOG_DEBUG, "[%s] OGG memory required: %i", fileName, info.temp_memory_required); + music.stream = InitAudioStream(info.sample_rate, 16, info.channels); + music.sampleCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData)*info.channels; + music.loopCount = 0; // Infinite loop by default + musicLoaded = true; } } +#endif #if defined(SUPPORT_FILEFORMAT_FLAC) else if (IsFileExtension(fileName, ".flac")) { - music->ctxFlac = drflac_open_file(fileName); + music.ctxData = drflac_open_file(fileName); - if (music->ctxFlac == NULL) musicLoaded = false; - else + if (music.ctxData != NULL) { - music->stream = InitAudioStream(music->ctxFlac->sampleRate, music->ctxFlac->bitsPerSample, music->ctxFlac->channels); - music->totalSamples = (unsigned int)music->ctxFlac->totalSampleCount/music->ctxFlac->channels; - music->samplesLeft = music->totalSamples; - music->ctxType = MUSIC_AUDIO_FLAC; - music->loopCount = -1; // Infinite loop by default + music.ctxType = MUSIC_AUDIO_FLAC; + drflac *ctxFlac = (drflac *)music.ctxData; - TraceLog(LOG_DEBUG, "[%s] FLAC total samples: %i", fileName, music->totalSamples); - TraceLog(LOG_DEBUG, "[%s] FLAC sample rate: %i", fileName, music->ctxFlac->sampleRate); - TraceLog(LOG_DEBUG, "[%s] FLAC bits per sample: %i", fileName, music->ctxFlac->bitsPerSample); - TraceLog(LOG_DEBUG, "[%s] FLAC channels: %i", fileName, music->ctxFlac->channels); + music.stream = InitAudioStream(ctxFlac->sampleRate, ctxFlac->bitsPerSample, ctxFlac->channels); + music.sampleCount = (unsigned int)ctxFlac->totalSampleCount; + music.loopCount = 0; // Infinite loop by default + musicLoaded = true; } } #endif #if defined(SUPPORT_FILEFORMAT_MP3) else if (IsFileExtension(fileName, ".mp3")) { - int result = drmp3_init_file(&music->ctxMp3, fileName, NULL); + drmp3 *ctxMp3 = RL_MALLOC(sizeof(drmp3)); + music.ctxData = ctxMp3; - if (!result) musicLoaded = false; - else + int result = drmp3_init_file(ctxMp3, fileName, NULL); + + if (result > 0) { - TraceLog(LOG_INFO, "[%s] MP3 sample rate: %i", fileName, music->ctxMp3.sampleRate); - TraceLog(LOG_INFO, "[%s] MP3 bits per sample: %i", fileName, 32); - TraceLog(LOG_INFO, "[%s] MP3 channels: %i", fileName, music->ctxMp3.channels); + music.ctxType = MUSIC_AUDIO_MP3; - music->stream = InitAudioStream(music->ctxMp3.sampleRate, 32, music->ctxMp3.channels); - - // TODO: There is not an easy way to compute the total number of samples available - // in an MP3, frames size could be variable... we tried with a 60 seconds music... but crashes... - music->totalSamples = drmp3_get_pcm_frame_count(&music->ctxMp3)*music->ctxMp3.channels; - music->samplesLeft = music->totalSamples; - music->ctxType = MUSIC_AUDIO_MP3; - music->loopCount = -1; // Infinite loop by default - - TraceLog(LOG_INFO, "[%s] MP3 total samples: %i", fileName, music->totalSamples); + music.stream = InitAudioStream(ctxMp3->sampleRate, 32, ctxMp3->channels); + music.sampleCount = drmp3_get_pcm_frame_count(ctxMp3)*ctxMp3->channels; + music.loopCount = 0; // Infinite loop by default + musicLoaded = true; } } #endif #if defined(SUPPORT_FILEFORMAT_XM) else if (IsFileExtension(fileName, ".xm")) { - int result = jar_xm_create_context_from_file(&music->ctxXm, 48000, fileName); + jar_xm_context_t *ctxXm = NULL; - if (!result) // XM context created successfully + int result = jar_xm_create_context_from_file(&ctxXm, 48000, fileName); + + if (result == 0) // XM context created successfully { - jar_xm_set_max_loop_count(music->ctxXm, 0); // Set infinite number of loops + music.ctxType = MUSIC_MODULE_XM; + jar_xm_set_max_loop_count(ctxXm, 0); // Set infinite number of loops // NOTE: Only stereo is supported for XM - music->stream = InitAudioStream(48000, 16, 2); - music->totalSamples = (unsigned int)jar_xm_get_remaining_samples(music->ctxXm); - music->samplesLeft = music->totalSamples; - music->ctxType = MUSIC_MODULE_XM; - music->loopCount = -1; // Infinite loop by default + music.stream = InitAudioStream(48000, 16, 2); + music.sampleCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm); + music.loopCount = 0; // Infinite loop by default + musicLoaded = true; - TraceLog(LOG_INFO, "[%s] XM number of samples: %i", fileName, music->totalSamples); - TraceLog(LOG_INFO, "[%s] XM track length: %11.6f sec", fileName, (float)music->totalSamples/48000.0f); + music.ctxData = ctxXm; } - else musicLoaded = false; } #endif #if defined(SUPPORT_FILEFORMAT_MOD) else if (IsFileExtension(fileName, ".mod")) { - jar_mod_init(&music->ctxMod); + jar_mod_context_t *ctxMod = RL_MALLOC(sizeof(jar_mod_context_t)); + music.ctxData = ctxMod; - if (jar_mod_load_file(&music->ctxMod, fileName)) + jar_mod_init(ctxMod); + int result = jar_mod_load_file(ctxMod, fileName); + + if (result > 0) { - music->stream = InitAudioStream(48000, 16, 2); - music->totalSamples = (unsigned int)jar_mod_max_samples(&music->ctxMod); - music->samplesLeft = music->totalSamples; - music->ctxType = MUSIC_MODULE_MOD; - music->loopCount = -1; // Infinite loop by default + music.ctxType = MUSIC_MODULE_MOD; - TraceLog(LOG_DEBUG, "[%s] MOD number of samples: %i", fileName, music->samplesLeft); - TraceLog(LOG_DEBUG, "[%s] MOD track length: %11.6f sec", fileName, (float)music->totalSamples/48000.0f); + // NOTE: Only stereo is supported for MOD + music.stream = InitAudioStream(48000, 16, 2); + music.sampleCount = (unsigned int)jar_mod_max_samples(ctxMod); + music.loopCount = 0; // Infinite loop by default + musicLoaded = true; } - else musicLoaded = false; } #endif - else musicLoaded = false; if (!musicLoaded) { - if (music->ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close(music->ctxOgg); + if (false) { } + #if defined(SUPPORT_FILEFORMAT_OGG) + 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(music->ctxFlac); + else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData); #endif #if defined(SUPPORT_FILEFORMAT_MP3) - else if (music->ctxType == MUSIC_AUDIO_MP3) drmp3_uninit(&music->ctxMp3); + 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(music->ctxXm); + 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(&music->ctxMod); + else if (music.ctxType == MUSIC_MODULE_MOD) { jar_mod_unload((jar_mod_context_t *)music.ctxData); RL_FREE(music.ctxData); } #endif - free(music); - music = NULL; - TraceLog(LOG_WARNING, "[%s] Music file could not be opened", fileName); } + else + { + // Show some music stream info + TraceLog(LOG_INFO, "[%s] Music file successfully loaded:", fileName); + 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; } @@ -1217,119 +1313,120 @@ Music LoadMusicStream(const char *fileName) // Unload music stream void UnloadMusicStream(Music music) { - CloseAudioStream(music->stream); + CloseAudioStream(music.stream); - if (music->ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close(music->ctxOgg); + if (false) { } +#if defined(SUPPORT_FILEFORMAT_OGG) + 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(music->ctxFlac); + else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData); #endif #if defined(SUPPORT_FILEFORMAT_MP3) - else if (music->ctxType == MUSIC_AUDIO_MP3) drmp3_uninit(&music->ctxMp3); + 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(music->ctxXm); + 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(&music->ctxMod); + else if (music.ctxType == MUSIC_MODULE_MOD) { jar_mod_unload((jar_mod_context_t *)music.ctxData); RL_FREE(music.ctxData); } #endif - - free(music); } // Start music playing (open stream) void PlayMusicStream(Music music) { - AudioBuffer *audioBuffer = (AudioBuffer *)music->stream.audioBuffer; + AudioBuffer *audioBuffer = music.stream.buffer; - if (audioBuffer == NULL) + if (audioBuffer != NULL) { - TraceLog(LOG_ERROR, "PlayMusicStream() : No audio buffer"); - return; + // For music streams, we need to make sure we maintain the frame cursor position + // This is 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); + ma_uint32 frameCursorPos = audioBuffer->frameCursorPos; + PlayAudioStream(music.stream); // WARNING: This resets the cursor position. + audioBuffer->frameCursorPos = frameCursorPos; } + else TraceLog(LOG_ERROR, "PlayMusicStream() : No audio buffer"); - // For music streams, we need to make sure we maintain the frame cursor position. This is hack for this section of code in UpdateMusicStream() - // // NOTE: In case window is minimized, music stream is stopped, - // // just make sure to play again on window restore - // if (IsMusicPlaying(music)) PlayMusicStream(music); - mal_uint32 frameCursorPos = audioBuffer->frameCursorPos; - - PlayAudioStream(music->stream); // <-- This resets the cursor position. - - audioBuffer->frameCursorPos = frameCursorPos; } // Pause music playing void PauseMusicStream(Music music) { - PauseAudioStream(music->stream); + PauseAudioStream(music.stream); } // Resume music playing void ResumeMusicStream(Music music) { - ResumeAudioStream(music->stream); + ResumeAudioStream(music.stream); } // Stop music playing (close stream) -// TODO: To clear a buffer, make sure they have been already processed! void StopMusicStream(Music music) { - StopAudioStream(music->stream); + StopAudioStream(music.stream); // Restart music context - switch (music->ctxType) + switch (music.ctxType) { - case MUSIC_AUDIO_OGG: stb_vorbis_seek_start(music->ctxOgg); break; +#if defined(SUPPORT_FILEFORMAT_OGG) + case MUSIC_AUDIO_OGG: stb_vorbis_seek_start((stb_vorbis *)music.ctxData); break; +#endif #if defined(SUPPORT_FILEFORMAT_FLAC) - case MUSIC_AUDIO_FLAC: /* TODO: Restart FLAC context */ break; + case MUSIC_AUDIO_FLAC: drflac_seek_to_pcm_frame((drflac *)music.ctxData, 0); break; #endif #if defined(SUPPORT_FILEFORMAT_MP3) - case MUSIC_AUDIO_MP3: drmp3_seek_to_pcm_frame(&music->ctxMp3, 0); break; + case MUSIC_AUDIO_MP3: drmp3_seek_to_pcm_frame((drmp3 *)music.ctxData, 0); break; #endif #if defined(SUPPORT_FILEFORMAT_XM) - case MUSIC_MODULE_XM: /* TODO: Restart XM context */ break; + case MUSIC_MODULE_XM: jar_xm_reset((jar_xm_context_t *)music.ctxData); break; #endif #if defined(SUPPORT_FILEFORMAT_MOD) - case MUSIC_MODULE_MOD: jar_mod_seek_start(&music->ctxMod); break; + case MUSIC_MODULE_MOD: jar_mod_seek_start((jar_mod_context_t *)music.ctxData); break; #endif default: break; } - - music->samplesLeft = music->totalSamples; } // Update (re-fill) music buffers if data already processed -// TODO: Make sure buffers are ready for update... check music state void UpdateMusicStream(Music music) { bool streamEnding = false; - unsigned int subBufferSizeInFrames = ((AudioBuffer *)music->stream.audioBuffer)->bufferSizeInFrames/2; + unsigned int subBufferSizeInFrames = music.stream.buffer->bufferSizeInFrames/2; // NOTE: Using dynamic allocation because it could require more than 16KB - void *pcm = calloc(subBufferSizeInFrames*music->stream.channels*music->stream.sampleSize/8, 1); + void *pcm = RL_CALLOC(subBufferSizeInFrames*music.stream.channels*music.stream.sampleSize/8, 1); - int samplesCount = 0; // Total size of data steamed in L+R samples for xm floats, individual L or R for ogg shorts + int samplesCount = 0; // Total size of data streamed in L+R samples for xm floats, individual L or R for ogg shorts - while (IsAudioBufferProcessed(music->stream)) + // TODO: Get the sampleLeft using totalFramesProcessed... but first, get total frames processed correctly... + //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); + + while (IsAudioStreamProcessed(music.stream)) { - if ((music->samplesLeft/music->stream.channels) >= subBufferSizeInFrames) samplesCount = subBufferSizeInFrames*music->stream.channels; - else samplesCount = music->samplesLeft; + if ((sampleLeft/music.stream.channels) >= subBufferSizeInFrames) samplesCount = subBufferSizeInFrames*music.stream.channels; + else samplesCount = sampleLeft; - // TODO: Really don't like ctxType thingy... - switch (music->ctxType) + switch (music.ctxType) { + #if defined(SUPPORT_FILEFORMAT_OGG) case MUSIC_AUDIO_OGG: { // NOTE: Returns the number of samples to process (be careful! we ask for number of shorts!) - stb_vorbis_get_samples_short_interleaved(music->ctxOgg, music->stream.channels, (short *)pcm, samplesCount); + stb_vorbis_get_samples_short_interleaved((stb_vorbis *)music.ctxData, music.stream.channels, (short *)pcm, samplesCount); } break; + #endif #if defined(SUPPORT_FILEFORMAT_FLAC) case MUSIC_AUDIO_FLAC: { - // NOTE: Returns the number of samples to process - unsigned int numSamplesFlac = (unsigned int)drflac_read_s16(music->ctxFlac, samplesCount, (short *)pcm); + // NOTE: Returns the number of samples to process (not required) + drflac_read_pcm_frames_s16((drflac *)music.ctxData, samplesCount, (short *)pcm); } break; #endif @@ -1337,27 +1434,37 @@ void UpdateMusicStream(Music music) case MUSIC_AUDIO_MP3: { // NOTE: samplesCount, actually refers to framesCount and returns the number of frames processed - drmp3_read_pcm_frames_f32(&music->ctxMp3, samplesCount/music->stream.channels, (float *)pcm); + drmp3_read_pcm_frames_f32((drmp3 *)music.ctxData, samplesCount/music.stream.channels, (float *)pcm); } break; #endif #if defined(SUPPORT_FILEFORMAT_XM) case MUSIC_MODULE_XM: { - // NOTE: Internally this function considers 2 channels generation, so samplesCount/2 --> WEIRD - jar_xm_generate_samples_16bit(music->ctxXm, (short *)pcm, samplesCount/2); + // 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; #endif #if defined(SUPPORT_FILEFORMAT_MOD) - case MUSIC_MODULE_MOD: jar_mod_fillbuffer(&music->ctxMod, pcm, samplesCount, 0); break; + case MUSIC_MODULE_MOD: + { + // NOTE: 3rd parameter (nbsample) specify the number of stereo 16bits samples you want, so sampleCount/2 + jar_mod_fillbuffer((jar_mod_context_t *)music.ctxData, (short *)pcm, samplesCount/2, 0); + } break; #endif default: break; } - UpdateAudioStream(music->stream, pcm, samplesCount); - music->samplesLeft -= samplesCount; + UpdateAudioStream(music.stream, pcm, samplesCount); - if (music->samplesLeft <= 0) + if ((music.ctxType == MUSIC_MODULE_XM) || (music.ctxType == MUSIC_MODULE_MOD)) + { + if (samplesCount > 1) sampleLeft -= samplesCount/2; + else sampleLeft -= samplesCount; + } + else sampleLeft -= samplesCount; + + if (sampleLeft <= 0) { streamEnding = true; break; @@ -1365,7 +1472,7 @@ void UpdateMusicStream(Music music) } // Free allocated pcm data - free(pcm); + RL_FREE(pcm); // Reset audio stream for looping if (streamEnding) @@ -1373,15 +1480,12 @@ void UpdateMusicStream(Music music) StopMusicStream(music); // Stop music (and reset) // Decrease loopCount to stop when required - if (music->loopCount > 0) + if (music.loopCount > 1) { - music->loopCount--; // Decrease loop count + music.loopCount--; // Decrease loop count PlayMusicStream(music); // Play again } - else - { - if (music->loopCount == -1) PlayMusicStream(music); - } + else if (music.loopCount == 0) PlayMusicStream(music); } else { @@ -1394,32 +1498,34 @@ void UpdateMusicStream(Music music) // Check if any music is playing bool IsMusicPlaying(Music music) { - return IsAudioStreamPlaying(music->stream); + return IsAudioStreamPlaying(music.stream); } // Set volume for music void SetMusicVolume(Music music, float volume) { - SetAudioStreamVolume(music->stream, volume); + SetAudioStreamVolume(music.stream, volume); } // Set pitch for music void SetMusicPitch(Music music, float pitch) { - SetAudioStreamPitch(music->stream, pitch); + SetAudioStreamPitch(music.stream, pitch); } // Set music loop count (loop repeats) -// NOTE: If set to -1, means infinite loop +// NOTE: If set to 0, means infinite loop void SetMusicLoopCount(Music music, int count) { - music->loopCount = count; + music.loopCount = count; } // Get music time length (in seconds) float GetMusicTimeLength(Music music) { - float totalSeconds = (float)music->totalSamples/(music->stream.sampleRate*music->stream.channels); + float totalSeconds = 0.0f; + + totalSeconds = (float)music.sampleCount/(music.stream.sampleRate*music.stream.channels); return totalSeconds; } @@ -1429,8 +1535,9 @@ float GetMusicTimePlayed(Music music) { float secondsPlayed = 0.0f; - unsigned int samplesPlayed = music->totalSamples - music->samplesLeft; - secondsPlayed = (float)samplesPlayed/(music->stream.sampleRate*music->stream.channels); + //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); return secondsPlayed; } @@ -1442,33 +1549,24 @@ AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, un stream.sampleRate = sampleRate; stream.sampleSize = sampleSize; + stream.channels = channels; - // Only mono and stereo channels are supported, more channels require AL_EXT_MCFORMATS extension - if ((channels > 0) && (channels < 3)) stream.channels = channels; - else - { - TraceLog(LOG_WARNING, "Init audio stream: Number of channels not supported: %i", channels); - stream.channels = 1; // Fallback to mono channel - } + ma_format formatIn = ((stream.sampleSize == 8)? ma_format_u8 : ((stream.sampleSize == 16)? ma_format_s16 : ma_format_f32)); - mal_format formatIn = ((stream.sampleSize == 8) ? mal_format_u8 : ((stream.sampleSize == 16) ? mal_format_s16 : mal_format_f32)); - - // The size of a streaming buffer must be at least double the size of a period. - unsigned int periodSize = device.bufferSizeInFrames/device.periods; + // The size of a streaming buffer must be at least double the size of a period + unsigned int periodSize = device.playback.internalBufferSizeInFrames/device.playback.internalPeriods; unsigned int subBufferSize = AUDIO_BUFFER_SIZE; + if (subBufferSize < periodSize) subBufferSize = periodSize; - AudioBuffer *audioBuffer = CreateAudioBuffer(formatIn, stream.channels, stream.sampleRate, subBufferSize*2, AUDIO_BUFFER_USAGE_STREAM); - if (audioBuffer == NULL) + stream.buffer = InitAudioBuffer(formatIn, stream.channels, stream.sampleRate, subBufferSize*2, AUDIO_BUFFER_USAGE_STREAM); + + if (stream.buffer != NULL) { - TraceLog(LOG_ERROR, "InitAudioStream() : Failed to create audio buffer"); - return stream; + stream.buffer->looping = true; // Always loop for streaming buffers + TraceLog(LOG_INFO, "Audio stream loaded successfully (%i Hz, %i bit, %s)", stream.sampleRate, stream.sampleSize, (stream.channels == 1)? "Mono" : "Stereo"); } - - audioBuffer->looping = true; // Always loop for streaming buffers. - stream.audioBuffer = audioBuffer; - - TraceLog(LOG_INFO, "[AUD ID %i] Audio stream loaded successfully (%i Hz, %i bit, %s)", stream.source, stream.sampleRate, stream.sampleSize, (stream.channels == 1) ? "Mono" : "Stereo"); + else TraceLog(LOG_ERROR, "InitAudioStream() : Failed to create audio buffer"); return stream; } @@ -1476,126 +1574,118 @@ AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, un // Close audio stream and free memory void CloseAudioStream(AudioStream stream) { - DeleteAudioBuffer((AudioBuffer *)stream.audioBuffer); + CloseAudioBuffer(stream.buffer); - TraceLog(LOG_INFO, "[AUD ID %i] Unloaded audio stream data", stream.source); + TraceLog(LOG_INFO, "Unloaded audio stream data"); } // Update audio stream buffers with data // NOTE 1: Only updates one buffer of the stream source: unqueue -> update -> queue -// NOTE 2: To unqueue a buffer it needs to be processed: IsAudioBufferProcessed() +// NOTE 2: To unqueue a buffer it needs to be processed: IsAudioStreamProcessed() void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount) { - AudioBuffer *audioBuffer = (AudioBuffer *)stream.audioBuffer; - if (audioBuffer == NULL) + AudioBuffer *audioBuffer = stream.buffer; + + if (audioBuffer != NULL) { - TraceLog(LOG_ERROR, "UpdateAudioStream() : No audio buffer"); - return; - } - - if (audioBuffer->isSubBufferProcessed[0] || audioBuffer->isSubBufferProcessed[1]) - { - mal_uint32 subBufferToUpdate; - - if (audioBuffer->isSubBufferProcessed[0] && audioBuffer->isSubBufferProcessed[1]) + if (audioBuffer->isSubBufferProcessed[0] || audioBuffer->isSubBufferProcessed[1]) { - // Both buffers are available for updating. Update the first one and make sure the cursor is moved back to the front. - subBufferToUpdate = 0; - audioBuffer->frameCursorPos = 0; - } - else - { - // Just update whichever sub-buffer is processed. - subBufferToUpdate = (audioBuffer->isSubBufferProcessed[0]) ? 0 : 1; - } + ma_uint32 subBufferToUpdate = 0; - mal_uint32 subBufferSizeInFrames = audioBuffer->bufferSizeInFrames/2; - unsigned char *subBuffer = audioBuffer->buffer + ((subBufferSizeInFrames*stream.channels*(stream.sampleSize/8))*subBufferToUpdate); - - // Does this API expect a whole buffer to be updated in one go? Assuming so, but if not will need to change this logic. - if (subBufferSizeInFrames >= (mal_uint32)samplesCount/stream.channels) - { - mal_uint32 framesToWrite = subBufferSizeInFrames; - - if (framesToWrite > ((mal_uint32)samplesCount/stream.channels)) framesToWrite = (mal_uint32)samplesCount/stream.channels; - - mal_uint32 bytesToWrite = framesToWrite*stream.channels*(stream.sampleSize/8); - memcpy(subBuffer, data, bytesToWrite); - - // Any leftover frames should be filled with zeros. - mal_uint32 leftoverFrameCount = subBufferSizeInFrames - framesToWrite; - - if (leftoverFrameCount > 0) + if (audioBuffer->isSubBufferProcessed[0] && audioBuffer->isSubBufferProcessed[1]) { - memset(subBuffer + bytesToWrite, 0, leftoverFrameCount*stream.channels*(stream.sampleSize/8)); + // Both buffers are available for updating. + // Update the first one and make sure the cursor is moved back to the front. + subBufferToUpdate = 0; + audioBuffer->frameCursorPos = 0; + } + else + { + // Just update whichever sub-buffer is processed. + subBufferToUpdate = (audioBuffer->isSubBufferProcessed[0])? 0 : 1; } - audioBuffer->isSubBufferProcessed[subBufferToUpdate] = false; - } - else - { - TraceLog(LOG_ERROR, "UpdateAudioStream() : Attempting to write too many frames to buffer"); - return; + ma_uint32 subBufferSizeInFrames = audioBuffer->bufferSizeInFrames/2; + unsigned char *subBuffer = audioBuffer->buffer + ((subBufferSizeInFrames*stream.channels*(stream.sampleSize/8))*subBufferToUpdate); + + // TODO: Get total frames processed on this buffer... DOES NOT WORK. + audioBuffer->totalFramesProcessed += subBufferSizeInFrames; + + // Does this API expect a whole buffer to be updated in one go? + // Assuming so, but if not will need to change this logic. + if (subBufferSizeInFrames >= (ma_uint32)samplesCount/stream.channels) + { + ma_uint32 framesToWrite = subBufferSizeInFrames; + + if (framesToWrite > ((ma_uint32)samplesCount/stream.channels)) framesToWrite = (ma_uint32)samplesCount/stream.channels; + + ma_uint32 bytesToWrite = framesToWrite*stream.channels*(stream.sampleSize/8); + memcpy(subBuffer, data, bytesToWrite); + + // Any leftover frames should be filled with zeros. + ma_uint32 leftoverFrameCount = subBufferSizeInFrames - framesToWrite; + + if (leftoverFrameCount > 0) memset(subBuffer + bytesToWrite, 0, leftoverFrameCount*stream.channels*(stream.sampleSize/8)); + + audioBuffer->isSubBufferProcessed[subBufferToUpdate] = false; + } + else TraceLog(LOG_ERROR, "UpdateAudioStream() : Attempting to write too many frames to buffer"); } + else TraceLog(LOG_ERROR, "UpdateAudioStream() : Audio buffer not available for updating"); } - else - { - TraceLog(LOG_ERROR, "Audio buffer not available for updating"); - return; - } + else TraceLog(LOG_ERROR, "UpdateAudioStream() : No audio buffer"); } // Check if any audio stream buffers requires refill -bool IsAudioBufferProcessed(AudioStream stream) +bool IsAudioStreamProcessed(AudioStream stream) { - AudioBuffer *audioBuffer = (AudioBuffer *)stream.audioBuffer; - if (audioBuffer == NULL) + if (stream.buffer == NULL) { - TraceLog(LOG_ERROR, "IsAudioBufferProcessed() : No audio buffer"); + TraceLog(LOG_ERROR, "IsAudioStreamProcessed() : No audio buffer"); return false; } - return audioBuffer->isSubBufferProcessed[0] || audioBuffer->isSubBufferProcessed[1]; + return (stream.buffer->isSubBufferProcessed[0] || stream.buffer->isSubBufferProcessed[1]); } // Play audio stream void PlayAudioStream(AudioStream stream) { - PlayAudioBuffer((AudioBuffer *)stream.audioBuffer); + PlayAudioBuffer(stream.buffer); } // Play audio stream void PauseAudioStream(AudioStream stream) { - PauseAudioBuffer((AudioBuffer *)stream.audioBuffer); + PauseAudioBuffer(stream.buffer); } // Resume audio stream playing void ResumeAudioStream(AudioStream stream) { - ResumeAudioBuffer((AudioBuffer *)stream.audioBuffer); + ResumeAudioBuffer(stream.buffer); } // Check if audio stream is playing. bool IsAudioStreamPlaying(AudioStream stream) { - return IsAudioBufferPlaying((AudioBuffer *)stream.audioBuffer); + return IsAudioBufferPlaying(stream.buffer); } // Stop audio stream void StopAudioStream(AudioStream stream) { - StopAudioBuffer((AudioBuffer *)stream.audioBuffer); + StopAudioBuffer(stream.buffer); } void SetAudioStreamVolume(AudioStream stream, float volume) { - SetAudioBufferVolume((AudioBuffer *)stream.audioBuffer, volume); + SetAudioBufferVolume(stream.buffer, volume); } void SetAudioStreamPitch(AudioStream stream, float pitch) { - SetAudioBufferPitch((AudioBuffer *)stream.audioBuffer, pitch); + SetAudioBufferPitch(stream.buffer, pitch); } //---------------------------------------------------------------------------------- @@ -1682,7 +1772,7 @@ static Wave LoadWAV(const char *fileName) else { // Allocate memory for data - wave.data = malloc(wavData.subChunkSize); + wave.data = RL_MALLOC(wavData.subChunkSize); // Read in the sound data into the soundData variable fread(wave.data, wavData.subChunkSize, 1, wavFile); @@ -1709,7 +1799,7 @@ static Wave LoadWAV(const char *fileName) // NOTE: subChunkSize comes in bytes, we need to translate it to number of samples wave.sampleCount = (wavData.subChunkSize/(wave.sampleSize/8))/wave.channels; - TraceLog(LOG_INFO, "[%s] WAV file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo"); + TraceLog(LOG_INFO, "[%s] WAV file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1)? "Mono" : "Stereo"); } } } @@ -1787,9 +1877,9 @@ static int SaveWAV(Wave wave, const char *fileName) waveData.subChunkID[3] = 'a'; waveData.subChunkSize = dataSize; - success = fwrite(&riffHeader, sizeof(RiffHeader), 1, wavFile); - success = fwrite(&waveFormat, sizeof(WaveFormat), 1, wavFile); - success = fwrite(&waveData, sizeof(WaveData), 1, wavFile); + fwrite(&riffHeader, sizeof(RiffHeader), 1, wavFile); + fwrite(&waveFormat, sizeof(WaveFormat), 1, wavFile); + fwrite(&waveData, sizeof(WaveData), 1, wavFile); success = fwrite(wave.data, dataSize, 1, wavFile); @@ -1818,19 +1908,19 @@ static Wave LoadOGG(const char *fileName) wave.sampleRate = info.sample_rate; wave.sampleSize = 16; // 16 bit per sample (short) wave.channels = info.channels; - wave.sampleCount = (int)stb_vorbis_stream_length_in_samples(oggFile); // Independent by channel + wave.sampleCount = (unsigned int)stb_vorbis_stream_length_in_samples(oggFile)*info.channels; // Independent by channel float totalSeconds = stb_vorbis_stream_length_in_seconds(oggFile); if (totalSeconds > 10) TraceLog(LOG_WARNING, "[%s] Ogg audio length is larger than 10 seconds (%f), that's a big file in memory, consider music streaming", fileName, totalSeconds); - wave.data = (short *)malloc(wave.sampleCount*wave.channels*sizeof(short)); + wave.data = (short *)RL_MALLOC(wave.sampleCount*wave.channels*sizeof(short)); // NOTE: Returns the number of samples to process (be careful! we ask for number of shorts!) int numSamplesOgg = stb_vorbis_get_samples_short_interleaved(oggFile, info.channels, (short *)wave.data, wave.sampleCount*wave.channels); TraceLog(LOG_DEBUG, "[%s] Samples obtained: %i", fileName, numSamplesOgg); - TraceLog(LOG_INFO, "[%s] OGG file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo"); + TraceLog(LOG_INFO, "[%s] OGG file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1)? "Mono" : "Stereo"); stb_vorbis_close(oggFile); } @@ -1848,16 +1938,16 @@ static Wave LoadFLAC(const char *fileName) // Decode an entire FLAC file in one go uint64_t totalSampleCount; - wave.data = drflac_open_and_decode_file_s16(fileName, &wave.channels, &wave.sampleRate, &totalSampleCount); + wave.data = drflac_open_file_and_read_pcm_frames_s16(fileName, &wave.channels, &wave.sampleRate, &totalSampleCount); - wave.sampleCount = (int)totalSampleCount/wave.channels; + wave.sampleCount = (unsigned int)totalSampleCount; wave.sampleSize = 16; // NOTE: Only support up to 2 channels (mono, stereo) if (wave.channels > 2) TraceLog(LOG_WARNING, "[%s] FLAC channels number (%i) not supported", fileName, wave.channels); if (wave.data == NULL) TraceLog(LOG_WARNING, "[%s] FLAC data could not be loaded", fileName); - else TraceLog(LOG_INFO, "[%s] FLAC file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo"); + else TraceLog(LOG_INFO, "[%s] FLAC file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1)? "Mono" : "Stereo"); return wave; } @@ -1884,14 +1974,14 @@ static Wave LoadMP3(const char *fileName) if (wave.channels > 2) TraceLog(LOG_WARNING, "[%s] MP3 channels number (%i) not supported", fileName, wave.channels); if (wave.data == NULL) TraceLog(LOG_WARNING, "[%s] MP3 data could not be loaded", fileName); - else TraceLog(LOG_INFO, "[%s] MP3 file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo"); + else TraceLog(LOG_INFO, "[%s] MP3 file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1)? "Mono" : "Stereo"); return wave; } #endif // Some required functions for audio standalone module version -#if defined(AUDIO_STANDALONE) +#if defined(RAUDIO_STANDALONE) // Check file extension bool IsFileExtension(const char *fileName, const char *ext) { @@ -1929,3 +2019,5 @@ void TraceLog(int msgType, const char *text, ...) if (msgType == LOG_ERROR) exit(1); } #endif + +#undef AudioBuffer diff --git a/raylib/audio.go b/raylib/raudio.go similarity index 92% rename from raylib/audio.go rename to raylib/raudio.go index 7315b6e..7dcca51 100644 --- a/raylib/audio.go +++ b/raylib/raudio.go @@ -3,7 +3,7 @@ package rl /* -#include "external/stb_vorbis.c" +//#include "external/stb_vorbis.c" #include "raylib.h" #include @@ -58,18 +58,6 @@ func LoadWave(fileName string) Wave { return v } -// LoadWaveEx - Load wave data from float array data (32bit) -func LoadWaveEx(data []byte, sampleCount int32, sampleRate int32, sampleSize int32, channels int32) Wave { - cdata := unsafe.Pointer(&data[0]) - csampleCount := (C.int)(sampleCount) - csampleRate := (C.int)(sampleRate) - csampleSize := (C.int)(sampleSize) - cchannels := (C.int)(channels) - ret := C.LoadWaveEx(cdata, csampleCount, csampleRate, csampleSize, cchannels) - v := newWaveFromPointer(unsafe.Pointer(&ret)) - return v -} - // LoadSound - Load sound to memory func LoadSound(fileName string) Sound { cfileName := C.CString(fileName) @@ -315,10 +303,10 @@ func CloseAudioStream(stream AudioStream) { C.CloseAudioStream(*cstream) } -// IsAudioBufferProcessed - Check if any audio stream buffers requires refill -func IsAudioBufferProcessed(stream AudioStream) bool { +// IsAudioStreamProcessed - Check if any audio stream buffers requires refill +func IsAudioStreamProcessed(stream AudioStream) bool { cstream := stream.cptr() - ret := C.IsAudioBufferProcessed(*cstream) + ret := C.IsAudioStreamProcessed(*cstream) v := bool(ret) return v } diff --git a/raylib/audio.h b/raylib/raudio.h similarity index 70% rename from raylib/audio.h rename to raylib/raudio.h index 01c9374..f531500 100644 --- a/raylib/audio.h +++ b/raylib/raudio.h @@ -1,6 +1,6 @@ /********************************************************************************************** * -* raylib.audio - Basic funtionality to work with audio +* raudio - A simple and easy-to-use audio library based on miniaudio * * FEATURES: * - Manage audio device (init/close) @@ -10,22 +10,17 @@ * - Manage mixing channels * - Manage raw audio context * -* LIMITATIONS (only OpenAL Soft): -* Only up to two channels supported: MONO and STEREO (for additional channels, use AL_EXT_MCFORMATS) -* Only the following sample sizes supported: 8bit PCM, 16bit PCM, 32-bit float PCM (using AL_EXT_FLOAT32) -* * DEPENDENCIES: -* mini_al - Audio device/context management (https://github.com/dr-soft/mini_al) -* stb_vorbis - OGG audio files loading (http://www.nothings.org/stb_vorbis/) -* jar_xm - XM module file loading -* jar_mod - MOD audio file loading -* dr_flac - FLAC audio file loading -* -* *OpenAL Soft - Audio device management, still used on HTML5 and OSX platforms +* miniaudio.h - Audio device management lib (https://github.com/dr-soft/miniaudio) +* stb_vorbis.h - Ogg audio files loading (http://www.nothings.org/stb_vorbis/) +* dr_mp3.h - MP3 audio file loading (https://github.com/mackron/dr_libs) +* dr_flac.h - FLAC audio file loading (https://github.com/mackron/dr_libs) +* jar_xm.h - XM module file loading +* jar_mod.h - MOD audio file loading * * CONTRIBUTORS: * David Reid (github: @mackron) (Nov. 2017): -* - Complete port to mini_al library +* - Complete port to miniaudio library * * Joshua Reisenauer (github: @kd7tck) (2015) * - XM audio module support (jar_xm) @@ -36,7 +31,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2018 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2019 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. @@ -55,17 +50,25 @@ * **********************************************************************************************/ -#ifndef AUDIO_H -#define AUDIO_H +#ifndef RAUDIO_H +#define RAUDIO_H //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -//... +// Allow custom memory allocators +#ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) +#endif +#ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RL_FREE + #define RL_FREE(p) free(p) +#endif //---------------------------------------------------------------------------------- // Types and Structures Definition -// NOTE: Below types are required for CAMERA_STANDALONE usage //---------------------------------------------------------------------------------- #ifndef __cplusplus // Boolean type @@ -77,40 +80,43 @@ // Wave type, defines audio wave data typedef struct Wave { - unsigned int sampleCount; // Number of samples - 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) - void *data; // Buffer data pointer + unsigned int sampleCount; // Total number of samples + 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) + void *data; // Buffer data pointer } Wave; -// Sound source type -typedef struct Sound { - void *audioBuffer; // Pointer to internal data used by the audio system - - unsigned int source; // Audio source id - unsigned int buffer; // Audio buffer id - int format; // Audio format specifier -} Sound; - -// Music type (file streaming from memory) -// NOTE: Anything longer than ~10 seconds should be streamed -typedef struct MusicData *Music; +typedef struct rAudioBuffer rAudioBuffer; // Audio stream type // NOTE: Useful to create custom audio streams not bound to a specific file typedef struct AudioStream { - 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) + 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) - void *audioBuffer; // Pointer to internal data used by the audio system. - - int format; // Audio format specifier - unsigned int source; // Audio source id - unsigned int buffers[2]; // Audio buffers (double buffering) + 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 +} Sound; + +// Music stream type (audio file streaming from memory) +// NOTE: Anything longer than ~10 seconds should be streamed +typedef struct Music { + int ctxType; // Type of music context (audio filetype) + void *ctxData; // Audio context data, depends on type + + unsigned int sampleCount; // Total number of samples + unsigned int loopCount; // Loops count (times music will play), 0 means infinite loop + + AudioStream stream; // Audio stream +} Music; + #ifdef __cplusplus extern "C" { // Prevents name mangling of functions #endif @@ -123,22 +129,31 @@ extern "C" { // Prevents name mangling of functions //---------------------------------------------------------------------------------- // Module Functions Declaration //---------------------------------------------------------------------------------- + +// Audio device management functions void InitAudioDevice(void); // Initialize audio device and context void CloseAudioDevice(void); // Close the audio device and context bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully void SetMasterVolume(float volume); // Set master volume (listener) +// Wave/Sound loading/unloading functions Wave LoadWave(const char *fileName); // Load wave data from file -Wave LoadWaveEx(void *data, int sampleCount, int sampleRate, int sampleSize, int channels); // Load wave data from raw array data 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) + +// Wave/Sound management functions void PlaySound(Sound sound); // Play a sound +void StopSound(Sound sound); // Stop playing a sound void PauseSound(Sound sound); // Pause a sound void ResumeSound(Sound sound); // Resume a paused sound -void StopSound(Sound sound); // Stop playing a sound +void PlaySoundMulti(Sound sound); // Play a sound (using multichannel buffer pool) +void StopSoundMulti(void); // Stop any sound playing (using multichannel buffer pool) +int GetSoundsPlaying(void); // Get number of sounds playing in the multichannel bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) @@ -146,6 +161,8 @@ void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels); // C 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 + +// Music management functions Music LoadMusicStream(const char *fileName); // Load music stream from file void UnloadMusicStream(Music music); // Unload music stream void PlayMusicStream(Music music); // Start music playing @@ -161,12 +178,10 @@ float GetMusicTimeLength(Music music); // Get music tim float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) // AudioStream management functions -AudioStream InitAudioStream(unsigned int sampleRate, - unsigned int sampleSize, - unsigned int channels); // Init audio stream (to stream raw audio pcm data) +AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Init audio stream (to stream raw audio pcm data) void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount); // Update audio stream buffers with data void CloseAudioStream(AudioStream stream); // Close audio stream and free memory -bool IsAudioBufferProcessed(AudioStream stream); // Check if any audio stream buffers requires refill +bool IsAudioStreamProcessed(AudioStream stream); // Check if any audio stream buffers requires refill void PlayAudioStream(AudioStream stream); // Play audio stream void PauseAudioStream(AudioStream stream); // Pause audio stream void ResumeAudioStream(AudioStream stream); // Resume audio stream @@ -179,4 +194,4 @@ void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for } #endif -#endif // AUDIO_H \ No newline at end of file +#endif // RAUDIO_H diff --git a/raylib/raylib.go b/raylib/raylib.go index 9c1168c..93e716f 100644 --- a/raylib/raylib.go +++ b/raylib/raylib.go @@ -74,17 +74,8 @@ func newWaveFromPointer(ptr unsafe.Pointer) Wave { // Sound source type type Sound struct { - // Audio source id - Source uint32 - // Audio buffer id - Buffer uint32 - // Audio format specifier - Format int32 -} - -// NewSound - Returns new Sound -func NewSound(source, buffer uint32, format int32) Sound { - return Sound{source, buffer, format} + SampleCount uint32 + Stream AudioStream } // newSoundFromPointer - Returns new Sound from pointer @@ -95,16 +86,11 @@ func newSoundFromPointer(ptr unsafe.Pointer) Sound { // Music type (file streaming from memory) // NOTE: Anything longer than ~10 seconds should be streamed type Music struct { - CtxType uint32 - _ [4]byte - ctxOgg unsafe.Pointer - ctxFlac unsafe.Pointer - ctxXm unsafe.Pointer - ctxMod unsafe.Pointer - Stream AudioStream - LoopCount int32 - TotalSamples uint32 - SamplesLeft uint32 + CtxType int32 + CtxData unsafe.Pointer + SampleCount uint32 + LoopCount uint32 + Stream AudioStream } // newMusicFromPointer - Returns new Music from pointer @@ -121,17 +107,8 @@ type AudioStream struct { SampleSize uint32 // Number of channels (1-mono, 2-stereo) Channels uint32 - // Audio format specifier - Format int32 - // Audio source id - Source uint32 - // Audio buffers (double buffering) - Buffers [2]uint32 -} - -// NewAudioStream - Returns new AudioStream -func NewAudioStream(sampleRate, sampleSize, channels uint32, format int32, source uint32, buffers [2]uint32) AudioStream { - return AudioStream{sampleRate, sampleSize, channels, format, source, buffers} + // Buffer + Buffer *_Ctype_struct_rAudioBuffer } // newAudioStreamFromPointer - Returns new AudioStream from pointer @@ -772,28 +749,31 @@ type Mesh struct { // Number of triangles stored (indexed or not) TriangleCount int32 // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) - Vertices *[]float32 + Vertices *float32 // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) - Texcoords *[]float32 + Texcoords *float32 // Vertex second texture coordinates (useful for lightmaps) (shader-location = 5) - Texcoords2 *[]float32 + Texcoords2 *float32 // Vertex normals (XYZ - 3 components per vertex) (shader-location = 2) - Normals *[]float32 + Normals *float32 // Vertex tangents (XYZ - 3 components per vertex) (shader-location = 4) - Tangents *[]float32 + Tangents *float32 // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) - Colors *[]uint8 + Colors *uint8 // Vertex indices (in case vertex data comes indexed) - Indices *[]uint16 + Indices *uint16 + // AnimVertices + AnimVertices *float32 + // AnimNormals + AnimNormals *float32 + // BoneIds + BoneIds *int32 + // BoneWeights + BoneWeights *float32 // OpenGL Vertex Array Object id VaoID uint32 // OpenGL Vertex Buffer Objects id (7 types of vertex data) - VboID [7]uint32 -} - -// NewMesh - Returns new Mesh -func NewMesh(vertexCount, triangleCount int32, vertices, texcoords, texcoords2, normals, tangents *[]float32, colors *[]uint8, indices *[]uint16, vaoID uint32, vboID [7]uint32) Mesh { - return Mesh{vertexCount, triangleCount, vertices, texcoords, texcoords2, normals, tangents, colors, indices, vaoID, vboID} + VboID *uint32 } // newMeshFromPointer - Returns new Mesh from pointer @@ -807,15 +787,8 @@ type Material struct { Shader Shader // Maps Maps [MaxMaterialMaps]MaterialMap - // Padding - _ [4]byte // Generic parameters (if required) - Params *[]float32 -} - -// NewMaterial - Returns new Material -func NewMaterial(shader Shader, maps [MaxMaterialMaps]MaterialMap, params *[]float32) Material { - return Material{shader, maps, [4]byte{}, params} + Params *float32 } // newMaterialFromPointer - Returns new Material from pointer @@ -833,21 +806,17 @@ type MaterialMap struct { Value float32 } -// Model type type Model struct { - // Vertex data buffers (RAM and VRAM) - Mesh Mesh // Local transform matrix - Transform Matrix - // Shader and textures data - Material Material - // Padding - _ [4]byte -} - -// NewModel - Returns new Model -func NewModel(mesh Mesh, transform Matrix, material Material) Model { - return Model{mesh, transform, material, [4]byte{}} + Transform Matrix + MeshCount int32 + Meshes []Mesh + MaterialCount int32 + Materials []Material + MeshMaterial *int32 + BoneCount int32 + Bones []BoneInfo + BindPose []Transform } // newModelFromPointer - Returns new Model from pointer @@ -855,6 +824,19 @@ func newModelFromPointer(ptr unsafe.Pointer) Model { return *(*Model)(ptr) } +// BoneInfo type. +type BoneInfo struct { + Name [32]int8 + Parent int32 +} + +// Transform type. +type Transform struct { + Translation Vector3 + Rotation Vector4 + Scale Vector3 +} + // Ray type (useful for raycast) type Ray struct { // Ray position (origin) @@ -889,9 +871,9 @@ const ( // VrDeviceInfo - Head-Mounted-Display device parameters type VrDeviceInfo struct { // HMD horizontal resolution in pixels - HResolution int + HResolution int32 // HMD vertical resolution in pixels - VResolution int + VResolution int32 // HMD horizontal size in meters HScreenSize float32 // HMD vertical size in meters @@ -911,7 +893,7 @@ type VrDeviceInfo struct { } // NewVrDeviceInfo - Returns new VrDeviceInfo -func NewVrDeviceInfo(hResolution, vResolution int, hScreenSize, vScreenSize, vScreenCenter, eyeToScreenDistance, +func NewVrDeviceInfo(hResolution, vResolution int32, hScreenSize, vScreenSize, vScreenCenter, eyeToScreenDistance, lensSeparationDistance, interpupillaryDistance float32, lensDistortionValues, chromaAbCorrection [4]float32) VrDeviceInfo { return VrDeviceInfo{hResolution, vResolution, hScreenSize, vScreenSize, vScreenCenter, eyeToScreenDistance, @@ -977,21 +959,18 @@ func newCharInfoFromPointer(ptr unsafe.Pointer) CharInfo { // Font type, includes texture and charSet array data type Font struct { - // Font texture - Texture Texture2D // Base size (default chars height) BaseSize int32 // Number of characters CharsCount int32 + // Characters texture atlas + Texture Texture2D + // Characters rectangles in texture + Recs *Rectangle // Characters info data Chars *CharInfo } -// NewFont - Returns new Font -func NewFont(texture Texture2D, baseSize, charsCount int32, chars *CharInfo) Font { - return Font{texture, baseSize, charsCount, chars} -} - // newFontFromPointer - Returns new Font from pointer func newFontFromPointer(ptr unsafe.Pointer) Font { return *(*Font)(ptr) @@ -1167,11 +1146,14 @@ func newRenderTexture2DFromPointer(ptr unsafe.Pointer) RenderTexture2D { // Log message types const ( - LogInfo = 1 << iota + LogAll = iota + LogTrace + LogDebug + LogInfo LogWarning LogError - LogDebug - LogOther + LogFatal + LogNone ) var logTypeFlags = LogInfo | LogWarning | LogError diff --git a/raylib/raylib.h b/raylib/raylib.h index ef47038..181be1f 100644 --- a/raylib/raylib.h +++ b/raylib/raylib.h @@ -4,19 +4,20 @@ * * FEATURES: * - NO external dependencies, all required libraries included with raylib -* - Multiple platforms support: Windows, Linux, FreeBSD, OpenBSD, NetBSD, DragonFly, MacOS, UWP, Android, Raspberry Pi, HTML5. +* - Multiplatform: Windows, Linux, FreeBSD, OpenBSD, NetBSD, DragonFly, MacOS, 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] -* - Powerful fonts module with Fonts support (XNA fonts, AngelCode fonts, TTF) +* - Powerful fonts module (XNA SpriteFonts, BMFonts, TTF) * - Outstanding texture formats support, including compressed formats (DXT, ETC, ASTC) * - Full 3d support for 3d Shapes, Models, Billboards, Heightmaps and more! * - Flexible Materials system, supporting classic maps and PBR maps +* - Skeletal Animation support (CPU bones-based animation) * - 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) * - VR stereo rendering with configurable HMD device parameters -* - Complete bindings to LUA (raylib-lua) and Go (raylib-go) +* - Bindings to multiple programming languages available! * * NOTES: * One custom font is loaded by default when InitWindow() [core] @@ -24,24 +25,26 @@ * If using OpenGL 3.3 or ES2, several vertex buffers (VAO/VBO) are created to manage lines-triangles-quads * * DEPENDENCIES (included): -* rglfw (github.com/glfw/glfw) for window/context management and input (only PLATFORM_DESKTOP) [core] -* glad (github.com/Dav1dde/glad) for OpenGL extensions loading (3.3 Core profile, only PLATFORM_DESKTOP) [rlgl] -* mini_al (github.com/dr-soft/mini_al) for audio device/context management [audio] +* [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 * * OPTIONAL DEPENDENCIES (included): -* stb_image (Sean Barret) for images loading (BMP, TGA, PNG, JPEG, HDR...) [textures] -* stb_image_resize (Sean Barret) for image resizing algorythms [textures] -* stb_image_write (Sean Barret) for image writting (PNG) [utils] -* stb_truetype (Sean Barret) for ttf fonts loading [text] -* stb_rect_pack (Sean Barret) for rectangles packing [text] -* stb_vorbis (Sean Barret) for OGG audio loading [audio] -* stb_perlin (Sean Barret) for Perlin noise image generation [textures] -* par_shapes (Philip Rideout) for parametric 3d shapes generation [models] -* jar_xm (Joshua Reisenauer) for XM audio module loading [audio] -* jar_mod (Joshua Reisenauer) for MOD audio module loading [audio] -* dr_flac (David Reid) for FLAC audio file loading [audio] -* dr_mp3 (David Reid) for MP3 audio file loading [audio] -* rgif (Charlie Tangora, Ramon Santamaria) for GIF recording [core] +* [core] rgif (Charlie Tangora, Ramon Santamaria) for GIF recording +* [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 +* [textures] stb_perlin (Sean Barret) for Perlin noise image generation +* [text] stb_truetype (Sean Barret) for ttf fonts loading +* [text] stb_rect_pack (Sean Barret) for rectangles packing +* [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_flac (David Reid) for FLAC audio file loading +* [raudio] dr_mp3 (David Reid) for MP3 audio file loading +* [raudio] jar_xm (Joshua Reisenauer) for XM audio module loading +* [raudio] jar_mod (Joshua Reisenauer) for MOD audio module loading * * * LICENSE: zlib/libpng @@ -49,7 +52,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-2018 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2019 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,265 +94,62 @@ #define DEG2RAD (PI/180.0f) #define RAD2DEG (180.0f/PI) -// raylib Config Flags -#define FLAG_SHOW_LOGO 1 // Set to show raylib logo at startup -#define FLAG_FULLSCREEN_MODE 2 // Set to run program in fullscreen -#define FLAG_WINDOW_RESIZABLE 4 // Set to allow resizable window -#define FLAG_WINDOW_UNDECORATED 8 // Set to disable window decoration (frame and buttons) -#define FLAG_WINDOW_TRANSPARENT 16 // Set to allow transparent window -#define FLAG_MSAA_4X_HINT 32 // Set to try enabling MSAA 4X -#define FLAG_VSYNC_HINT 64 // Set to try enabling V-Sync on GPU +#define MAX_TOUCH_POINTS 10 // Maximum number of touch points supported -// Keyboard Function Keys -#define KEY_SPACE 32 -#define KEY_ESCAPE 256 -#define KEY_ENTER 257 -#define KEY_TAB 258 -#define KEY_BACKSPACE 259 -#define KEY_INSERT 260 -#define KEY_DELETE 261 -#define KEY_RIGHT 262 -#define KEY_LEFT 263 -#define KEY_DOWN 264 -#define KEY_UP 265 -#define KEY_PAGE_UP 266 -#define KEY_PAGE_DOWN 267 -#define KEY_HOME 268 -#define KEY_END 269 -#define KEY_CAPS_LOCK 280 -#define KEY_SCROLL_LOCK 281 -#define KEY_NUM_LOCK 282 -#define KEY_PRINT_SCREEN 283 -#define KEY_PAUSE 284 -#define KEY_F1 290 -#define KEY_F2 291 -#define KEY_F3 292 -#define KEY_F4 293 -#define KEY_F5 294 -#define KEY_F6 295 -#define KEY_F7 296 -#define KEY_F8 297 -#define KEY_F9 298 -#define KEY_F10 299 -#define KEY_F11 300 -#define KEY_F12 301 -#define KEY_LEFT_SHIFT 340 -#define KEY_LEFT_CONTROL 341 -#define KEY_LEFT_ALT 342 -#define KEY_LEFT_SUPER 343 -#define KEY_RIGHT_SHIFT 344 -#define KEY_RIGHT_CONTROL 345 -#define KEY_RIGHT_ALT 346 -#define KEY_RIGHT_SUPER 347 -#define KEY_KB_MENU 348 -#define KEY_LEFT_BRACKET 91 -#define KEY_BACKSLASH 92 -#define KEY_RIGHT_BRACKET 93 -#define KEY_GRAVE 96 - -// Keyboard Number Pad Keys -#define KEY_KP_0 320 -#define KEY_KP_1 321 -#define KEY_KP_2 322 -#define KEY_KP_3 323 -#define KEY_KP_4 324 -#define KEY_KP_5 325 -#define KEY_KP_6 326 -#define KEY_KP_7 327 -#define KEY_KP_8 328 -#define KEY_KP_9 329 -#define KEY_KP_DECIMAL 330 -#define KEY_KP_DIVIDE 331 -#define KEY_KP_MULTIPLY 332 -#define KEY_KP_SUBTRACT 333 -#define KEY_KP_ADD 334 -#define KEY_KP_ENTER 335 -#define KEY_KP_EQUAL 336 - -// Keyboard Alpha Numeric Keys -#define KEY_APOSTROPHE 39 -#define KEY_COMMA 44 -#define KEY_MINUS 45 -#define KEY_PERIOD 46 -#define KEY_SLASH 47 -#define KEY_ZERO 48 -#define KEY_ONE 49 -#define KEY_TWO 50 -#define KEY_THREE 51 -#define KEY_FOUR 52 -#define KEY_FIVE 53 -#define KEY_SIX 54 -#define KEY_SEVEN 55 -#define KEY_EIGHT 56 -#define KEY_NINE 57 -#define KEY_SEMICOLON 59 -#define KEY_EQUAL 61 -#define KEY_A 65 -#define KEY_B 66 -#define KEY_C 67 -#define KEY_D 68 -#define KEY_E 69 -#define KEY_F 70 -#define KEY_G 71 -#define KEY_H 72 -#define KEY_I 73 -#define KEY_J 74 -#define KEY_K 75 -#define KEY_L 76 -#define KEY_M 77 -#define KEY_N 78 -#define KEY_O 79 -#define KEY_P 80 -#define KEY_Q 81 -#define KEY_R 82 -#define KEY_S 83 -#define KEY_T 84 -#define KEY_U 85 -#define KEY_V 86 -#define KEY_W 87 -#define KEY_X 88 -#define KEY_Y 89 -#define KEY_Z 90 - -// Android Physical Buttons -#define KEY_BACK 4 -#define KEY_MENU 82 -#define KEY_VOLUME_UP 24 -#define KEY_VOLUME_DOWN 25 - -// Mouse Buttons -#define MOUSE_LEFT_BUTTON 0 -#define MOUSE_RIGHT_BUTTON 1 -#define MOUSE_MIDDLE_BUTTON 2 - -// Touch points registered -#define MAX_TOUCH_POINTS 10 - -// Gamepad Number -#define GAMEPAD_PLAYER1 0 -#define GAMEPAD_PLAYER2 1 -#define GAMEPAD_PLAYER3 2 -#define GAMEPAD_PLAYER4 3 - -// Gamepad Buttons/Axis - -// PS3 USB Controller Buttons -#define GAMEPAD_PS3_BUTTON_TRIANGLE 0 -#define GAMEPAD_PS3_BUTTON_CIRCLE 1 -#define GAMEPAD_PS3_BUTTON_CROSS 2 -#define GAMEPAD_PS3_BUTTON_SQUARE 3 -#define GAMEPAD_PS3_BUTTON_L1 6 -#define GAMEPAD_PS3_BUTTON_R1 7 -#define GAMEPAD_PS3_BUTTON_L2 4 -#define GAMEPAD_PS3_BUTTON_R2 5 -#define GAMEPAD_PS3_BUTTON_START 8 -#define GAMEPAD_PS3_BUTTON_SELECT 9 -#define GAMEPAD_PS3_BUTTON_UP 24 -#define GAMEPAD_PS3_BUTTON_RIGHT 25 -#define GAMEPAD_PS3_BUTTON_DOWN 26 -#define GAMEPAD_PS3_BUTTON_LEFT 27 -#define GAMEPAD_PS3_BUTTON_PS 12 - -// PS3 USB Controller Axis -#define GAMEPAD_PS3_AXIS_LEFT_X 0 -#define GAMEPAD_PS3_AXIS_LEFT_Y 1 -#define GAMEPAD_PS3_AXIS_RIGHT_X 2 -#define GAMEPAD_PS3_AXIS_RIGHT_Y 5 -#define GAMEPAD_PS3_AXIS_L2 3 // [1..-1] (pressure-level) -#define GAMEPAD_PS3_AXIS_R2 4 // [1..-1] (pressure-level) - -// Xbox360 USB Controller Buttons -#define GAMEPAD_XBOX_BUTTON_A 0 -#define GAMEPAD_XBOX_BUTTON_B 1 -#define GAMEPAD_XBOX_BUTTON_X 2 -#define GAMEPAD_XBOX_BUTTON_Y 3 -#define GAMEPAD_XBOX_BUTTON_LB 4 -#define GAMEPAD_XBOX_BUTTON_RB 5 -#define GAMEPAD_XBOX_BUTTON_SELECT 6 -#define GAMEPAD_XBOX_BUTTON_START 7 -#define GAMEPAD_XBOX_BUTTON_UP 10 -#define GAMEPAD_XBOX_BUTTON_RIGHT 11 -#define GAMEPAD_XBOX_BUTTON_DOWN 12 -#define GAMEPAD_XBOX_BUTTON_LEFT 13 -#define GAMEPAD_XBOX_BUTTON_HOME 8 - -// Android Gamepad Controller (SNES CLASSIC) -#define GAMEPAD_ANDROID_DPAD_UP 19 -#define GAMEPAD_ANDROID_DPAD_DOWN 20 -#define GAMEPAD_ANDROID_DPAD_LEFT 21 -#define GAMEPAD_ANDROID_DPAD_RIGHT 22 -#define GAMEPAD_ANDROID_DPAD_CENTER 23 - -#define GAMEPAD_ANDROID_BUTTON_A 96 -#define GAMEPAD_ANDROID_BUTTON_B 97 -#define GAMEPAD_ANDROID_BUTTON_C 98 -#define GAMEPAD_ANDROID_BUTTON_X 99 -#define GAMEPAD_ANDROID_BUTTON_Y 100 -#define GAMEPAD_ANDROID_BUTTON_Z 101 -#define GAMEPAD_ANDROID_BUTTON_L1 102 -#define GAMEPAD_ANDROID_BUTTON_R1 103 -#define GAMEPAD_ANDROID_BUTTON_L2 104 -#define GAMEPAD_ANDROID_BUTTON_R2 105 - -// Xbox360 USB Controller Axis -// NOTE: For Raspberry Pi, axis must be reconfigured -#if defined(PLATFORM_RPI) - #define GAMEPAD_XBOX_AXIS_LEFT_X 0 // [-1..1] (left->right) - #define GAMEPAD_XBOX_AXIS_LEFT_Y 1 // [-1..1] (up->down) - #define GAMEPAD_XBOX_AXIS_RIGHT_X 3 // [-1..1] (left->right) - #define GAMEPAD_XBOX_AXIS_RIGHT_Y 4 // [-1..1] (up->down) - #define GAMEPAD_XBOX_AXIS_LT 2 // [-1..1] (pressure-level) - #define GAMEPAD_XBOX_AXIS_RT 5 // [-1..1] (pressure-level) -#else - #define GAMEPAD_XBOX_AXIS_LEFT_X 0 // [-1..1] (left->right) - #define GAMEPAD_XBOX_AXIS_LEFT_Y 1 // [1..-1] (up->down) - #define GAMEPAD_XBOX_AXIS_RIGHT_X 2 // [-1..1] (left->right) - #define GAMEPAD_XBOX_AXIS_RIGHT_Y 3 // [1..-1] (up->down) - #define GAMEPAD_XBOX_AXIS_LT 4 // [-1..1] (pressure-level) - #define GAMEPAD_XBOX_AXIS_RT 5 // [-1..1] (pressure-level) +// Allow custom memory allocators +#ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) +#endif +#ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RL_FREE + #define RL_FREE(p) free(p) #endif // NOTE: MSC 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 + #define CLITERAL(type) type #else - #define CLITERAL (Color) + #define CLITERAL(type) (type) #endif // Some Basic Colors // NOTE: Custom raylib color palette for amazing visuals on WHITE background -#define LIGHTGRAY CLITERAL{ 200, 200, 200, 255 } // Light Gray -#define GRAY CLITERAL{ 130, 130, 130, 255 } // Gray -#define DARKGRAY CLITERAL{ 80, 80, 80, 255 } // Dark Gray -#define YELLOW CLITERAL{ 253, 249, 0, 255 } // Yellow -#define GOLD CLITERAL{ 255, 203, 0, 255 } // Gold -#define ORANGE CLITERAL{ 255, 161, 0, 255 } // Orange -#define PINK CLITERAL{ 255, 109, 194, 255 } // Pink -#define RED CLITERAL{ 230, 41, 55, 255 } // Red -#define MAROON CLITERAL{ 190, 33, 55, 255 } // Maroon -#define GREEN CLITERAL{ 0, 228, 48, 255 } // Green -#define LIME CLITERAL{ 0, 158, 47, 255 } // Lime -#define DARKGREEN CLITERAL{ 0, 117, 44, 255 } // Dark Green -#define SKYBLUE CLITERAL{ 102, 191, 255, 255 } // Sky Blue -#define BLUE CLITERAL{ 0, 121, 241, 255 } // Blue -#define DARKBLUE CLITERAL{ 0, 82, 172, 255 } // Dark Blue -#define PURPLE CLITERAL{ 200, 122, 255, 255 } // Purple -#define VIOLET CLITERAL{ 135, 60, 190, 255 } // Violet -#define DARKPURPLE CLITERAL{ 112, 31, 126, 255 } // Dark Purple -#define BEIGE CLITERAL{ 211, 176, 131, 255 } // Beige -#define BROWN CLITERAL{ 127, 106, 79, 255 } // Brown -#define DARKBROWN CLITERAL{ 76, 63, 47, 255 } // Dark Brown +#define LIGHTGRAY CLITERAL(Color){ 200, 200, 200, 255 } // Light Gray +#define GRAY CLITERAL(Color){ 130, 130, 130, 255 } // Gray +#define DARKGRAY CLITERAL(Color){ 80, 80, 80, 255 } // Dark Gray +#define YELLOW CLITERAL(Color){ 253, 249, 0, 255 } // Yellow +#define GOLD CLITERAL(Color){ 255, 203, 0, 255 } // Gold +#define ORANGE CLITERAL(Color){ 255, 161, 0, 255 } // Orange +#define PINK CLITERAL(Color){ 255, 109, 194, 255 } // Pink +#define RED CLITERAL(Color){ 230, 41, 55, 255 } // Red +#define MAROON CLITERAL(Color){ 190, 33, 55, 255 } // Maroon +#define GREEN CLITERAL(Color){ 0, 228, 48, 255 } // Green +#define LIME CLITERAL(Color){ 0, 158, 47, 255 } // Lime +#define DARKGREEN CLITERAL(Color){ 0, 117, 44, 255 } // Dark Green +#define SKYBLUE CLITERAL(Color){ 102, 191, 255, 255 } // Sky Blue +#define BLUE CLITERAL(Color){ 0, 121, 241, 255 } // Blue +#define DARKBLUE CLITERAL(Color){ 0, 82, 172, 255 } // Dark Blue +#define PURPLE CLITERAL(Color){ 200, 122, 255, 255 } // Purple +#define VIOLET CLITERAL(Color){ 135, 60, 190, 255 } // Violet +#define DARKPURPLE CLITERAL(Color){ 112, 31, 126, 255 } // Dark Purple +#define BEIGE CLITERAL(Color){ 211, 176, 131, 255 } // Beige +#define BROWN CLITERAL(Color){ 127, 106, 79, 255 } // Brown +#define DARKBROWN CLITERAL(Color){ 76, 63, 47, 255 } // Dark Brown -#define WHITE CLITERAL{ 255, 255, 255, 255 } // White -#define BLACK CLITERAL{ 0, 0, 0, 255 } // Black -#define BLANK CLITERAL{ 0, 0, 0, 0 } // Blank (Transparent) -#define MAGENTA CLITERAL{ 255, 0, 255, 255 } // Magenta -#define RAYWHITE CLITERAL{ 245, 245, 245, 255 } // My own White (raylib logo) +#define WHITE CLITERAL(Color){ 255, 255, 255, 255 } // White +#define BLACK CLITERAL(Color){ 0, 0, 0, 255 } // Black +#define BLANK CLITERAL(Color){ 0, 0, 0, 0 } // Blank (Transparent) +#define MAGENTA CLITERAL(Color){ 255, 0, 255, 255 } // Magenta +#define RAYWHITE CLITERAL(Color){ 245, 245, 245, 255 } // My own White (raylib logo) -// Shader and material limits -#define MAX_SHADER_LOCATIONS 32 // Maximum number of predefined locations stored in shader struct -#define MAX_MATERIAL_MAPS 12 // Maximum number of texture maps stored in shader struct +// Temporal hack to avoid breaking old codebases using +// deprecated raylib implementation of these functions +#define FormatText TextFormat +#define SubText TextSubtext +#define ShowWindow UnhideWindow //---------------------------------------------------------------------------------- // Structures Definition @@ -432,11 +232,15 @@ typedef struct 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 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, same as RenderTexture2D @@ -455,18 +259,18 @@ typedef struct NPatchInfo { // Font character info typedef struct CharInfo { int value; // Character value (Unicode) - Rectangle rec; // Character rectangle in sprite font int offsetX; // Character offset X when drawing int offsetY; // Character offset Y when drawing int advanceX; // Character advance position X - unsigned char *data; // Character pixel data (grayscale) + Image image; // Character image data } CharInfo; // Font type, includes texture and charSet array data typedef struct Font { - Texture2D texture; // Font texture int baseSize; // Base size (default chars height) int charsCount; // Number of characters + Texture2D texture; // Characters texture atlas + Rectangle *recs; // Characters rectangles in texture CharInfo *chars; // Characters info data } Font; @@ -481,7 +285,7 @@ typedef struct Camera3D { int type; // Camera type, defines projection type: CAMERA_PERSPECTIVE or CAMERA_ORTHOGRAPHIC } Camera3D; -#define Camera Camera3D // Camera type fallback, defaults to Camera3D +typedef Camera3D Camera; // Camera type fallback, defaults to Camera3D // Camera2D type, defines a 2d camera typedef struct Camera2D { @@ -491,12 +295,6 @@ typedef struct Camera2D { float zoom; // Camera zoom (scaling), should be 1.0f by default } Camera2D; -// Bounding box type -typedef struct BoundingBox { - Vector3 min; // Minimum vertex box-corner - Vector3 max; // Maximum vertex box-corner -} BoundingBox; - // Vertex data definning a mesh // NOTE: Data stored in CPU memory (and GPU) typedef struct Mesh { @@ -513,20 +311,20 @@ typedef struct Mesh { unsigned short *indices;// Vertex indices (in case vertex data comes indexed) // Animation vertex data - float *baseVertices; // Vertex base position (required to apply bones transformations) - float *baseNormals; // Vertex base normals (required to apply bones transformations) - float *weightBias; // Vertex weight bias - int *weightId; // Vertex weight id + 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[7]; // OpenGL Vertex Buffer Objects id (default vertex data) + unsigned int *vboId; // OpenGL Vertex Buffer Objects id (default vertex data) } Mesh; // Shader type (generic) typedef struct Shader { - unsigned int id; // Shader program id - int locs[MAX_SHADER_LOCATIONS]; // Shader locations array + unsigned int id; // Shader program id + int *locs; // Shader locations array (MAX_SHADER_LOCATIONS) } Shader; // Material texture map @@ -539,17 +337,49 @@ typedef struct MaterialMap { // Material type (generic) typedef struct Material { Shader shader; // Material shader - MaterialMap maps[MAX_MATERIAL_MAPS]; // Material maps + MaterialMap *maps; // Material maps array (MAX_MATERIAL_MAPS) float *params; // Material generic parameters (if required) } Material; +// Transformation properties +typedef struct Transform { + Vector3 translation; // Translation + Quaternion rotation; // Rotation + Vector3 scale; // Scale +} Transform; + +// Bone information +typedef struct BoneInfo { + char name[32]; // Bone name + int parent; // Bone parent +} BoneInfo; + // Model type typedef struct Model { - Mesh mesh; // Vertex data buffers (RAM and VRAM) Matrix transform; // Local transform matrix - Material material; // Shader and textures data + + int meshCount; // Number of meshes + Mesh *meshes; // Meshes array + + int materialCount; // Number of materials + Material *materials; // Materials array + int *meshMaterial; // Mesh material number + + // Animation data + int boneCount; // Number of bones + BoneInfo *bones; // Bones information (skeleton) + Transform *bindPose; // Bones base transformation (pose) } Model; +// Model animation +typedef struct ModelAnimation { + int boneCount; // Number of bones + BoneInfo *bones; // Bones information (skeleton) + + int frameCount; // Number of animation frames + Transform **framePoses; // Poses array by frame +} ModelAnimation; + // Ray type (useful for raycast) typedef struct Ray { Vector3 position; // Ray position (origin) @@ -564,42 +394,51 @@ typedef struct RayHitInfo { Vector3 normal; // Surface normal of hit } RayHitInfo; +// Bounding box type +typedef struct BoundingBox { + Vector3 min; // Minimum vertex box-corner + Vector3 max; // Maximum vertex box-corner +} BoundingBox; + // Wave type, defines audio wave data typedef struct Wave { - unsigned int sampleCount; // Number of samples - 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) - void *data; // Buffer data pointer + unsigned int sampleCount; // Total number of samples + 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) + void *data; // Buffer data pointer } Wave; -// Sound source type -typedef struct Sound { - void *audioBuffer; // Pointer to internal data used by the audio system - - unsigned int source; // Audio source id - unsigned int buffer; // Audio buffer id - int format; // Audio format specifier -} Sound; - -// Music type (file streaming from memory) -// NOTE: Anything longer than ~10 seconds should be streamed -typedef struct MusicData *Music; +typedef struct rAudioBuffer rAudioBuffer; // Audio stream type // NOTE: Useful to create custom audio streams not bound to a specific file typedef struct AudioStream { - 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) + 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) - void *audioBuffer; // Pointer to internal data used by the audio system. - - int format; // Audio format specifier - unsigned int source; // Audio source id - unsigned int buffers[2]; // Audio buffers (double buffering) + 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 +} Sound; + +// Music stream type (audio file streaming from memory) +// NOTE: Anything longer than ~10 seconds should be streamed +typedef struct Music { + int ctxType; // Type of music context (audio filetype) + void *ctxData; // Audio context data, depends on type + + unsigned int sampleCount; // Total number of samples + unsigned int loopCount; // Loops count (times music will play), 0 means infinite loop + + AudioStream stream; // Audio stream +} Music; + // Head-Mounted-Display device parameters typedef struct VrDeviceInfo { int hResolution; // HMD horizontal resolution in pixels @@ -617,14 +456,219 @@ typedef struct VrDeviceInfo { //---------------------------------------------------------------------------------- // Enumerators Definition //---------------------------------------------------------------------------------- +// System config flags +// NOTE: Used for bit masks +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; + // Trace log type typedef enum { - LOG_INFO = 1, - LOG_WARNING = 2, - LOG_ERROR = 4, - LOG_DEBUG = 8, - LOG_OTHER = 16 -} LogType; + LOG_ALL = 0, // Display all logs + LOG_TRACE, + LOG_DEBUG, + LOG_INFO, + LOG_WARNING, + LOG_ERROR, + LOG_FATAL, + LOG_NONE // Disable logging +} TraceLogType; + +// Keyboard keys +typedef enum { + // Alphanumeric keys + KEY_APOSTROPHE = 39, + KEY_COMMA = 44, + KEY_MINUS = 45, + KEY_PERIOD = 46, + KEY_SLASH = 47, + KEY_ZERO = 48, + KEY_ONE = 49, + KEY_TWO = 50, + KEY_THREE = 51, + KEY_FOUR = 52, + KEY_FIVE = 53, + KEY_SIX = 54, + KEY_SEVEN = 55, + KEY_EIGHT = 56, + KEY_NINE = 57, + KEY_SEMICOLON = 59, + KEY_EQUAL = 61, + KEY_A = 65, + KEY_B = 66, + KEY_C = 67, + KEY_D = 68, + KEY_E = 69, + KEY_F = 70, + KEY_G = 71, + KEY_H = 72, + KEY_I = 73, + KEY_J = 74, + KEY_K = 75, + KEY_L = 76, + KEY_M = 77, + KEY_N = 78, + KEY_O = 79, + KEY_P = 80, + KEY_Q = 81, + KEY_R = 82, + KEY_S = 83, + KEY_T = 84, + KEY_U = 85, + KEY_V = 86, + KEY_W = 87, + KEY_X = 88, + KEY_Y = 89, + KEY_Z = 90, + + // Function keys + KEY_SPACE = 32, + KEY_ESCAPE = 256, + KEY_ENTER = 257, + KEY_TAB = 258, + KEY_BACKSPACE = 259, + KEY_INSERT = 260, + KEY_DELETE = 261, + KEY_RIGHT = 262, + KEY_LEFT = 263, + KEY_DOWN = 264, + KEY_UP = 265, + KEY_PAGE_UP = 266, + KEY_PAGE_DOWN = 267, + KEY_HOME = 268, + KEY_END = 269, + KEY_CAPS_LOCK = 280, + KEY_SCROLL_LOCK = 281, + KEY_NUM_LOCK = 282, + KEY_PRINT_SCREEN = 283, + KEY_PAUSE = 284, + KEY_F1 = 290, + KEY_F2 = 291, + KEY_F3 = 292, + KEY_F4 = 293, + KEY_F5 = 294, + KEY_F6 = 295, + KEY_F7 = 296, + KEY_F8 = 297, + KEY_F9 = 298, + KEY_F10 = 299, + KEY_F11 = 300, + KEY_F12 = 301, + KEY_LEFT_SHIFT = 340, + KEY_LEFT_CONTROL = 341, + KEY_LEFT_ALT = 342, + KEY_LEFT_SUPER = 343, + KEY_RIGHT_SHIFT = 344, + KEY_RIGHT_CONTROL = 345, + KEY_RIGHT_ALT = 346, + KEY_RIGHT_SUPER = 347, + KEY_KB_MENU = 348, + KEY_LEFT_BRACKET = 91, + KEY_BACKSLASH = 92, + KEY_RIGHT_BRACKET = 93, + KEY_GRAVE = 96, + + // Keypad keys + KEY_KP_0 = 320, + KEY_KP_1 = 321, + KEY_KP_2 = 322, + KEY_KP_3 = 323, + KEY_KP_4 = 324, + KEY_KP_5 = 325, + KEY_KP_6 = 326, + KEY_KP_7 = 327, + KEY_KP_8 = 328, + KEY_KP_9 = 329, + KEY_KP_DECIMAL = 330, + KEY_KP_DIVIDE = 331, + KEY_KP_MULTIPLY = 332, + KEY_KP_SUBTRACT = 333, + KEY_KP_ADD = 334, + KEY_KP_ENTER = 335, + KEY_KP_EQUAL = 336 +} KeyboardKey; + +// Android buttons +typedef enum { + KEY_BACK = 4, + KEY_MENU = 82, + KEY_VOLUME_UP = 24, + KEY_VOLUME_DOWN = 25 +} AndroidButton; + +// Mouse buttons +typedef enum { + MOUSE_LEFT_BUTTON = 0, + MOUSE_RIGHT_BUTTON = 1, + MOUSE_MIDDLE_BUTTON = 2 +} MouseButton; + +// Gamepad number +typedef enum { + GAMEPAD_PLAYER1 = 0, + GAMEPAD_PLAYER2 = 1, + GAMEPAD_PLAYER3 = 2, + GAMEPAD_PLAYER4 = 3 +} GamepadNumber; + +// Gamepad Buttons +typedef enum { + // This is here just for error checking + GAMEPAD_BUTTON_UNKNOWN = 0, + + // This is normally [A,B,X,Y]/[Circle,Triangle,Square,Cross] + // No support for 6 button controllers though.. + GAMEPAD_BUTTON_LEFT_FACE_UP, + GAMEPAD_BUTTON_LEFT_FACE_RIGHT, + GAMEPAD_BUTTON_LEFT_FACE_DOWN, + GAMEPAD_BUTTON_LEFT_FACE_LEFT, + + // This is normally a DPAD + GAMEPAD_BUTTON_RIGHT_FACE_UP, + GAMEPAD_BUTTON_RIGHT_FACE_RIGHT, + GAMEPAD_BUTTON_RIGHT_FACE_DOWN, + GAMEPAD_BUTTON_RIGHT_FACE_LEFT, + + // Triggers + GAMEPAD_BUTTON_LEFT_TRIGGER_1, + GAMEPAD_BUTTON_LEFT_TRIGGER_2, + GAMEPAD_BUTTON_RIGHT_TRIGGER_1, + 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 + + // These are the joystick press in buttons + GAMEPAD_BUTTON_LEFT_THUMB, + GAMEPAD_BUTTON_RIGHT_THUMB +} GamepadButton; + +typedef enum { + // This is here just for error checking + GAMEPAD_AXIS_UNKNOWN = 0, + + // Left stick + GAMEPAD_AXIS_LEFT_X, + GAMEPAD_AXIS_LEFT_Y, + + // Right stick + GAMEPAD_AXIS_RIGHT_X, + GAMEPAD_AXIS_RIGHT_Y, + + // Pressure levels for the back triggers + GAMEPAD_AXIS_LEFT_TRIGGER, // [1..-1] (pressure-level) + GAMEPAD_AXIS_RIGHT_TRIGGER // [1..-1] (pressure-level) +} GamepadAxis; // Shader location point type typedef enum { @@ -658,6 +702,19 @@ typedef enum { #define LOC_MAP_DIFFUSE LOC_MAP_ALBEDO #define LOC_MAP_SPECULAR 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 +} ShaderUniformDataType; + // Material map type typedef enum { MAP_ALBEDO = 0, // MAP_DIFFUSE @@ -671,7 +728,7 @@ typedef enum { MAP_IRRADIANCE, // NOTE: Uses GL_TEXTURE_CUBE_MAP MAP_PREFILTER, // NOTE: Uses GL_TEXTURE_CUBE_MAP MAP_BRDF -} TexmapIndex; +} MaterialMapType; #define MAP_DIFFUSE MAP_ALBEDO #define MAP_SPECULAR MAP_METALNESS @@ -714,25 +771,36 @@ typedef enum { 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 parameters: wrap mode typedef enum { - WRAP_REPEAT = 0, - WRAP_CLAMP, - WRAP_MIRROR + 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; // Font type, defines generation method typedef enum { - FONT_DEFAULT = 0, // Default font generation, anti-aliased - FONT_BITMAP, // Bitmap font generation, no anti-aliasing - FONT_SDF // SDF font generation, requires external shader + FONT_DEFAULT = 0, // Default font generation, anti-aliased + FONT_BITMAP, // Bitmap font generation, no anti-aliasing + FONT_SDF // SDF font generation, requires external shader } FontType; // Color blending modes (pre-defined) typedef enum { - BLEND_ALPHA = 0, - BLEND_ADDITIVE, - BLEND_MULTIPLIED + BLEND_ALPHA = 0, // Blend textures considering alpha (default) + BLEND_ADDITIVE, // Blend textures adding colors + BLEND_MULTIPLIED // Blend textures multiplying colors } BlendMode; // Gestures type @@ -749,7 +817,7 @@ typedef enum { GESTURE_SWIPE_DOWN = 128, GESTURE_PINCH_IN = 256, GESTURE_PINCH_OUT = 512 -} Gestures; +} GestureType; // Camera system modes typedef enum { @@ -766,25 +834,15 @@ typedef enum { CAMERA_ORTHOGRAPHIC } CameraType; -// Head Mounted Display devices -typedef enum { - HMD_DEFAULT_DEVICE = 0, - HMD_OCULUS_RIFT_DK2, - HMD_OCULUS_RIFT_CV1, - HMD_OCULUS_GO, - HMD_VALVE_HTC_VIVE, - HMD_SONY_PSVR -} VrDeviceType; - // Type of n-patch typedef enum { - NPT_9PATCH = 0, // 3x3 - NPT_3PATCH_VERTICAL, // 1x3 - NPT_3PATCH_HORIZONTAL // 3x1 + 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; // Callbacks to be implemented by users -typedef void (*TraceLogCallback)(int msgType, const char *text, va_list args); +typedef void (*TraceLogCallback)(int logType, const char *text, va_list args); #if defined(__cplusplus) extern "C" { // Prevents name mangling of functions @@ -801,26 +859,33 @@ extern "C" { // Prevents name mangling of functions // Window-related functions RLAPI void InitWindow(int width, int height, const char *title); // Initialize window and OpenGL context +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 WindowShouldClose(void); // Check if KEY_ESCAPE pressed or Close icon pressed RLAPI bool IsWindowMinimized(void); // Check if window has been minimized (or lost focus) +RLAPI bool IsWindowResized(void); // Check if window has been resized +RLAPI bool IsWindowHidden(void); // Check if window is currently hidden 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 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) RLAPI void SetWindowMonitor(int monitor); // Set monitor for the current window (fullscreen mode) RLAPI void SetWindowMinSize(int width, int height); // Set window minimum dimensions (for FLAG_WINDOW_RESIZABLE) RLAPI void SetWindowSize(int width, int height); // Set window dimensions +RLAPI void *GetWindowHandle(void); // Get native window handle RLAPI int GetScreenWidth(void); // Get current screen width RLAPI int GetScreenHeight(void); // Get current screen height -RLAPI void *GetWindowHandle(void); // Get native window handle 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 Vector2 GetWindowPosition(void); // Get window position XY on monitor 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 // Cursor-related functions RLAPI void ShowCursor(void); // Shows cursor @@ -839,13 +904,18 @@ RLAPI void BeginMode3D(Camera3D camera); // Initializes 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 EndTextureMode(void); // Ends drawing to render texture +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 // Screen-space-related functions RLAPI Ray GetMouseRay(Vector2 mousePosition, Camera camera); // Returns a ray trace from mouse position -RLAPI Vector2 GetWorldToScreen(Vector3 position, Camera camera); // Returns the screen space position for a 3d world space position RLAPI Matrix GetCameraMatrix(Camera camera); // Returns camera transform matrix (view matrix) +RLAPI Matrix GetCameraMatrix2D(Camera2D camera); // Returns camera 2d transform matrix +RLAPI Vector2 GetWorldToScreen(Vector3 position, Camera camera); // Returns the screen space position for a 3d world space position +RLAPI Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera); // Returns the screen space position for a 2d camera world space position +RLAPI Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera); // Returns the world space position for a 2d camera screen space position -// timing-related functions +// 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 @@ -854,26 +924,30 @@ RLAPI double GetTime(void); // Returns ela // Color-related functions 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 Color GetColor(int hexValue); // Returns a Color struct from hexadecimal value RLAPI Color Fade(Color color, float alpha); // Color fade-in or fade-out, alpha goes from 0.0f to 1.0f // Misc. functions -RLAPI void ShowLogo(void); // Activate raylib logo at startup (can be done with flags) -RLAPI void SetConfigFlags(unsigned char flags); // Setup window configuration flags (view FLAGS) -RLAPI void SetTraceLog(unsigned char types); // Enable trace log message types (bit flags based) -RLAPI void SetTraceLogCallback(TraceLogCallback callback); // Set a trace log callback to enable custom logging bypassing raylib's one -RLAPI void TraceLog(int logType, const char *text, ...); // Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG) +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) // Files management functions 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 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 (memory should be freed) -RLAPI const char *GetDirectoryPath(const char *fileName); // Get full path for a given fileName (uses static 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) +RLAPI const char *GetPrevDirectoryPath(const char *dirPath); // Get previous directory path for a given path (uses static string) 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) @@ -883,6 +957,9 @@ RLAPI char **GetDroppedFiles(int *count); // Get dropped 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) + // Persistent storage management RLAPI void StorageSaveValue(int position, int value); // Save integer value to storage file (to defined position) RLAPI int StorageLoadValue(int position); // Load integer value from storage file (from defined position) @@ -898,8 +975,8 @@ RLAPI bool IsKeyPressed(int key); // Detect if a key RLAPI bool IsKeyDown(int key); // Detect if a key is being pressed 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 int GetKeyPressed(void); // Get latest key 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 // Input-related functions: gamepads RLAPI bool IsGamepadAvailable(int gamepad); // Detect if a gamepad is available @@ -921,8 +998,9 @@ RLAPI bool IsMouseButtonUp(int button); // Detect if a mou RLAPI int GetMouseX(void); // Returns mouse position X RLAPI int GetMouseY(void); // Returns mouse position Y RLAPI Vector2 GetMousePosition(void); // Returns mouse position XY -RLAPI void SetMousePosition(Vector2 position); // Set mouse position XY -RLAPI void SetMouseScale(float scale); // Set mouse scaling +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 // Input-related functions: touch @@ -965,24 +1043,31 @@ 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 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 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 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 DrawRectangle(int posX, int posY, int width, int height, Color color); // Draw a color-filled rectangle RLAPI void DrawRectangleV(Vector2 position, Vector2 size, Color color); // Draw a color-filled rectangle (Vector version) RLAPI void DrawRectangleRec(Rectangle rec, Color color); // Draw a color-filled rectangle -RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color colors[4]); // Draw a color-filled rectangle with pro parameters +RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2);// Draw a vertical-gradient-filled rectangle RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color color1, Color color2);// Draw a horizontal-gradient-filled rectangle RLAPI void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // Draw a gradient-filled rectangle with custom vertex colors RLAPI void DrawRectangleLines(int posX, int posY, int width, int height, Color color); // Draw rectangle outline RLAPI void DrawRectangleLinesEx(Rectangle rec, int lineThick, Color color); // Draw rectangle outline with extended parameters -RLAPI void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw a color-filled triangle -RLAPI void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline +RLAPI void 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 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 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 DrawPolyEx(Vector2 *points, int numPoints, Color color); // Draw a closed polygon defined by points -RLAPI void DrawPolyExLines(Vector2 *points, int numPoints, Color color); // Draw polygon lines RLAPI void SetShapesTexture(Texture2D texture, Rectangle source); // Define default texture used to draw shapes @@ -1008,18 +1093,22 @@ RLAPI void ExportImage(Image image, const char *fileName); RLAPI void ExportImageAsCode(Image image, const char *fileName); // Export image as code file defining an array of bytes 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 RenderTexture2D LoadRenderTexture(int width, int height); // Load texture for rendering (framebuffer) RLAPI void UnloadImage(Image image); // Unload image from CPU memory (RAM) RLAPI void UnloadTexture(Texture2D texture); // Unload texture from GPU memory (VRAM) RLAPI void UnloadRenderTexture(RenderTexture2D target); // Unload render texture from GPU memory (VRAM) RLAPI Color *GetImageData(Image image); // Get pixel data from image as a Color struct array RLAPI Vector4 *GetImageDataNormalized(Image image); // Get pixel data from image as Vector4 array (float normalized) +RLAPI Rectangle GetImageAlphaBorder(Image image, float threshold); // Get image alpha border rectangle RLAPI int GetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes (image or texture) RLAPI Image GetTextureData(Texture2D texture); // Get pixel data from GPU texture and return an Image +RLAPI Image GetScreenData(void); // Get pixel data from screen buffer and return an Image (screenshot) RLAPI void UpdateTexture(Texture2D texture, const void *pixels); // Update GPU texture with new data // Image manipulation functions RLAPI Image ImageCopy(Image image); // Create an image duplicate (useful for transformations) +RLAPI Image ImageFromImage(Image image, Rectangle rec); // Create an image from another image piece RLAPI void ImageToPOT(Image *image, Color fillColor); // Convert image to POT (power-of-two) RLAPI void ImageFormat(Image *image, int newFormat); // Convert image data to desired format RLAPI void ImageAlphaMask(Image *image, Image alphaMask); // Apply alpha mask to image @@ -1035,7 +1124,7 @@ RLAPI void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp); RLAPI Color *ImageExtractPalette(Image image, int maxPaletteSize, int *extractCount); // Extract color palette from image to maximum size (memory should be freed) RLAPI Image ImageText(const char *text, int fontSize, Color color); // Create an image from text (default font) RLAPI Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint); // Create an image from text (custom sprite font) -RLAPI void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec); // Draw a source image within a destination image +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 ImageDrawRectangle(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 ImageDrawText(Image *dst, Vector2 position, const char *text, int fontSize, Color color); // Draw text (default font) within an image (destination) @@ -1071,8 +1160,9 @@ RLAPI void DrawTexture(Texture2D texture, int posX, int posY, Color tint); 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 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 DrawTextureQuad(Texture2D texture, Vector2 tiling, Vector2 offset, Rectangle quad, Color tint); // Draw texture quad with tiling and offset parameters +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 //------------------------------------------------------------------------------------ // Font Loading and Text Drawing Functions (Module: text) @@ -1081,26 +1171,48 @@ RLAPI void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle // Font loading/unloading functions RLAPI Font GetFontDefault(void); // Get the default Font RLAPI Font LoadFont(const char *fileName); // Load font from file into GPU memory (VRAM) -RLAPI Font LoadFontEx(const char *fileName, int fontSize, int charsCount, int *fontChars); // Load font from file with extended parameters +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(CharInfo *chars, int charsCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info +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 UnloadFont(Font font); // Unload Font from GPU memory (VRAM) // Text drawing functions RLAPI void DrawFPS(int posX, int posY); // Shows 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 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 selectText, Color selectBack); // Draw text using font inside rectangle limits with support for text selection // Text misc. functions RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font RLAPI Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing); // Measure string size for Font RLAPI int GetGlyphIndex(Font font, int character); // Get index position for a unicode character on font -// Text string edition functions -RLAPI const char *FormatText(const char *text, ...); // Formatting of text with variables to 'embed' -RLAPI const char *SubText(const char *text, int position, int length); // Get a piece of a text string -RLAPI char **SplitText(char *text, char delimiter, int *strCount); // Split text string into multiple strings (memory should be freed manually!) -RLAPI bool IsEqualText(const char *text1, const char *text2); // Check if two text string are equal +// Text strings management functions (no utf8 strings, only byte chars) +// NOTE: Some strings allocate memory internally for returned strings, just be careful! +RLAPI bool TextIsEqual(const char *text1, const char *text2); // Check if two text string are equal +RLAPI unsigned int TextLength(const char *text); // Get text length, checks for '\0' ending +RLAPI const char *TextFormat(const char *text, ...); // Text formatting with variables (sprintf style) +RLAPI const char *TextSubtext(const char *text, int position, int length); // Get a piece of a text string +RLAPI char *TextReplace(char *text, const char *replace, const char *by); // Replace text string (memory must be freed!) +RLAPI char *TextInsert(const char *text, const char *insert, int position); // Insert text in a position (memory must be freed!) +RLAPI const char *TextJoin(const char **textList, int count, const char *delimiter); // Join text strings with delimiter +RLAPI const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings +RLAPI void TextAppend(char *text, const char *append, int *position); // Append text at specific position and move cursor! +RLAPI int TextFindIndex(const char *text, const char *find); // Find first text occurrence within a string +RLAPI const char *TextToUpper(const char *text); // Get upper case version of provided string +RLAPI const char *TextToLower(const char *text); // Get lower case version of provided string +RLAPI const char *TextToPascal(const char *text); // Get Pascal case notation version of provided string +RLAPI int TextToInteger(const char *text); // Get integer value from text (negative values not supported) +RLAPI char *TextToUtf8(int *codepoints, int length); // Encode text codepoint into utf8 text (memory must be freed!) + +// UTF8 text strings management functions +RLAPI int *GetCodepoints(const char *text, int *count); // Get all codepoints in a string, codepoints count returned by parameters +RLAPI int GetCodepointsCount(const char *text); // Get total number of characters (codepoints) in a UTF8 encoded string +RLAPI int GetNextCodepoint(const char *text, int *bytesProcessed); // Returns next codepoint in a UTF8 encoded string; 0x3f('?') is returned on failure +RLAPI const char *CodepointToUtf8(int codepoint, int *byteLength); // Encode codepoint into utf8 text (char array length returned as parameter) //------------------------------------------------------------------------------------ // Basic 3d Shapes Drawing Functions (Module: models) @@ -1112,6 +1224,7 @@ RLAPI void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, floa RLAPI void DrawCube(Vector3 position, float width, float height, float length, Color color); // Draw cube RLAPI void DrawCubeV(Vector3 position, Vector3 size, Color color); // Draw cube (Vector version) RLAPI void DrawCubeWires(Vector3 position, float width, float height, float length, Color color); // Draw cube wires +RLAPI void DrawCubeWiresV(Vector3 position, Vector3 size, Color color); // Draw cube wires (Vector version) RLAPI void DrawCubeTexture(Texture2D texture, Vector3 position, float width, float height, float length, Color color); // Draw cube textured RLAPI void DrawSphere(Vector3 centerPos, float radius, Color color); // Draw sphere RLAPI void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere with extended parameters @@ -1129,21 +1242,30 @@ RLAPI void DrawGizmo(Vector3 position); //------------------------------------------------------------------------------------ // Model loading/unloading functions -RLAPI Model LoadModel(const char *fileName); // Load model from files (mesh and material) -RLAPI Model LoadModelFromMesh(Mesh mesh); // Load model from generated mesh +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) // Mesh loading/unloading functions -RLAPI Mesh LoadMesh(const char *fileName); // Load mesh from file -RLAPI void UnloadMesh(Mesh *mesh); // Unload mesh from memory (RAM and/or VRAM) +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) -// 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 +// 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 + +// 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 // 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) @@ -1154,10 +1276,10 @@ RLAPI Mesh GenMeshKnot(float radius, float size, int radSeg, int sides); 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 -// Material loading/unloading functions -RLAPI Material LoadMaterial(const char *fileName); // Load material from file -RLAPI Material LoadMaterialDefault(void); // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) -RLAPI void UnloadMaterial(Material material); // Unload material from GPU memory (VRAM) +// 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 // Model drawing functions RLAPI void DrawModel(Model model, Vector3 position, float scale, Color tint); // Draw a model (with texture if set) @@ -1171,11 +1293,11 @@ RLAPI void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle sourceRe // Collision detection functions RLAPI bool CheckCollisionSpheres(Vector3 centerA, float radiusA, Vector3 centerB, float radiusB); // Detect collision between two spheres RLAPI bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2); // Detect collision between two bounding boxes -RLAPI bool CheckCollisionBoxSphere(BoundingBox box, Vector3 centerSphere, float radiusSphere); // Detect collision between box and sphere -RLAPI bool CheckCollisionRaySphere(Ray ray, Vector3 spherePosition, float sphereRadius); // Detect collision between ray and sphere -RLAPI bool CheckCollisionRaySphereEx(Ray ray, Vector3 spherePosition, float sphereRadius, Vector3 *collisionPoint); // Detect collision between ray and sphere, returns collision point +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 GetCollisionRayModel(Ray ray, Model *model); // Get collision info between ray and model +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) @@ -1187,27 +1309,29 @@ RLAPI RayHitInfo GetCollisionRayGround(Ray ray, float groundHeight); // Shader loading/unloading functions RLAPI char *LoadText(const char *fileName); // Load chars array from text file RLAPI Shader LoadShader(const char *vsFileName, const char *fsFileName); // Load shader from files and bind default locations -RLAPI Shader LoadShaderCode(char *vsCode, char *fsCode); // Load shader from code strings 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 // Shader configuration functions -RLAPI int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location -RLAPI void SetShaderValue(Shader shader, int uniformLoc, const float *value, int size); // Set shader uniform value (float) -RLAPI void SetShaderValuei(Shader shader, int uniformLoc, const int *value, int size); // Set shader uniform value (int) -RLAPI void SetShaderValueMatrix(Shader shader, int uniformLoc, Matrix mat); // Set shader uniform value (matrix 4x4) +RLAPI 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(); // Get 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 skyHDR, 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, Texture2D cubemap, int size); // Generate BRDF 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 @@ -1216,12 +1340,11 @@ RLAPI void BeginBlendMode(int mode); // Beg RLAPI void EndBlendMode(void); // End blending mode (reset to default: alpha blending) // VR control functions -RLAPI VrDeviceInfo GetVrDeviceInfo(int vrDeviceType); // Get VR device information for some standard devices -RLAPI void InitVrSimulator(VrDeviceInfo info); // Init VR simulator for selected device parameters +RLAPI void InitVrSimulator(void); // Init VR simulator for selected device parameters RLAPI void CloseVrSimulator(void); // Close VR simulator for current device -RLAPI bool IsVrSimulatorReady(void); // Detect if VR simulator is ready -RLAPI void SetVrDistortionShader(Shader shader); // Set VR distortion shader for stereoscopic rendering RLAPI void UpdateVrTracking(Camera *camera); // Update VR tracking (position and orientation) and camera +RLAPI void 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 @@ -1238,7 +1361,6 @@ 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 LoadWaveEx(void *data, int sampleCount, int sampleRate, int sampleSize, int channels); // Load wave data from raw array data 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 @@ -1249,9 +1371,12 @@ RLAPI void ExportWaveAsCode(Wave wave, const char *fileName); // Export // Wave/Sound management functions RLAPI void PlaySound(Sound sound); // Play a sound +RLAPI void StopSound(Sound sound); // Stop playing a sound RLAPI void PauseSound(Sound sound); // Pause a sound RLAPI void ResumeSound(Sound sound); // Resume a paused sound -RLAPI void StopSound(Sound sound); // Stop playing a sound +RLAPI void PlaySoundMulti(Sound sound); // Play a sound (using multichannel buffer pool) +RLAPI void StopSoundMulti(void); // Stop any sound playing (using multichannel buffer pool) +RLAPI int GetSoundsPlaying(void); // Get number of sounds playing in the multichannel RLAPI bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing RLAPI void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) RLAPI void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) @@ -1279,7 +1404,7 @@ RLAPI float GetMusicTimePlayed(Music music); // Get cur RLAPI AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Init audio stream (to stream raw audio pcm data) RLAPI void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount); // Update audio stream buffers with data RLAPI void CloseAudioStream(AudioStream stream); // Close audio stream and free memory -RLAPI bool IsAudioBufferProcessed(AudioStream stream); // Check if any audio stream buffers requires refill +RLAPI bool IsAudioStreamProcessed(AudioStream stream); // Check if any audio stream buffers requires refill RLAPI void PlayAudioStream(AudioStream stream); // Play audio stream RLAPI void PauseAudioStream(AudioStream stream); // Pause audio stream RLAPI void ResumeAudioStream(AudioStream stream); // Resume audio stream @@ -1288,6 +1413,12 @@ RLAPI void StopAudioStream(AudioStream stream); // Stop au RLAPI void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) RLAPI void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) +//------------------------------------------------------------------------------------ +// 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 42a45ad..12ea76b 100644 --- a/raylib/raymath.h +++ b/raylib/raymath.h @@ -20,7 +20,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2015-2017 Ramon Santamaria (@raysan5) +* Copyright (c) 2015-2019 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. @@ -56,7 +56,7 @@ #if defined(RAYMATH_IMPLEMENTATION) #if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) #define RMDEF __declspec(dllexport) extern inline // We are building raylib as a Win32 shared library (.dll). - #elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) + #elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) #define RMDEF __declspec(dllimport) // We are using raylib as a Win32 shared library (.dll) #else #define RMDEF extern inline // Provide external definition @@ -113,7 +113,7 @@ float y; float z; } Vector3; - + // Quaternion type typedef struct Quaternion { float x; @@ -148,7 +148,7 @@ RMDEF float Clamp(float value, float min, float max) return res > max ? max : res; } -// Calculate linear interpolation between two vectors +// Calculate linear interpolation between two floats RMDEF float Lerp(float start, float end, float amount) { return start + amount*(end - start); @@ -225,8 +225,8 @@ RMDEF Vector2 Vector2Scale(Vector2 v, float scale) // Multiply vector by vector RMDEF Vector2 Vector2MultiplyV(Vector2 v1, Vector2 v2) { - Vector2 result = { v1.x*v2.x, v1.y*v2.y }; - return result; + Vector2 result = { v1.x*v2.x, v1.y*v2.y }; + return result; } // Negate vector @@ -246,8 +246,8 @@ RMDEF Vector2 Vector2Divide(Vector2 v, float div) // Divide vector by vector RMDEF Vector2 Vector2DivideV(Vector2 v1, Vector2 v2) { - Vector2 result = { v1.x/v2.x, v1.y/v2.y }; - return result; + Vector2 result = { v1.x/v2.x, v1.y/v2.y }; + return result; } // Normalize provided vector @@ -388,15 +388,15 @@ RMDEF Vector3 Vector3Negate(Vector3 v) // Divide vector by a float value RMDEF Vector3 Vector3Divide(Vector3 v, float div) { - Vector3 result = { v.x / div, v.y / div, v.z / div }; - return result; + Vector3 result = { v.x / div, v.y / div, v.z / div }; + return result; } // Divide vector by vector RMDEF Vector3 Vector3DivideV(Vector3 v1, Vector3 v2) { - Vector3 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z }; - return result; + Vector3 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z }; + return result; } // Normalize provided vector @@ -440,7 +440,7 @@ RMDEF Vector3 Vector3Transform(Vector3 v, Matrix mat) result.z = mat.m2*x + mat.m6*y + mat.m10*z + mat.m14; return result; -}; +} // Transform a vector by quaternion rotation RMDEF Vector3 Vector3RotateByQuaternion(Vector3 v, Quaternion q) @@ -794,6 +794,33 @@ RMDEF Matrix MatrixRotate(Vector3 axis, float angle) return result; } +// Returns xyz-rotation matrix (angles in radians) +RMDEF Matrix MatrixRotateXYZ(Vector3 ang) +{ + Matrix result = MatrixIdentity(); + + float cosz = cosf(-ang.z); + float sinz = sinf(-ang.z); + float cosy = cosf(-ang.y); + float siny = sinf(-ang.y); + 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.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; + + return result; +} + // Returns x-rotation matrix (angle in radians) RMDEF Matrix MatrixRotateX(float angle) { @@ -1159,7 +1186,7 @@ RMDEF Quaternion QuaternionFromVector3ToVector3(Vector3 from, Vector3 to) // Above lines are equivalent to: //Quaternion result = QuaternionNlerp(q, QuaternionIdentity(), 0.5f); - return result; + return result; } // Returns a quaternion for a given rotation matrix @@ -1320,21 +1347,21 @@ RMDEF void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle // Returns he quaternion equivalent to Euler angles RMDEF Quaternion QuaternionFromEuler(float roll, float pitch, float yaw) { - Quaternion q = { 0 }; + 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(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); - q.x = x1*y0*z0 - x0*y1*z1; - q.y = x0*y1*z0 + x1*y0*z1; - q.z = x0*y0*z1 - x1*y1*z0; - q.w = x0*y0*z0 + x1*y1*z1; + q.x = x1*y0*z0 - x0*y1*z1; + q.y = x0*y1*z0 + x1*y0*z1; + q.z = x0*y0*z1 - x1*y1*z0; + q.w = x0*y0*z0 + x1*y1*z1; - return q; + return q; } // Return the Euler angles equivalent to quaternion (roll, pitch, yaw) @@ -1343,21 +1370,21 @@ RMDEF Vector3 QuaternionToEuler(Quaternion q) { Vector3 result = { 0 }; - // roll (x-axis rotation) - float x0 = 2.0f*(q.w*q.x + q.y*q.z); - float x1 = 1.0f - 2.0f*(q.x*q.x + q.y*q.y); - result.x = atan2f(x0, x1)*RAD2DEG; + // roll (x-axis rotation) + float x0 = 2.0f*(q.w*q.x + q.y*q.z); + float x1 = 1.0f - 2.0f*(q.x*q.x + q.y*q.y); + result.x = atan2f(x0, x1)*RAD2DEG; - // pitch (y-axis rotation) - float y0 = 2.0f*(q.w*q.y - q.z*q.x); - y0 = y0 > 1.0f ? 1.0f : y0; - y0 = y0 < -1.0f ? -1.0f : y0; - result.y = asinf(y0)*RAD2DEG; + // pitch (y-axis rotation) + float y0 = 2.0f*(q.w*q.y - q.z*q.x); + y0 = y0 > 1.0f ? 1.0f : y0; + y0 = y0 < -1.0f ? -1.0f : y0; + result.y = asinf(y0)*RAD2DEG; - // yaw (z-axis rotation) - float z0 = 2.0f*(q.w*q.z + q.x*q.y); - float z1 = 1.0f - 2.0f*(q.y*q.y + q.z*q.z); - result.z = atan2f(z0, z1)*RAD2DEG; + // yaw (z-axis rotation) + float z0 = 2.0f*(q.w*q.z + q.x*q.y); + float z1 = 1.0f - 2.0f*(q.y*q.y + q.z*q.z); + result.z = atan2f(z0, z1)*RAD2DEG; return result; } diff --git a/raylib/rlgl.h b/raylib/rlgl.h index 7f2a171..5c71c47 100644 --- a/raylib/rlgl.h +++ b/raylib/rlgl.h @@ -32,9 +32,6 @@ * #define SUPPORT_VR_SIMULATOR * Support VR simulation functionality (stereo rendering) * -* #define SUPPORT_DISTORTION_SHADER -* Include stereo rendering distortion shader (embedded) -* * DEPENDENCIES: * raymath - 3D math functionality (Vector3, Matrix, Quaternion) * GLAD - OpenGL extensions loading (OpenGL 3.3 Core only) @@ -42,7 +39,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2018 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2019 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. @@ -67,6 +64,25 @@ #if defined(RLGL_STANDALONE) #define RAYMATH_STANDALONE #define RAYMATH_HEADER_ONLY + + #if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __declspec(dllexport) // We are building raylib as a Win32 shared library (.dll) + #elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) + #define RLAPI __declspec(dllimport) // We are using raylib as a Win32 shared library (.dll) + #else + #define RLAPI // We are building or using raylib as a static library (or Linux shared library) + #endif + + // Allow custom memory allocators + #ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) + #endif + #ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) + #endif + #ifndef RL_FREE + #define RL_FREE(p) free(p) + #endif #else #include "raylib.h" // Required for: Model, Shader, Texture2D, TraceLog() #endif @@ -102,18 +118,23 @@ // Defines and Macros //---------------------------------------------------------------------------------- #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) - // NOTE: This is the maximum amount of lines, triangles and quads per frame, be careful! - #define MAX_LINES_BATCH 8192 - #define MAX_TRIANGLES_BATCH 4096 - #define MAX_QUADS_BATCH 8192 + // This is the maximum amount of elements (quads) per batch + // NOTE: Be careful with text, every letter maps to a quad + #define MAX_BATCH_ELEMENTS 8192 #elif defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: Reduce memory sizes for embedded systems (RPI and HTML5) + // We reduce memory sizes for embedded systems (RPI and HTML5) // NOTE: On HTML5 (emscripten) this is allocated on heap, by default it's only 16MB!...just take care... - #define MAX_LINES_BATCH 1024 // Critical for wire shapes (sphere) - #define MAX_TRIANGLES_BATCH 2048 // Critical for some shapes (sphere) - #define MAX_QUADS_BATCH 1024 // Be careful with text, every letter maps a quad + #define MAX_BATCH_ELEMENTS 2048 #endif +#define MAX_BATCH_BUFFERING 1 // Max number of buffers for batching (multi-buffering) +#define MAX_MATRIX_STACK_SIZE 32 // Max size of Matrix stack +#define MAX_DRAWCALL_REGISTERED 256 // Max draws by state changes (mode, texture) + +// Shader and material limits +#define MAX_SHADER_LOCATIONS 32 // Maximum number of predefined locations stored in shader struct +#define MAX_MATERIAL_MAPS 12 // Maximum number of texture maps stored in shader struct + // 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 @@ -130,7 +151,8 @@ #define RL_WRAP_REPEAT 0x2901 // GL_REPEAT #define RL_WRAP_CLAMP 0x812F // GL_CLAMP_TO_EDGE -#define RL_WRAP_CLAMP_MIRROR 0x8742 // GL_MIRROR_CLAMP_EXT +#define RL_WRAP_MIRROR_REPEAT 0x8370 // GL_MIRRORED_REPEAT +#define RL_WRAP_MIRROR_CLAMP 0x8742 // GL_MIRROR_CLAMP_EXT // Matrix modes (equivalent to OpenGL) #define RL_MODELVIEW 0x1700 // GL_MODELVIEW @@ -163,14 +185,6 @@ typedef unsigned char byte; unsigned char a; } Color; - // Rectangle type - typedef struct Rectangle { - int x; - int y; - int width; - int height; - } Rectangle; - // Texture2D type // NOTE: Data stored in GPU memory typedef struct Texture2D { @@ -181,13 +195,23 @@ 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; // Render texture (fbo) id + 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 @@ -201,14 +225,14 @@ typedef unsigned char byte; unsigned short *indices;// vertex indices (in case vertex data comes indexed) // Animation vertex data - float *baseVertices; // Vertex base position (required to apply bones transformations) - float *baseNormals; // Vertex base normals (required to apply bones transformations) - float *weightBias; // Vertex weight bias - int *weightId; // Vertex weight id + 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[7]; // OpenGL Vertex Buffer Objects id (7 types of vertex data) + unsigned int *vboId; // OpenGL Vertex Buffer Objects id (7 types of vertex data) } Mesh; // Shader and material limits @@ -217,8 +241,8 @@ typedef unsigned char byte; // Shader type (generic) typedef struct Shader { - unsigned int id; // Shader program id - int locs[MAX_SHADER_LOCATIONS]; // Shader locations array + unsigned int id; // Shader program id + int *locs; // Shader locations array (MAX_SHADER_LOCATIONS) } Shader; // Material texture map @@ -231,7 +255,7 @@ typedef unsigned char byte; // Material type (generic) typedef struct Material { Shader shader; // Material shader - MaterialMap maps[MAX_MATERIAL_MAPS]; // Material maps + MaterialMap *maps; // Material maps (MAX_MATERIAL_MAPS) float *params; // Material generic parameters (if required) } Material; @@ -257,13 +281,26 @@ typedef unsigned char byte; 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_INFO = 0, - LOG_ERROR, - LOG_WARNING, + LOG_ALL, + LOG_TRACE, LOG_DEBUG, - LOG_OTHER + LOG_INFO, + LOG_WARNING, + LOG_ERROR, + LOG_FATAL, + LOG_NONE } TraceLogType; // Texture formats (support depends on OpenGL version) @@ -303,13 +340,6 @@ typedef unsigned char byte; FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x } TextureFilterMode; - // Texture parameters: wrap mode - typedef enum { - WRAP_REPEAT = 0, - WRAP_CLAMP, - WRAP_MIRROR - } TextureWrapMode; - // Color blending modes (pre-defined) typedef enum { BLEND_ALPHA = 0, @@ -346,6 +376,19 @@ typedef unsigned char byte; LOC_MAP_BRDF } ShaderLocationIndex; + // 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 + } ShaderUniformDataType; + #define LOC_MAP_DIFFUSE LOC_MAP_ALBEDO #define LOC_MAP_SPECULAR LOC_MAP_METALNESS @@ -362,20 +405,10 @@ typedef unsigned char byte; MAP_IRRADIANCE, // NOTE: Uses GL_TEXTURE_CUBE_MAP MAP_PREFILTER, // NOTE: Uses GL_TEXTURE_CUBE_MAP MAP_BRDF - } TexmapIndex; + } MaterialMapType; #define MAP_DIFFUSE MAP_ALBEDO #define MAP_SPECULAR MAP_METALNESS - - // VR Head Mounted Display devices - typedef enum { - HMD_DEFAULT_DEVICE = 0, - HMD_OCULUS_RIFT_DK2, - HMD_OCULUS_RIFT_CV1, - HMD_OCULUS_GO, - HMD_VALVE_HTC_VIVE, - HMD_SONY_PSVR - } VrDevice; #endif #if defined(__cplusplus) @@ -385,82 +418,96 @@ extern "C" { // Prevents name mangling of functions //------------------------------------------------------------------------------------ // Functions Declaration - Matrix operations //------------------------------------------------------------------------------------ -void rlMatrixMode(int mode); // Choose the current matrix to be transformed -void rlPushMatrix(void); // Push the current matrix to stack -void rlPopMatrix(void); // Pop lattest inserted matrix from stack -void rlLoadIdentity(void); // Reset current matrix to identity matrix -void rlTranslatef(float x, float y, float z); // Multiply the current matrix by a translation matrix -void rlRotatef(float angleDeg, float x, float y, float z); // Multiply the current matrix by a rotation matrix -void rlScalef(float x, float y, float z); // Multiply the current matrix by a scaling matrix -void rlMultMatrixf(float *matf); // Multiply the current matrix by another matrix -void rlFrustum(double left, double right, double bottom, double top, double near, double far); -void rlOrtho(double left, double right, double bottom, double top, double near, double far); -void rlViewport(int x, int y, int width, int height); // Set the viewport area +RLAPI void rlMatrixMode(int mode); // Choose the current matrix to be transformed +RLAPI void rlPushMatrix(void); // Push the current matrix to stack +RLAPI void rlPopMatrix(void); // Pop lattest inserted matrix from stack +RLAPI void rlLoadIdentity(void); // Reset current matrix to identity matrix +RLAPI void rlTranslatef(float x, float y, float z); // Multiply the current matrix by a translation matrix +RLAPI void rlRotatef(float angleDeg, float x, float y, float z); // Multiply the current matrix by a rotation matrix +RLAPI void rlScalef(float x, float y, float z); // Multiply the current matrix by a scaling matrix +RLAPI void rlMultMatrixf(float *matf); // Multiply the current matrix by another matrix +RLAPI void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar); +RLAPI void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar); +RLAPI void rlViewport(int x, int y, int width, int height); // Set the viewport area //------------------------------------------------------------------------------------ // Functions Declaration - Vertex level operations //------------------------------------------------------------------------------------ -void rlBegin(int mode); // Initialize drawing mode (how to organize vertex) -void rlEnd(void); // Finish vertex providing -void rlVertex2i(int x, int y); // Define one vertex (position) - 2 int -void rlVertex2f(float x, float y); // Define one vertex (position) - 2 float -void rlVertex3f(float x, float y, float z); // Define one vertex (position) - 3 float -void rlTexCoord2f(float x, float y); // Define one vertex (texture coordinate) - 2 float -void rlNormal3f(float x, float y, float z); // Define one vertex (normal) - 3 float -void rlColor4ub(byte r, byte g, byte b, byte a); // Define one vertex (color) - 4 byte -void rlColor3f(float x, float y, float z); // Define one vertex (color) - 3 float -void rlColor4f(float x, float y, float z, float w); // Define one vertex (color) - 4 float +RLAPI void rlBegin(int mode); // Initialize drawing mode (how to organize vertex) +RLAPI void rlEnd(void); // Finish vertex providing +RLAPI void rlVertex2i(int x, int y); // Define one vertex (position) - 2 int +RLAPI void rlVertex2f(float x, float y); // Define one vertex (position) - 2 float +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 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 //------------------------------------------------------------------------------------ -void rlEnableTexture(unsigned int id); // Enable texture usage -void rlDisableTexture(void); // Disable texture usage -void rlTextureParameters(unsigned int id, int param, int value); // Set texture parameters (filter, wrap) -void rlEnableRenderTexture(unsigned int id); // Enable render texture (fbo) -void rlDisableRenderTexture(void); // Disable render texture (fbo), return to default framebuffer -void rlEnableDepthTest(void); // Enable depth test -void rlDisableDepthTest(void); // Disable depth test -void rlEnableWireMode(void); // Enable wire mode -void rlDisableWireMode(void); // Disable wire mode -void rlDeleteTextures(unsigned int id); // Delete OpenGL texture from GPU -void rlDeleteRenderTextures(RenderTexture2D target); // Delete render textures (fbo) from GPU -void rlDeleteShader(unsigned int id); // Delete OpenGL shader program from GPU -void rlDeleteVertexArrays(unsigned int id); // Unload vertex data (VAO) from GPU memory -void rlDeleteBuffers(unsigned int id); // Unload vertex data (VBO) from GPU memory -void rlClearColor(byte r, byte g, byte b, byte a); // Clear color buffer with color -void rlClearScreenBuffers(void); // Clear used screen buffers (color and depth) +RLAPI void rlEnableTexture(unsigned int id); // Enable texture usage +RLAPI void rlDisableTexture(void); // Disable texture usage +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 //------------------------------------------------------------------------------------ // Functions Declaration - rlgl functionality //------------------------------------------------------------------------------------ -void rlglInit(int width, int height); // Initialize rlgl (buffers, shaders, textures, states) -void rlglClose(void); // De-inititialize rlgl (buffers, shaders, textures) -void rlglDraw(void); // Update and Draw default buffers (lines, triangles, quads) +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 -int rlGetVersion(void); // Returns current OpenGL version -bool rlCheckBufferLimit(int type, int vCount); // Check internal buffer overflow for a given number of vertex -void rlSetDebugMarker(const char *text); // Set debug marker for analysis -void rlLoadExtensions(void *loader); // Load OpenGL extensions -Vector3 rlUnproject(Vector3 source, Matrix proj, Matrix view); // Get world coordinates from screen coordinates +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 rlLoadExtensions(void *loader); // Load OpenGL extensions +RLAPI Vector3 rlUnproject(Vector3 source, Matrix proj, Matrix view); // Get world coordinates from screen coordinates // Textures data management -unsigned int rlLoadTexture(void *data, int width, int height, int format, int mipmapCount); // Load texture in GPU -void rlUpdateTexture(unsigned int id, int width, int height, int format, const void *data); // Update GPU texture with new data -void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType); // Get OpenGL internal formats -void rlUnloadTexture(unsigned int id); // Unload texture from GPU memory +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 rlLoadTextureCubemap(void *data, int size, int format); // Load texture cubemap +RLAPI void rlUpdateTexture(unsigned int id, 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 -void rlGenerateMipmaps(Texture2D *texture); // Generate mipmap data for selected texture -void *rlReadTexturePixels(Texture2D texture); // Read texture pixel data -unsigned char *rlReadScreenPixels(int width, int height); // Read screen pixel data (color buffer) -RenderTexture2D rlLoadRenderTexture(int width, int height); // Load a texture to be used for rendering (fbo with color and depth attachments) +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 // Vertex data management -void rlLoadMesh(Mesh *mesh, bool dynamic); // Upload vertex data into GPU and provided VAO/VBO ids -void rlUpdateMesh(Mesh mesh, int buffer, int numVertex); // Update vertex data on GPU (upload new data to one buffer) -void rlDrawMesh(Mesh mesh, Material material, Matrix transform); // Draw a 3d mesh with material and transform -void rlUnloadMesh(Mesh *mesh); // Unload mesh data from CPU and GPU +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 num); // Update vertex or index data on GPU (upload new data to one buffer) +RLAPI void rlUpdateMeshAt(Mesh mesh, int buffer, int num, 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 // 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 @@ -471,49 +518,48 @@ void rlUnloadMesh(Mesh *mesh); // Unload me // NOTE: This functions are useless when using OpenGL 1.1 //------------------------------------------------------------------------------------ // Shader loading/unloading functions -char *LoadText(const char *fileName); // Load chars array from text file -Shader LoadShader(const char *vsFileName, const char *fsFileName); // Load shader from files and bind default locations -Shader LoadShaderCode(char *vsCode, char *fsCode); // Load shader from code strings and bind default locations -void UnloadShader(Shader shader); // Unload shader from GPU memory (VRAM) +RLAPI char *LoadText(const char *fileName); // Load chars array from text file +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) -Shader GetShaderDefault(void); // Get default shader -Texture2D GetTextureDefault(void); // Get default texture +RLAPI Shader GetShaderDefault(void); // Get default shader +RLAPI Texture2D GetTextureDefault(void); // Get default texture // Shader configuration functions -int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location -void SetShaderValue(Shader shader, int uniformLoc, const float *value, int size); // Set shader uniform value (float) -void SetShaderValuei(Shader shader, int uniformLoc, const int *value, int size); // Set shader uniform value (int) -void SetShaderValueMatrix(Shader shader, int uniformLoc, Matrix mat); // Set shader uniform value (matrix 4x4) -void SetMatrixProjection(Matrix proj); // Set a custom projection matrix (replaces internal projection matrix) -void SetMatrixModelview(Matrix view); // Set a custom modelview matrix (replaces internal modelview matrix) -Matrix GetMatrixModelview(); // Get internal modelview matrix +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 -Texture2D GenTextureCubemap(Shader shader, Texture2D skyHDR, int size); // Generate cubemap texture from HDR texture -Texture2D GenTextureIrradiance(Shader shader, Texture2D cubemap, int size); // Generate irradiance texture using cubemap data -Texture2D GenTexturePrefilter(Shader shader, Texture2D cubemap, int size); // Generate prefilter texture using cubemap data -Texture2D GenTextureBRDF(Shader shader, Texture2D cubemap, int size); // Generate BRDF texture using cubemap data +RLAPI Texture2D GenTextureCubemap(Shader shader, Texture2D skyHDR, 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 -void BeginShaderMode(Shader shader); // Begin custom shader drawing -void EndShaderMode(void); // End custom shader drawing (use default shader) -void BeginBlendMode(int mode); // Begin blending mode (alpha, additive, multiplied) -void EndBlendMode(void); // End blending mode (reset to default: alpha blending) +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 -VrDeviceInfo GetVrDeviceInfo(int vrDeviceType); // Get VR device information for some standard devices -void InitVrSimulator(VrDeviceInfo info); // Init VR simulator for selected device parameters -void CloseVrSimulator(void); // Close VR simulator for current device -bool IsVrSimulatorReady(void); // Detect if VR simulator is ready -void SetVrDistortionShader(Shader shader); // Set VR distortion shader for stereoscopic rendering -void UpdateVrTracking(Camera *camera); // Update VR tracking (position and orientation) and camera -void ToggleVrMode(void); // Enable/Disable VR experience -void BeginVrDrawing(void); // Begin VR simulator stereo rendering -void EndVrDrawing(void); // End VR simulator stereo rendering +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 -void TraceLog(int msgType, const char *text, ...); // Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG) -int GetPixelDataSize(int width, int height, int format);// Get pixel data size in bytes (image or texture) +RLAPI void TraceLog(int msgType, const char *text, ...); // Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG) +RLAPI int GetPixelDataSize(int width, int height, int format);// Get pixel data size in bytes (image or texture) #endif #if defined(__cplusplus) @@ -530,11 +576,11 @@ int GetPixelDataSize(int width, int height, int format);// Get pixel data size i #if defined(RLGL_IMPLEMENTATION) -#if defined(RLGL_STANDALONE) - #define SUPPORT_VR_SIMULATOR - #define SUPPORT_DISTORTION_SHADER -#else - #include "config.h" // rlgl module configuration +#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 + #endif #endif #include // Required for: fopen(), fclose(), fread()... [Used only on LoadText()] @@ -599,11 +645,6 @@ int GetPixelDataSize(int width, int height, int format);// Get pixel data size i //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -#define MATRIX_STACK_SIZE 16 // Matrix stack max size -#define MAX_DRAWS_BY_TEXTURE 256 // Draws are organized by texture changes -#define TEMP_VERTEX_BUFFER_SIZE 4096 // Temporal Vertex Buffer (required for vertex-transformations) - // NOTE: Every vertex are 3 floats (12 bytes) - #ifndef GL_SHADING_LANGUAGE_VERSION #define GL_SHADING_LANGUAGE_VERSION 0x8B8C #endif @@ -698,172 +739,90 @@ typedef struct DynamicBuffer { } DynamicBuffer; // Draw call type -// NOTE: Used to track required draw-calls, organized by texture typedef struct DrawCall { - int vertexCount; - GLuint vaoId; - GLuint textureId; - GLuint shaderId; + 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 + // TODO: Support additional texture units? - Matrix projection; - Matrix modelview; - - // TODO: Store additional draw state data - //int blendMode; - //Guint fboId; + //Matrix projection; // Projection matrix for this draw + //Matrix modelview; // Modelview matrix for this draw } DrawCall; #if defined(SUPPORT_VR_SIMULATOR) // VR Stereo rendering configuration for simulator typedef struct VrStereoConfig { - RenderTexture2D stereoFbo; // VR stereo rendering framebuffer Shader distortionShader; // VR stereo rendering distortion shader - Rectangle eyesViewport[2]; // VR stereo rendering eyes viewports Matrix eyesProjection[2]; // VR stereo rendering eyes projection matrices Matrix eyesViewOffset[2]; // VR stereo rendering eyes view offset matrices + 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 //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- -#if !defined(GRAPHICS_API_OPENGL_11) && defined(SUPPORT_DISTORTION_SHADER) - // Distortion shader embedded - static char distortionFShaderStr[] = - #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" - #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" - #if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) - "uniform vec2 leftLensCenter; \n" - "uniform vec2 rightLensCenter; \n" - "uniform vec2 leftScreenCenter; \n" - "uniform vec2 rightScreenCenter; \n" - "uniform vec2 scale; \n" - "uniform vec2 scaleIn; \n" - "uniform vec4 hmdWarpParam; \n" - "uniform vec4 chromaAbParam; \n" - #elif defined(GRAPHICS_API_OPENGL_33) - "uniform vec2 leftLensCenter = vec2(0.288, 0.5); \n" - "uniform vec2 rightLensCenter = vec2(0.712, 0.5); \n" - "uniform vec2 leftScreenCenter = vec2(0.25, 0.5); \n" - "uniform vec2 rightScreenCenter = vec2(0.75, 0.5); \n" - "uniform vec2 scale = vec2(0.25, 0.45); \n" - "uniform vec2 scaleIn = vec2(4, 2.2222); \n" - "uniform vec4 hmdWarpParam = vec4(1, 0.22, 0.24, 0); \n" - "uniform vec4 chromaAbParam = vec4(0.996, -0.004, 1.014, 0.0); \n" - #endif - "void main() \n" - "{ \n" - " vec2 lensCenter = fragTexCoord.x < 0.5 ? leftLensCenter : rightLensCenter; \n" - " vec2 screenCenter = fragTexCoord.x < 0.5 ? leftScreenCenter : rightScreenCenter; \n" - " vec2 theta = (fragTexCoord - lensCenter)*scaleIn; \n" - " float rSq = theta.x*theta.x + theta.y*theta.y; \n" - " vec2 theta1 = theta*(hmdWarpParam.x + hmdWarpParam.y*rSq + hmdWarpParam.z*rSq*rSq + hmdWarpParam.w*rSq*rSq*rSq); \n" - " vec2 thetaBlue = theta1*(chromaAbParam.z + chromaAbParam.w*rSq); \n" - " vec2 tcBlue = lensCenter + scale*thetaBlue; \n" - " if (any(bvec2(clamp(tcBlue, screenCenter - vec2(0.25, 0.5), screenCenter + vec2(0.25, 0.5)) - tcBlue))) \n" - " { \n" - #if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) - " gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); \n" - #elif defined(GRAPHICS_API_OPENGL_33) - " finalColor = vec4(0.0, 0.0, 0.0, 1.0); \n" - #endif - " } \n" - " else \n" - " { \n" - #if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) - " float blue = texture2D(texture0, tcBlue).b; \n" - " vec2 tcGreen = lensCenter + scale*theta1; \n" - " float green = texture2D(texture0, tcGreen).g; \n" - #elif defined(GRAPHICS_API_OPENGL_33) - " float blue = texture(texture0, tcBlue).b; \n" - " vec2 tcGreen = lensCenter + scale*theta1; \n" - " float green = texture(texture0, tcGreen).g; \n" - #endif - " vec2 thetaRed = theta1*(chromaAbParam.x + chromaAbParam.y*rSq); \n" - " vec2 tcRed = lensCenter + scale*thetaRed; \n" - #if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) - " float red = texture2D(texture0, tcRed).r; \n" - " gl_FragColor = vec4(red, green, blue, 1.0); \n" - #elif defined(GRAPHICS_API_OPENGL_33) - " float red = texture(texture0, tcRed).r; \n" - " finalColor = vec4(red, green, blue, 1.0); \n" - #endif - " } \n" - "} \n"; -#endif - #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -static Matrix stack[MATRIX_STACK_SIZE]; +static Matrix stack[MAX_MATRIX_STACK_SIZE] = { 0 }; static int stackCounter = 0; static Matrix modelview = { 0 }; static Matrix projection = { 0 }; static Matrix *currentMatrix = NULL; static int currentMatrixMode = -1; - -static int currentDrawMode = -1; - static float currentDepth = -1.0f; -static DynamicBuffer lines = { 0 }; // Default dynamic buffer for lines data -static DynamicBuffer triangles = { 0 }; // Default dynamic buffer for triangles data -static DynamicBuffer quads = { 0 }; // Default dynamic buffer for quads data (used to draw textures) +// Default dynamic buffer for elements data +// NOTE: A multi-buffering system is supported +static DynamicBuffer vertexData[MAX_BATCH_BUFFERING] = { 0 }; +static int currentBuffer = 0; + +// Transform matrix to be used with rlTranslate, rlRotate, rlScale +static Matrix transformMatrix = { 0 }; +static bool useTransformMatrix = false; // Default buffers draw calls static DrawCall *draws = NULL; static int drawsCounter = 0; -// Temp vertex buffer to be used with rlTranslate, rlRotate, rlScale -static Vector3 *tempBuffer = NULL; -static int tempBufferCount = 0; -static bool useTempBuffer = false; +// Default texture (1px white) useful for plain color polys (required by shader) +static unsigned int defaultTextureId = 0; -// Shaders -static unsigned int defaultVShaderId; // Default vertex shader id (used by default shader program) -static unsigned int defaultFShaderId; // Default fragment shader Id (used by default shader program) +// Default shaders +static unsigned int defaultVShaderId = 0; // Default vertex shader id (used by default shader program) +static unsigned int defaultFShaderId = 0; // Default fragment shader Id (used by default shader program) -static Shader defaultShader; // Basic shader, support vertex color and diffuse texture -static Shader currentShader; // Shader to be used on rendering (by default, defaultShader) +static Shader defaultShader = { 0 }; // Basic shader, support vertex color and diffuse texture +static Shader currentShader = { 0 }; // Shader to be used on rendering (by default, defaultShader) // Extension supported flag: VAO static bool vaoSupported = false; // VAO support (OpenGL ES2 could not support VAO extension) // Extension supported flag: Compressed textures +static bool texCompDXTSupported = false; // DDS texture compression support static bool texCompETC1Supported = false; // ETC1 texture compression support static bool texCompETC2Supported = false; // ETC2/EAC texture compression support static bool texCompPVRTSupported = false; // PVR texture compression support static bool texCompASTCSupported = false; // ASTC texture compression support -#if defined(SUPPORT_VR_SIMULATOR) -// VR global variables -static VrStereoConfig vrConfig; // VR stereo configuration for simulator -static bool vrSimulatorReady = false; // VR simulator ready flag -static bool vrStereoRender = false; // VR stereo rendering enabled/disabled flag - // NOTE: This flag is useful to render data over stereo image (i.e. FPS) -#endif // defined(SUPPORT_VR_SIMULATOR) +// Extension supported flag: Textures format +static bool texNPOTSupported = false; // NPOT textures full support +static bool texFloatSupported = false; // float textures support (32 bit per channel) +static bool texDepthSupported = false; // Depth textures supported +static int maxDepthBits = 16; // Maximum bits for depth component -#endif // defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Extension supported flag: Clamp mirror wrap mode +static bool texMirrorClampSupported = false; // Clamp mirror wrap mode supported // Extension supported flag: Anisotropic filtering static bool texAnisotropicFilterSupported = false; // Anisotropic texture filtering support static float maxAnisotropicLevel = 0.0f; // Maximum anisotropy level supported (minimum is 2.0f) -// Extension supported flag: Clamp mirror wrap mode -static bool texClampMirrorSupported = false; // Clamp mirror wrap mode supported +static bool debugMarkerSupported = false; // Debug marker support #if defined(GRAPHICS_API_OPENGL_ES2) // NOTE: VAO functionality is exposed through extensions (OES) @@ -873,21 +832,22 @@ static PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArrays; //static PFNGLISVERTEXARRAYOESPROC glIsVertexArray; // NOTE: Fails in WebGL, omitted #endif -static bool debugMarkerSupported = false; +#if defined(SUPPORT_VR_SIMULATOR) +// VR global variables +static VrStereoConfig vrConfig = { 0 }; // VR stereo configuration for simulator +static RenderTexture2D stereoFbo = { 0 }; // VR stereo rendering framebuffer +static bool vrSimulatorReady = false; // VR simulator ready flag +static bool vrStereoRender = false; // VR stereo rendering enabled/disabled flag + // NOTE: This flag is useful to render data over stereo image (i.e. FPS) +#endif // SUPPORT_VR_SIMULATOR -// Compressed textures support flags -static bool texCompDXTSupported = false; // DDS texture compression support -static bool texNPOTSupported = false; // NPOT textures full support -static bool texFloatSupported = false; // float textures support (32 bit per channel) +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 -static int blendMode = 0; // Track current blending mode - -// White texture useful for plain color polys (required by shader) -static unsigned int whiteTexture; +static int blendMode = 0; // Track current blending mode // Default framebuffer size -static int screenWidth; // Default framebuffer width -static int screenHeight; // Default framebuffer height +static int framebufferWidth = 0; // Default framebuffer width +static int framebufferHeight = 0; // Default framebuffer height //---------------------------------------------------------------------------------- // Module specific Functions Declaration @@ -900,7 +860,7 @@ static Shader LoadShaderDefault(void); // Load default shader (just vertex static void SetShaderDefaultLocations(Shader *shader); // Bind default shader locations (attributes and uniforms) static void UnloadShaderDefault(void); // Unload default shader -static void LoadBuffersDefault(void); // Load default internal buffers (lines, triangles, quads) +static void LoadBuffersDefault(void); // Load default internal buffers static void UpdateBuffersDefault(void); // Update default internal buffers (VAOs/VBOs) with vertex data static void DrawBuffersDefault(void); // Draw default internal buffers vertex data static void UnloadBuffersDefault(void); // Unload default internal buffers vertex data from CPU and GPU @@ -909,11 +869,10 @@ static void GenDrawCube(void); // Generate and draw cube static void GenDrawQuad(void); // Generate and draw quad #if defined(SUPPORT_VR_SIMULATOR) -static void SetStereoConfig(VrDeviceInfo info); // Configure stereo rendering (including distortion shader) with HMD device parameters -static void SetStereoView(int eye, Matrix matProjection, Matrix matModelView); // Set internal projection and modelview matrix depending on eye +static void SetStereoView(int eye, Matrix matProjection, Matrix matModelView); // Set internal projection and modelview matrix depending on eye #endif -#endif // defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +#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); @@ -939,14 +898,14 @@ void rlMatrixMode(int mode) } } -void rlFrustum(double left, double right, double bottom, double top, double zNear, double zFar) +void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) { - glFrustum(left, right, bottom, top, zNear, zFar); + glFrustum(left, right, bottom, top, znear, zfar); } -void rlOrtho(double left, double right, double bottom, double top, double zNear, double zFar) +void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) { - glOrtho(left, right, bottom, top, zNear, zFar); + glOrtho(left, right, bottom, top, znear, zfar); } void rlPushMatrix(void) { glPushMatrix(); } @@ -969,19 +928,19 @@ void rlMatrixMode(int mode) currentMatrixMode = mode; } -// Push the current matrix to stack +// Push the current matrix into stack void rlPushMatrix(void) { - if (stackCounter == MATRIX_STACK_SIZE - 1) + if (stackCounter >= MAX_MATRIX_STACK_SIZE) TraceLog(LOG_ERROR, "Matrix stack overflow"); + + if (currentMatrixMode == RL_MODELVIEW) { - TraceLog(LOG_ERROR, "Stack Buffer Overflow (MAX %i Matrix)", MATRIX_STACK_SIZE); + useTransformMatrix = true; + currentMatrix = &transformMatrix; } stack[stackCounter] = *currentMatrix; - rlLoadIdentity(); // TODO: Review matrix stack logic! stackCounter++; - - if (currentMatrixMode == RL_MODELVIEW) useTempBuffer = true; } // Pop lattest inserted matrix from stack @@ -993,6 +952,12 @@ void rlPopMatrix(void) *currentMatrix = mat; stackCounter--; } + + if ((stackCounter == 0) && (currentMatrixMode == RL_MODELVIEW)) + { + currentMatrix = &modelview; + useTransformMatrix = false; + } } // Reset current matrix to identity matrix @@ -1044,17 +1009,17 @@ void rlMultMatrixf(float *matf) } // Multiply the current matrix by a perspective matrix generated by parameters -void rlFrustum(double left, double right, double bottom, double top, double near, double far) +void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) { - Matrix matPerps = MatrixFrustum(left, right, bottom, top, near, far); + Matrix matPerps = MatrixFrustum(left, right, bottom, top, znear, zfar); *currentMatrix = MatrixMultiply(*currentMatrix, matPerps); } // Multiply the current matrix by an orthographic matrix generated by parameters -void rlOrtho(double left, double right, double bottom, double top, double near, double far) +void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) { - Matrix matOrtho = MatrixOrtho(left, right, bottom, top, near, far); + Matrix matOrtho = MatrixOrtho(left, right, bottom, top, znear, zfar); *currentMatrix = MatrixMultiply(*currentMatrix, matOrtho); } @@ -1062,7 +1027,7 @@ void rlOrtho(double left, double right, double bottom, double top, double near, #endif // Set the viewport area (transformation from normalized device coordinates to window coordinates) -// NOTE: Updates global variables: screenWidth, screenHeight +// NOTE: Updates global variables: framebufferWidth, framebufferHeight void rlViewport(int x, int y, int width, int height) { glViewport(x, y, width, height); @@ -1101,108 +1066,77 @@ void rlColor4f(float x, float y, float z, float w) { glColor4f(x, y, z, w); } // Initialize drawing mode (how to organize vertex) void rlBegin(int mode) { - // Draw mode can only be RL_LINES, RL_TRIANGLES and RL_QUADS - currentDrawMode = mode; + // Draw mode can be RL_LINES, RL_TRIANGLES and RL_QUADS + // NOTE: In all three cases, vertex are accumulated over default internal vertex buffer + if (draws[drawsCounter - 1].mode != mode) + { + if (draws[drawsCounter - 1].vertexCount > 0) + { + // Make sure current 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 (draws[drawsCounter - 1].mode == RL_LINES) draws[drawsCounter - 1].vertexAlignment = ((draws[drawsCounter - 1].vertexCount < 4)? draws[drawsCounter - 1].vertexCount : draws[drawsCounter - 1].vertexCount%4); + else if (draws[drawsCounter - 1].mode == RL_TRIANGLES) draws[drawsCounter - 1].vertexAlignment = ((draws[drawsCounter - 1].vertexCount < 4)? 1 : (4 - (draws[drawsCounter - 1].vertexCount%4))); + + else draws[drawsCounter - 1].vertexAlignment = 0; + + if (rlCheckBufferLimit(draws[drawsCounter - 1].vertexAlignment)) rlglDraw(); + else + { + vertexData[currentBuffer].vCounter += draws[drawsCounter - 1].vertexAlignment; + vertexData[currentBuffer].cCounter += draws[drawsCounter - 1].vertexAlignment; + vertexData[currentBuffer].tcCounter += draws[drawsCounter - 1].vertexAlignment; + + drawsCounter++; + } + } + + if (drawsCounter >= MAX_DRAWCALL_REGISTERED) rlglDraw(); + + draws[drawsCounter - 1].mode = mode; + draws[drawsCounter - 1].vertexCount = 0; + draws[drawsCounter - 1].textureId = defaultTextureId; + } } // Finish vertex providing void rlEnd(void) { - if (useTempBuffer) + // Make sure vertexCount is the same for vertices, texcoords, colors and normals + // NOTE: In OpenGL 1.1, one glColor call can be made for all the subsequent glVertex calls + + // Make sure colors count match vertex count + if (vertexData[currentBuffer].vCounter != vertexData[currentBuffer].cCounter) { - // NOTE: In this case, *currentMatrix is already transposed because transposing has been applied - // independently to translation-scale-rotation matrices -> t(M1 x M2) = t(M2) x t(M1) - // This way, rlTranslatef(), rlRotatef()... behaviour is the same than OpenGL 1.1 + int addColors = vertexData[currentBuffer].vCounter - vertexData[currentBuffer].cCounter; - // Apply transformation matrix to all temp vertices - for (int i = 0; i < tempBufferCount; i++) tempBuffer[i] = Vector3Transform(tempBuffer[i], *currentMatrix); - - // Deactivate tempBuffer usage to allow rlVertex3f do its job - useTempBuffer = false; - - // Copy all transformed vertices to right VAO - for (int i = 0; i < tempBufferCount; i++) rlVertex3f(tempBuffer[i].x, tempBuffer[i].y, tempBuffer[i].z); - - // Reset temp buffer - tempBufferCount = 0; + for (int i = 0; i < addColors; i++) + { + vertexData[currentBuffer].colors[4*vertexData[currentBuffer].cCounter] = vertexData[currentBuffer].colors[4*vertexData[currentBuffer].cCounter - 4]; + vertexData[currentBuffer].colors[4*vertexData[currentBuffer].cCounter + 1] = vertexData[currentBuffer].colors[4*vertexData[currentBuffer].cCounter - 3]; + vertexData[currentBuffer].colors[4*vertexData[currentBuffer].cCounter + 2] = vertexData[currentBuffer].colors[4*vertexData[currentBuffer].cCounter - 2]; + vertexData[currentBuffer].colors[4*vertexData[currentBuffer].cCounter + 3] = vertexData[currentBuffer].colors[4*vertexData[currentBuffer].cCounter - 1]; + vertexData[currentBuffer].cCounter++; + } } - // Make sure vertexCount is the same for vertices-texcoords-normals-colors - // NOTE: In OpenGL 1.1, one glColor call can be made for all the subsequent glVertex calls. - switch (currentDrawMode) + // Make sure texcoords count match vertex count + if (vertexData[currentBuffer].vCounter != vertexData[currentBuffer].tcCounter) { - case RL_LINES: + int addTexCoords = vertexData[currentBuffer].vCounter - vertexData[currentBuffer].tcCounter; + + for (int i = 0; i < addTexCoords; i++) { - if (lines.vCounter != lines.cCounter) - { - int addColors = lines.vCounter - lines.cCounter; - - for (int i = 0; i < addColors; i++) - { - lines.colors[4*lines.cCounter] = lines.colors[4*lines.cCounter - 4]; - lines.colors[4*lines.cCounter + 1] = lines.colors[4*lines.cCounter - 3]; - lines.colors[4*lines.cCounter + 2] = lines.colors[4*lines.cCounter - 2]; - lines.colors[4*lines.cCounter + 3] = lines.colors[4*lines.cCounter - 1]; - - lines.cCounter++; - } - } - } break; - case RL_TRIANGLES: - { - if (triangles.vCounter != triangles.cCounter) - { - int addColors = triangles.vCounter - triangles.cCounter; - - for (int i = 0; i < addColors; i++) - { - triangles.colors[4*triangles.cCounter] = triangles.colors[4*triangles.cCounter - 4]; - triangles.colors[4*triangles.cCounter + 1] = triangles.colors[4*triangles.cCounter - 3]; - triangles.colors[4*triangles.cCounter + 2] = triangles.colors[4*triangles.cCounter - 2]; - triangles.colors[4*triangles.cCounter + 3] = triangles.colors[4*triangles.cCounter - 1]; - - triangles.cCounter++; - } - } - } break; - case RL_QUADS: - { - // Make sure colors count match vertex count - if (quads.vCounter != quads.cCounter) - { - int addColors = quads.vCounter - quads.cCounter; - - for (int i = 0; i < addColors; i++) - { - quads.colors[4*quads.cCounter] = quads.colors[4*quads.cCounter - 4]; - quads.colors[4*quads.cCounter + 1] = quads.colors[4*quads.cCounter - 3]; - quads.colors[4*quads.cCounter + 2] = quads.colors[4*quads.cCounter - 2]; - quads.colors[4*quads.cCounter + 3] = quads.colors[4*quads.cCounter - 1]; - - quads.cCounter++; - } - } - - // Make sure texcoords count match vertex count - if (quads.vCounter != quads.tcCounter) - { - int addTexCoords = quads.vCounter - quads.tcCounter; - - for (int i = 0; i < addTexCoords; i++) - { - quads.texcoords[2*quads.tcCounter] = 0.0f; - quads.texcoords[2*quads.tcCounter + 1] = 0.0f; - - quads.tcCounter++; - } - } - - // TODO: Make sure normals count match vertex count... if normals support is added in a future... :P - - } break; - default: break; + vertexData[currentBuffer].texcoords[2*vertexData[currentBuffer].tcCounter] = 0.0f; + vertexData[currentBuffer].texcoords[2*vertexData[currentBuffer].tcCounter + 1] = 0.0f; + vertexData[currentBuffer].tcCounter++; + } } + // TODO: Make sure normals count match vertex count... if normals support is added in a future... :P + // NOTE: Depth increment is dependant on rlOrtho(): z-near and z-far values, // as well as depth buffer bit-depth (16bit or 24bit or 32bit) // Correct increment formula would be: depthInc = (zfar - znear)/pow(2, bits) @@ -1210,84 +1144,36 @@ void rlEnd(void) // Verify internal buffers limits // NOTE: This check is combined with usage of rlCheckBufferLimit() - if ((lines.vCounter/2 >= (MAX_LINES_BATCH - 2)) || - (triangles.vCounter/3 >= (MAX_TRIANGLES_BATCH - 3)) || - (quads.vCounter/4 >= (MAX_QUADS_BATCH - 4))) + if ((vertexData[currentBuffer].vCounter) >= (MAX_BATCH_ELEMENTS*4 - 4)) { // WARNING: If we are between rlPushMatrix() and rlPopMatrix() and we need to force a rlglDraw(), // we need to call rlPopMatrix() before to recover *currentMatrix (modelview) for the next forced draw call! - // Also noted that if we had multiple matrix pushed, it will require "stackCounter" pops before launching the draw - - // TODO: Undoubtely, current rlPushMatrix/rlPopMatrix should be redesigned... or removed... it's not working properly - - rlPopMatrix(); + // If we have multiple matrix pushed, it will require "stackCounter" pops before launching the draw + for (int i = stackCounter; i >= 0; i--) rlPopMatrix(); rlglDraw(); } } // Define one vertex (position) +// NOTE: Vertex position data is the basic information required for drawing void rlVertex3f(float x, float y, float z) { - // NOTE: Temp buffer is processed and resetted at rlEnd() - // Between rlBegin() and rlEnd() can not be more than TEMP_VERTEX_BUFFER_SIZE rlVertex3f() calls - if (useTempBuffer && (tempBufferCount < TEMP_VERTEX_BUFFER_SIZE)) + Vector3 vec = { x, y, z }; + + // Transform provided vector if required + if (useTransformMatrix) vec = Vector3Transform(vec, transformMatrix); + + // Verify that MAX_BATCH_ELEMENTS limit not reached + if (vertexData[currentBuffer].vCounter < (MAX_BATCH_ELEMENTS*4)) { - tempBuffer[tempBufferCount].x = x; - tempBuffer[tempBufferCount].y = y; - tempBuffer[tempBufferCount].z = z; - tempBufferCount++; - } - else - { - switch (currentDrawMode) - { - case RL_LINES: - { - // Verify that MAX_LINES_BATCH limit not reached - if (lines.vCounter/2 < MAX_LINES_BATCH) - { - lines.vertices[3*lines.vCounter] = x; - lines.vertices[3*lines.vCounter + 1] = y; - lines.vertices[3*lines.vCounter + 2] = z; - - lines.vCounter++; - } - else TraceLog(LOG_ERROR, "MAX_LINES_BATCH overflow"); - - } break; - case RL_TRIANGLES: - { - // Verify that MAX_TRIANGLES_BATCH limit not reached - if (triangles.vCounter/3 < MAX_TRIANGLES_BATCH) - { - triangles.vertices[3*triangles.vCounter] = x; - triangles.vertices[3*triangles.vCounter + 1] = y; - triangles.vertices[3*triangles.vCounter + 2] = z; - - triangles.vCounter++; - } - else TraceLog(LOG_ERROR, "MAX_TRIANGLES_BATCH overflow"); - - } break; - case RL_QUADS: - { - // Verify that MAX_QUADS_BATCH limit not reached - if (quads.vCounter/4 < MAX_QUADS_BATCH) - { - quads.vertices[3*quads.vCounter] = x; - quads.vertices[3*quads.vCounter + 1] = y; - quads.vertices[3*quads.vCounter + 2] = z; - - quads.vCounter++; - - draws[drawsCounter - 1].vertexCount++; - } - else TraceLog(LOG_ERROR, "MAX_QUADS_BATCH overflow"); - - } break; - default: break; - } + vertexData[currentBuffer].vertices[3*vertexData[currentBuffer].vCounter] = vec.x; + vertexData[currentBuffer].vertices[3*vertexData[currentBuffer].vCounter + 1] = vec.y; + vertexData[currentBuffer].vertices[3*vertexData[currentBuffer].vCounter + 2] = vec.z; + vertexData[currentBuffer].vCounter++; + + draws[drawsCounter - 1].vertexCount++; } + else TraceLog(LOG_ERROR, "MAX_BATCH_ELEMENTS overflow"); } // Define one vertex (position) @@ -1306,17 +1192,13 @@ void rlVertex2i(int x, int y) // NOTE: Texture coordinates are limited to QUADS only void rlTexCoord2f(float x, float y) { - if (currentDrawMode == RL_QUADS) - { - quads.texcoords[2*quads.tcCounter] = x; - quads.texcoords[2*quads.tcCounter + 1] = y; - - quads.tcCounter++; - } + vertexData[currentBuffer].texcoords[2*vertexData[currentBuffer].tcCounter] = x; + vertexData[currentBuffer].texcoords[2*vertexData[currentBuffer].tcCounter + 1] = y; + vertexData[currentBuffer].tcCounter++; } // Define one vertex (normal) -// NOTE: Normals limited to TRIANGLES only ? +// NOTE: Normals limited to TRIANGLES only? void rlNormal3f(float x, float y, float z) { // TODO: Normals usage... @@ -1325,40 +1207,11 @@ void rlNormal3f(float x, float y, float z) // Define one vertex (color) void rlColor4ub(byte x, byte y, byte z, byte w) { - switch (currentDrawMode) - { - case RL_LINES: - { - lines.colors[4*lines.cCounter] = x; - lines.colors[4*lines.cCounter + 1] = y; - lines.colors[4*lines.cCounter + 2] = z; - lines.colors[4*lines.cCounter + 3] = w; - - lines.cCounter++; - - } break; - case RL_TRIANGLES: - { - triangles.colors[4*triangles.cCounter] = x; - triangles.colors[4*triangles.cCounter + 1] = y; - triangles.colors[4*triangles.cCounter + 2] = z; - triangles.colors[4*triangles.cCounter + 3] = w; - - triangles.cCounter++; - - } break; - case RL_QUADS: - { - quads.colors[4*quads.cCounter] = x; - quads.colors[4*quads.cCounter + 1] = y; - quads.colors[4*quads.cCounter + 2] = z; - quads.colors[4*quads.cCounter + 3] = w; - - quads.cCounter++; - - } break; - default: break; - } + vertexData[currentBuffer].colors[4*vertexData[currentBuffer].cCounter] = x; + vertexData[currentBuffer].colors[4*vertexData[currentBuffer].cCounter + 1] = y; + vertexData[currentBuffer].colors[4*vertexData[currentBuffer].cCounter + 2] = z; + vertexData[currentBuffer].colors[4*vertexData[currentBuffer].cCounter + 3] = w; + vertexData[currentBuffer].cCounter++; } // Define one vertex (color) @@ -1390,9 +1243,30 @@ void rlEnableTexture(unsigned int id) #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) if (draws[drawsCounter - 1].textureId != id) { - if (draws[drawsCounter - 1].vertexCount > 0) drawsCounter++; + if (draws[drawsCounter - 1].vertexCount > 0) + { + // Make sure current 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 (draws[drawsCounter - 1].mode == RL_LINES) draws[drawsCounter - 1].vertexAlignment = ((draws[drawsCounter - 1].vertexCount < 4)? draws[drawsCounter - 1].vertexCount : draws[drawsCounter - 1].vertexCount%4); + else if (draws[drawsCounter - 1].mode == RL_TRIANGLES) draws[drawsCounter - 1].vertexAlignment = ((draws[drawsCounter - 1].vertexCount < 4)? 1 : (4 - (draws[drawsCounter - 1].vertexCount%4))); - if (drawsCounter >= MAX_DRAWS_BY_TEXTURE) rlglDraw(); + else draws[drawsCounter - 1].vertexAlignment = 0; + + if (rlCheckBufferLimit(draws[drawsCounter - 1].vertexAlignment)) rlglDraw(); + else + { + vertexData[currentBuffer].vCounter += draws[drawsCounter - 1].vertexAlignment; + vertexData[currentBuffer].cCounter += draws[drawsCounter - 1].vertexAlignment; + vertexData[currentBuffer].tcCounter += draws[drawsCounter - 1].vertexAlignment; + + drawsCounter++; + } + } + + if (drawsCounter >= MAX_DRAWCALL_REGISTERED) rlglDraw(); draws[drawsCounter - 1].textureId = id; draws[drawsCounter - 1].vertexCount = 0; @@ -1409,7 +1283,7 @@ void rlDisableTexture(void) #else // NOTE: If quads batch limit is reached, // we force a draw call and next batch starts - if (quads.vCounter/4 >= MAX_QUADS_BATCH) rlglDraw(); + if (vertexData[currentBuffer].vCounter >= (MAX_BATCH_ELEMENTS*4)) rlglDraw(); #endif } @@ -1423,13 +1297,19 @@ void rlTextureParameters(unsigned int id, int param, int value) case RL_TEXTURE_WRAP_S: case RL_TEXTURE_WRAP_T: { - if ((value == RL_WRAP_CLAMP_MIRROR) && !texClampMirrorSupported) TraceLog(LOG_WARNING, "Clamp mirror wrap mode not supported"); + if (value == RL_WRAP_MIRROR_CLAMP) + { + if (texMirrorClampSupported) glTexParameteri(GL_TEXTURE_2D, param, value); + else TraceLog(LOG_WARNING, "Clamp mirror wrap mode not supported"); + } else glTexParameteri(GL_TEXTURE_2D, param, value); + } break; case RL_TEXTURE_MAG_FILTER: case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_2D, param, value); break; case RL_TEXTURE_ANISOTROPIC_FILTER: { +#if !defined(GRAPHICS_API_OPENGL_11) if (value <= maxAnisotropicLevel) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); else if (maxAnisotropicLevel > 0.0f) { @@ -1437,6 +1317,7 @@ void rlTextureParameters(unsigned int id, int param, int value) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); } else TraceLog(LOG_WARNING, "Anisotropic filtering not supported"); +#endif } break; default: break; } @@ -1467,16 +1348,25 @@ void rlDisableRenderTexture(void) } // Enable depth test -void rlEnableDepthTest(void) -{ - glEnable(GL_DEPTH_TEST); -} +void rlEnableDepthTest(void) { glEnable(GL_DEPTH_TEST); } // Disable depth test -void rlDisableDepthTest(void) -{ - glDisable(GL_DEPTH_TEST); -} +void rlDisableDepthTest(void) { glDisable(GL_DEPTH_TEST); } + +// Enable backface culling +void rlEnableBackfaceCulling(void) { glEnable(GL_CULL_FACE); } + +// Disable backface culling +void rlDisableBackfaceCulling(void) { glDisable(GL_CULL_FACE); } + +// Enable scissor test +RLAPI void rlEnableScissorTest(void) { glEnable(GL_SCISSOR_TEST); } + +// Disable scissor test +RLAPI 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); } // Enable wire mode void rlEnableWireMode(void) @@ -1509,11 +1399,8 @@ void rlDeleteRenderTextures(RenderTexture2D target) if (target.texture.id > 0) glDeleteTextures(1, &target.texture.id); if (target.depth.id > 0) { -#if defined(GRAPHICS_API_OPENGL_21) || defined(GRAPHICS_API_OPENGL_ES2) - glDeleteRenderbuffers(1, &target.depth.id); -#elif defined(GRAPHICS_API_OPENGL_33) - glDeleteTextures(1, &target.depth.id); -#endif + if (target.depthTexture) glDeleteTextures(1, &target.depth.id); + else glDeleteRenderbuffers(1, &target.depth.id); } if (target.id > 0) glDeleteFramebuffers(1, &target.id); @@ -1573,6 +1460,15 @@ 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) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, bufferId); + glBufferSubData(GL_ARRAY_BUFFER, 0, dataSize, data); +#endif +} + //---------------------------------------------------------------------------------- // Module Functions Definition - rlgl Functions //---------------------------------------------------------------------------------- @@ -1609,57 +1505,58 @@ void rlglInit(int width, int height) // NOTE: We don't need that much data on screen... right now... -#if defined(GRAPHICS_API_OPENGL_11) - //TraceLog(LOG_INFO, "OpenGL 1.1 (or driver default) profile initialized"); -#endif + // TODO: Automatize extensions loading using rlLoadExtensions() and GLAD + // Actually, when rlglInit() is called in InitWindow() in core.c, + // OpenGL required extensions have already been loaded (PLATFORM_DESKTOP) #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) // Get supported extensions list GLint numExt = 0; #if defined(GRAPHICS_API_OPENGL_33) - // NOTE: On OpenGL 3.3 VAO and NPOT are supported by default vaoSupported = true; + + // Multiple texture extensions supported by default texNPOTSupported = true; texFloatSupported = true; + texDepthSupported = 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); -#if defined(_MSC_VER) - const char **extList = malloc(sizeof(const char *)*numExt); -#else - const char *extList[numExt]; -#endif + // Allocate numExt strings pointers + const char **extList = RL_MALLOC(sizeof(const char *)*numExt); - for (int i = 0; i < numExt; i++) extList[i] = (char *)glGetStringi(GL_EXTENSIONS, i); + // Get extensions strings + for (int i = 0; i < numExt; i++) extList[i] = (const char *)glGetStringi(GL_EXTENSIONS, i); #elif defined(GRAPHICS_API_OPENGL_ES2) - char *extensions = (char *)glGetString(GL_EXTENSIONS); // One big const string + // Allocate 512 strings pointers (2 KB) + const char **extList = RL_MALLOC(sizeof(const char *)*512); + + 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 *)malloc(len); + char *extensionsDup = (char *)RL_CALLOC(len, sizeof(char)); strcpy(extensionsDup, extensions); - // NOTE: String could be splitted using strtok() function (string.h) - // NOTE: strtok() modifies the passed string, it can not be const + extList[numExt] = extensionsDup; - char *extList[512]; // Allocate 512 strings pointers (2 KB) - - extList[numExt] = strtok(extensionsDup, " "); - - while (extList[numExt] != NULL) + for (int i = 0; i < len; i++) { - numExt++; - extList[numExt] = strtok(NULL, " "); + if (extensionsDup[i] == ' ') + { + extensionsDup[i] = '\0'; + + numExt++; + extList[numExt] = &extensionsDup[i + 1]; + } } - free(extensionsDup); // Duplicated string must be deallocated - - numExt -= 1; + // NOTE: Duplicated string (extensionsDup) must be deallocated #endif TraceLog(LOG_INFO, "Number of supported extensions: %i", numExt); @@ -1675,14 +1572,14 @@ void rlglInit(int width, int height) // 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) { - vaoSupported = true; - // 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)) vaoSupported = true; } // Check NPOT textures support @@ -1691,8 +1588,14 @@ void rlglInit(int width, int height) // Check texture float support if (strcmp(extList[i], (const char *)"GL_OES_texture_float") == 0) texFloatSupported = true; -#endif + // 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)) texDepthSupported = true; + + if (strcmp(extList[i], (const char *)"GL_OES_depth24") == 0) maxDepthBits = 24; + if (strcmp(extList[i], (const char *)"GL_OES_depth32") == 0) 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) || @@ -1719,17 +1622,18 @@ void rlglInit(int width, int height) } // Clamp mirror wrap mode supported - if (strcmp(extList[i], (const char *)"GL_EXT_texture_mirror_clamp") == 0) texClampMirrorSupported = true; + if (strcmp(extList[i], (const char *)"GL_EXT_texture_mirror_clamp") == 0) texMirrorClampSupported = true; // Debug marker support - if(strcmp(extList[i], (const char *)"GL_EXT_debug_marker") == 0) debugMarkerSupported = true; + if (strcmp(extList[i], (const char *)"GL_EXT_debug_marker") == 0) debugMarkerSupported = true; } -#if defined(_MSC_VER) - //free(extList); -#endif + // Free extensions pointers + RL_FREE(extList); #if defined(GRAPHICS_API_OPENGL_ES2) + RL_FREE(extensionsDup); // Duplicated string must be deallocated + if (vaoSupported) TraceLog(LOG_INFO, "[EXTENSION] VAO extension detected, VAO functions initialized successfully"); else TraceLog(LOG_WARNING, "[EXTENSION] VAO extension not found, VAO usage not supported"); @@ -1744,63 +1648,57 @@ void rlglInit(int width, int height) if (texCompASTCSupported) TraceLog(LOG_INFO, "[EXTENSION] ASTC compressed textures supported"); if (texAnisotropicFilterSupported) TraceLog(LOG_INFO, "[EXTENSION] Anisotropic textures filtering supported (max: %.0fX)", maxAnisotropicLevel); - if (texClampMirrorSupported) TraceLog(LOG_INFO, "[EXTENSION] Clamp mirror wrap texture mode supported"); + if (texMirrorClampSupported) TraceLog(LOG_INFO, "[EXTENSION] Mirror clamp wrap texture mode supported"); if (debugMarkerSupported) TraceLog(LOG_INFO, "[EXTENSION] 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) + defaultTextureId = rlLoadTexture(pixels, 1, 1, UNCOMPRESSED_R8G8B8A8, 1); - whiteTexture = rlLoadTexture(pixels, 1, 1, UNCOMPRESSED_R8G8B8A8, 1); - - if (whiteTexture != 0) TraceLog(LOG_INFO, "[TEX ID %i] Base white texture loaded successfully", whiteTexture); + if (defaultTextureId != 0) TraceLog(LOG_INFO, "[TEX ID %i] Base white texture loaded successfully", defaultTextureId); else TraceLog(LOG_WARNING, "Base white texture could not be loaded"); // Init default Shader (customized for GL 3.3 and ES2) defaultShader = LoadShaderDefault(); currentShader = defaultShader; - // Init default vertex arrays buffers (lines, triangles, quads) + // Init default vertex arrays buffers LoadBuffersDefault(); - // Init temp vertex buffer, used when transformation required (translate, rotate, scale) - tempBuffer = (Vector3 *)malloc(sizeof(Vector3)*TEMP_VERTEX_BUFFER_SIZE); - - for (int i = 0; i < TEMP_VERTEX_BUFFER_SIZE; i++) tempBuffer[i] = Vector3Zero(); + // Init transformations matrix accumulator + transformMatrix = MatrixIdentity(); // Init draw calls tracking system - draws = (DrawCall *)malloc(sizeof(DrawCall)*MAX_DRAWS_BY_TEXTURE); + draws = (DrawCall *)RL_MALLOC(sizeof(DrawCall)*MAX_DRAWCALL_REGISTERED); - for (int i = 0; i < MAX_DRAWS_BY_TEXTURE; i++) + for (int i = 0; i < MAX_DRAWCALL_REGISTERED; i++) { + draws[i].mode = RL_QUADS; draws[i].vertexCount = 0; - draws[i].vaoId = 0; - draws[i].shaderId = 0; - draws[i].textureId = 0; - - draws[i].projection = MatrixIdentity(); - draws[i].modelview = MatrixIdentity(); + draws[i].vertexAlignment = 0; + //draws[i].vaoId = 0; + //draws[i].shaderId = 0; + draws[i].textureId = defaultTextureId; + //draws[i].projection = MatrixIdentity(); + //draws[i].modelview = MatrixIdentity(); } drawsCounter = 1; - draws[0].textureId = whiteTexture; // Set default draw texture id - currentDrawMode = RL_TRIANGLES; // Set default draw mode // Init internal matrix stack (emulating OpenGL 1.1) - for (int i = 0; i < MATRIX_STACK_SIZE; i++) stack[i] = MatrixIdentity(); + for (int i = 0; i < MAX_MATRIX_STACK_SIZE; i++) stack[i] = MatrixIdentity(); // Init internal projection and modelview matrices projection = MatrixIdentity(); modelview = MatrixIdentity(); currentMatrix = &modelview; -#endif // defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 // Initialize OpenGL default states //---------------------------------------------------------- - // Init state: Depth test glDepthFunc(GL_LEQUAL); // Type of depth testing to apply glDisable(GL_DEPTH_TEST); // Disable depth testing for 2D (only used for 3D) @@ -1827,8 +1725,8 @@ void rlglInit(int width, int height) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear color and depth buffers (depth buffer required for 3D) // Store screen size into global variables - screenWidth = width; - screenHeight = height; + framebufferWidth = width; + framebufferHeight = height; TraceLog(LOG_INFO, "OpenGL default states initialized successfully"); } @@ -1838,26 +1736,25 @@ void rlglClose(void) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) UnloadShaderDefault(); // Unload default shader - UnloadBuffersDefault(); // Unload default buffers (lines, triangles, quads) - glDeleteTextures(1, &whiteTexture); // Unload default texture + UnloadBuffersDefault(); // Unload default buffers + glDeleteTextures(1, &defaultTextureId); // Unload default texture - TraceLog(LOG_INFO, "[TEX ID %i] Unloaded texture data (base white texture) from VRAM", whiteTexture); + TraceLog(LOG_INFO, "[TEX ID %i] Unloaded texture data (base white texture) from VRAM", defaultTextureId); - free(draws); - free(tempBuffer); + RL_FREE(draws); #endif } -// Drawing batches: triangles, quads, lines +// Update and draw internal buffers void rlglDraw(void) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: In a future version, models could be stored in a stack... - //for (int i = 0; i < modelsCount; i++) rlDrawMesh(models[i]->mesh, models[i]->material, models[i]->transform); - - // NOTE: Default buffers upload and draw - UpdateBuffersDefault(); - DrawBuffersDefault(); // NOTE: Stereo rendering is checked inside + // Only process data if we have data to process + if (vertexData[currentBuffer].vCounter > 0) + { + UpdateBuffersDefault(); + DrawBuffersDefault(); // NOTE: Stereo rendering is checked inside + } #endif } @@ -1868,7 +1765,7 @@ int rlGetVersion(void) return OPENGL_11; #elif defined(GRAPHICS_API_OPENGL_21) #if defined(__APPLE__) - return OPENGL_33; // NOTE: Force OpenGL 3.3 on OSX + return OPENGL_33; // NOTE: Force OpenGL 3.3 on OSX #else return OPENGL_21; #endif @@ -1880,17 +1777,11 @@ int rlGetVersion(void) } // Check internal buffer overflow for a given number of vertex -bool rlCheckBufferLimit(int type, int vCount) +bool rlCheckBufferLimit(int vCount) { bool overflow = false; #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - switch (type) - { - case RL_LINES: overflow = ((lines.vCounter + vCount)/2 >= MAX_LINES_BATCH); break; - case RL_TRIANGLES: overflow = ((triangles.vCounter + vCount)/3 >= MAX_TRIANGLES_BATCH); break; - case RL_QUADS: overflow = ((quads.vCounter + vCount)/4 >= MAX_QUADS_BATCH); break; - default: break; - } + if ((vertexData[currentBuffer].vCounter + vCount) >= (MAX_BATCH_ELEMENTS*4)) overflow = true; #endif return overflow; } @@ -1916,7 +1807,7 @@ void rlLoadExtensions(void *loader) #if defined(GRAPHICS_API_OPENGL_21) if (GLAD_GL_VERSION_2_1) TraceLog(LOG_INFO, "OpenGL 2.1 profile supported"); #elif defined(GRAPHICS_API_OPENGL_33) - if(GLAD_GL_VERSION_3_3) TraceLog(LOG_INFO, "OpenGL 3.3 Core profile supported"); + if (GLAD_GL_VERSION_3_3) TraceLog(LOG_INFO, "OpenGL 3.3 Core profile supported"); else TraceLog(LOG_ERROR, "OpenGL 3.3 Core profile not supported"); #endif #endif @@ -1954,7 +1845,7 @@ unsigned int rlLoadTexture(void *data, int width, int height, int format, int mi { glBindTexture(GL_TEXTURE_2D, 0); // Free any old binding - GLuint id = 0; + unsigned int id = 0; // Check texture format support by OpenGL 1.1 (compressed textures not supported) #if defined(GRAPHICS_API_OPENGL_11) @@ -1963,8 +1854,7 @@ unsigned int rlLoadTexture(void *data, int width, int height, int format, int mi TraceLog(LOG_WARNING, "OpenGL 1.1 does not support GPU compressed texture formats"); return id; } -#endif - +#else if ((!texCompDXTSupported) && ((format == COMPRESSED_DXT1_RGB) || (format == COMPRESSED_DXT1_RGBA) || (format == COMPRESSED_DXT3_RGBA) || (format == COMPRESSED_DXT5_RGBA))) { @@ -1996,10 +1886,11 @@ unsigned int rlLoadTexture(void *data, int width, int height, int format, int mi return id; } #endif +#endif // GRAPHICS_API_OPENGL_11 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glGenTextures(1, &id); // Generate Pointer to the texture + 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) @@ -2103,7 +1994,123 @@ unsigned int rlLoadTexture(void *data, int width, int height, int format, int mi return id; } +// 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 id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int glInternalFormat = GL_DEPTH_COMPONENT16; + + if ((bits != 16) && (bits != 24) && (bits != 32)) bits = 16; + + if (bits == 24) + { +#if defined(GRAPHICS_API_OPENGL_33) + glInternalFormat = GL_DEPTH_COMPONENT24; +#elif defined(GRAPHICS_API_OPENGL_ES2) + if (maxDepthBits >= 24) glInternalFormat = GL_DEPTH_COMPONENT24_OES; +#endif + } + + if (bits == 32) + { +#if defined(GRAPHICS_API_OPENGL_33) + glInternalFormat = GL_DEPTH_COMPONENT32; +#elif defined(GRAPHICS_API_OPENGL_ES2) + if (maxDepthBits == 32) glInternalFormat = GL_DEPTH_COMPONENT32_OES; +#endif + } + + if (!useRenderBuffer && texDepthSupported) + { + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + glTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glBindTexture(GL_TEXTURE_2D, 0); + } + else + { + // Create the renderbuffer that will serve as the depth attachment for the framebuffer + // NOTE: A renderbuffer is simpler than a texture and could offer better performance on embedded devices + glGenRenderbuffers(1, &id); + glBindRenderbuffer(GL_RENDERBUFFER, id); + glRenderbufferStorage(GL_RENDERBUFFER, glInternalFormat, width, height); + + glBindRenderbuffer(GL_RENDERBUFFER, 0); + } +#endif + + return id; +} + +// Load texture cubemap +// NOTE: Cubemap data is expected to be 6 images in a single column, +// 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 dataSize = GetPixelDataSize(size, size, format); + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glGenTextures(1, &cubemapId); + glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapId); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + + if (glInternalFormat != -1) + { + // 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); +#if !defined(GRAPHICS_API_OPENGL_11) + else glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, size, size, 0, dataSize, (unsigned char *)data + i*dataSize); +#endif +#if defined(GRAPHICS_API_OPENGL_33) + if (format == 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) + { +#if defined(GRAPHICS_API_OPENGL_21) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; +#elif defined(GRAPHICS_API_OPENGL_33) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; +#endif + glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } +#endif + } + } + + // Set cubemap texture sampling parameters + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + 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 + + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +#endif + + return cubemapId; +} + // Update already loaded texture in GPU with new data +// NOTE: We don't know safely if internal texture format is the expected one... void rlUpdateTexture(unsigned int id, int width, int height, int format, const void *data) { glBindTexture(GL_TEXTURE_2D, id); @@ -2176,102 +2183,54 @@ void rlUnloadTexture(unsigned int id) if (id > 0) glDeleteTextures(1, &id); } -// Load a texture to be used for rendering (fbo with color and depth attachments) -RenderTexture2D rlLoadRenderTexture(int width, int height) +// 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 }; - target.id = 0; - - target.texture.id = 0; - target.texture.width = width; - target.texture.height = height; - target.texture.format = UNCOMPRESSED_R8G8B8A8; - target.texture.mipmaps = 1; - - target.depth.id = 0; - target.depth.width = width; - target.depth.height = height; - target.depth.format = 19; //DEPTH_COMPONENT_24BIT - target.depth.mipmaps = 1; - #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Create the texture that will serve as the color attachment for the framebuffer - glGenTextures(1, &target.texture.id); - glBindTexture(GL_TEXTURE_2D, target.texture.id); - 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); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); - glBindTexture(GL_TEXTURE_2D, 0); - -#if defined(GRAPHICS_API_OPENGL_21) || defined(GRAPHICS_API_OPENGL_ES2) - #define USE_DEPTH_RENDERBUFFER -#else - #define USE_DEPTH_TEXTURE -#endif - -#if defined(USE_DEPTH_RENDERBUFFER) - // Create the renderbuffer that will serve as the depth attachment for the framebuffer. - glGenRenderbuffers(1, &target.depth.id); - glBindRenderbuffer(GL_RENDERBUFFER, target.depth.id); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height); // GL_DEPTH_COMPONENT24 not supported on Android -#elif defined(USE_DEPTH_TEXTURE) - // NOTE: We can also use a texture for depth buffer (GL_ARB_depth_texture/GL_OES_depth_texture extension required) - // A renderbuffer is simpler than a texture and could offer better performance on embedded devices - glGenTextures(1, &target.depth.id); - glBindTexture(GL_TEXTURE_2D, target.depth.id); - 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_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL); - glBindTexture(GL_TEXTURE_2D, 0); -#endif + if (useDepthTexture && texDepthSupported) target.depthTexture = true; // Create the framebuffer object glGenFramebuffers(1, &target.id); glBindFramebuffer(GL_FRAMEBUFFER, target.id); - // Attach color texture and depth renderbuffer to FBO - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target.texture.id, 0); -#if defined(USE_DEPTH_RENDERBUFFER) - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, target.depth.id); -#elif defined(USE_DEPTH_TEXTURE) - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, target.depth.id, 0); -#endif - - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - - if (status != GL_FRAMEBUFFER_COMPLETE) + // Create fbo color texture attachment + //----------------------------------------------------------------------------------------------------- + if ((format != -1) && (format < COMPRESSED_DXT1_RGB)) { - TraceLog(LOG_WARNING, "Framebuffer object could not be created..."); - - switch (status) - { - case GL_FRAMEBUFFER_UNSUPPORTED: TraceLog(LOG_WARNING, "Framebuffer is unsupported"); break; - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: TraceLog(LOG_WARNING, "Framebuffer incomplete attachment"); break; -#if defined(GRAPHICS_API_OPENGL_ES2) - case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: TraceLog(LOG_WARNING, "Framebuffer incomplete dimensions"); break; -#endif - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: TraceLog(LOG_WARNING, "Framebuffer incomplete missing attachment"); break; - default: break; - } - - if (target.texture.id > 0) glDeleteTextures(1, &target.texture.id); - if (target.depth.id > 0) - { -#if defined(USE_DEPTH_RENDERBUFFER) - glDeleteRenderbuffers(1, &target.depth.id); -#elif defined(USE_DEPTH_TEXTURE) - glDeleteTextures(1, &target.depth.id); -#endif - } - - glDeleteFramebuffers(1, &target.id); + // 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; } - else TraceLog(LOG_INFO, "[FBO ID %i] Framebuffer object created successfully", target.id); + //----------------------------------------------------------------------------------------------------- + + // 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 @@ -2279,6 +2238,56 @@ RenderTexture2D rlLoadRenderTexture(int width, int height) 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) + 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) + glBindFramebuffer(GL_FRAMEBUFFER, target.id); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + if (status != GL_FRAMEBUFFER_COMPLETE) + { + switch (status) + { + case GL_FRAMEBUFFER_UNSUPPORTED: TraceLog(LOG_WARNING, "Framebuffer is unsupported"); break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: TraceLog(LOG_WARNING, "Framebuffer has incomplete attachment"); break; +#if defined(GRAPHICS_API_OPENGL_ES2) + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: TraceLog(LOG_WARNING, "Framebuffer has incomplete dimensions"); break; +#endif + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: TraceLog(LOG_WARNING, "Framebuffer has a missing attachment"); break; + default: break; + } + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + result = (status == GL_FRAMEBUFFER_COMPLETE); +#endif + + return result; +} + // Generate mipmap data for selected texture void rlGenerateMipmaps(Texture2D *texture) { @@ -2290,9 +2299,9 @@ void rlGenerateMipmaps(Texture2D *texture) if (((texture->width > 0) && ((texture->width & (texture->width - 1)) == 0)) && ((texture->height > 0) && ((texture->height & (texture->height - 1)) == 0))) texIsPOT = true; - if ((texIsPOT) || (texNPOTSupported)) - { #if defined(GRAPHICS_API_OPENGL_11) + if (texIsPOT) + { // WARNING: Manual mipmap generation only works for RGBA 32bit textures! if (texture->format == UNCOMPRESSED_R8G8B8A8) { @@ -2322,14 +2331,15 @@ void rlGenerateMipmaps(Texture2D *texture) } texture->mipmaps = mipmapCount + 1; - free(data); // Once mipmaps have been generated and data has been uploaded to GPU VRAM, we can discard RAM data + RL_FREE(data); // Once mipmaps have been generated and data has been uploaded to GPU VRAM, we can discard RAM data TraceLog(LOG_WARNING, "[TEX ID %i] Mipmaps [%i] generated manually on CPU side", texture->id, texture->mipmaps); } else TraceLog(LOG_WARNING, "[TEX ID %i] Mipmaps could not be generated for texture format", texture->id); -#endif - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + } +#elif defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((texIsPOT) || (texNPOTSupported)) + { //glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE); // Hint for mipmaps generation algorythm: GL_FASTEST, GL_NICEST, GL_DONT_CARE glGenerateMipmap(GL_TEXTURE_2D); // Generate mipmaps automatically TraceLog(LOG_INFO, "[TEX ID %i] Mipmaps generated automatically", texture->id); @@ -2341,17 +2351,23 @@ void rlGenerateMipmaps(Texture2D *texture) #define MAX(a,b) (((a)>(b))?(a):(b)) texture->mipmaps = 1 + (int)floor(log(MAX(texture->width, texture->height))/log(2)); -#endif } +#endif else TraceLog(LOG_WARNING, "[TEX ID %i] Mipmaps can not be generated", texture->id); glBindTexture(GL_TEXTURE_2D, 0); } // Upload vertex data into a VAO (if supported) and VBO -// TODO: Check if mesh has already been loaded in GPU void rlLoadMesh(Mesh *mesh, bool dynamic) { + if (mesh->vaoId > 0) + { + // Check if mesh has already been loaded in GPU + TraceLog(LOG_WARNING, "Trying to re-load an already loaded mesh"); + return; + } + mesh->vaoId = 0; // Vertex Array Object mesh->vboId[0] = 0; // Vertex positions VBO mesh->vboId[1] = 0; // Vertex texcoords VBO @@ -2456,7 +2472,7 @@ void rlLoadMesh(Mesh *mesh, bool dynamic) { glGenBuffers(1, &mesh->vboId[6]); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh->vboId[6]); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short)*mesh->triangleCount*3, mesh->indices, GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short)*mesh->triangleCount*3, mesh->indices, drawHint); } if (vaoSupported) @@ -2471,8 +2487,39 @@ void rlLoadMesh(Mesh *mesh, bool dynamic) #endif } -// Update vertex data on GPU (upload new data to one buffer) -void rlUpdateMesh(Mesh mesh, int buffer, int numVertex) +// 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 (vaoSupported) 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 (vaoSupported) 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 num) +{ + rlUpdateMeshAt(mesh, buffer, num, 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 num, int index) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) // Activate mesh VAO @@ -2483,42 +2530,60 @@ void rlUpdateMesh(Mesh mesh, int buffer, int numVertex) case 0: // Update vertices (vertex position) { glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[0]); - if (numVertex >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*numVertex, mesh.vertices, GL_DYNAMIC_DRAW); - else glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*3*numVertex, mesh.vertices); + if (index == 0 && num >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*num, mesh.vertices, GL_DYNAMIC_DRAW); + else if (index + num >= mesh.vertexCount) break; + else glBufferSubData(GL_ARRAY_BUFFER, sizeof(float)*3*index, sizeof(float)*3*num, mesh.vertices); } break; case 1: // Update texcoords (vertex texture coordinates) { glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[1]); - if (numVertex >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*numVertex, mesh.texcoords, GL_DYNAMIC_DRAW); - else glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*2*numVertex, mesh.texcoords); + if (index == 0 && num >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*num, mesh.texcoords, GL_DYNAMIC_DRAW); + else if (index + num >= mesh.vertexCount) break; + else glBufferSubData(GL_ARRAY_BUFFER, sizeof(float)*2*index, sizeof(float)*2*num, mesh.texcoords); } break; case 2: // Update normals (vertex normals) { glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[2]); - if (numVertex >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*numVertex, mesh.normals, GL_DYNAMIC_DRAW); - else glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*3*numVertex, mesh.normals); + if (index == 0 && num >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*num, mesh.normals, GL_DYNAMIC_DRAW); + else if (index + num >= mesh.vertexCount) break; + else glBufferSubData(GL_ARRAY_BUFFER, sizeof(float)*3*index, sizeof(float)*3*num, mesh.normals); } break; case 3: // Update colors (vertex colors) { glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[3]); - if (numVertex >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*numVertex, mesh.colors, GL_DYNAMIC_DRAW); - else glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(unsigned char)*4*numVertex, mesh.colors); + if (index == 0 && num >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*num, mesh.colors, GL_DYNAMIC_DRAW); + else if (index + num >= mesh.vertexCount) break; + else glBufferSubData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*index, sizeof(unsigned char)*4*num, mesh.colors); } break; case 4: // Update tangents (vertex tangents) { glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[4]); - if (numVertex >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*numVertex, mesh.tangents, GL_DYNAMIC_DRAW); - else glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*4*numVertex, mesh.tangents); + if (index == 0 && num >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*num, mesh.tangents, GL_DYNAMIC_DRAW); + else if (index + num >= mesh.vertexCount) break; + else glBufferSubData(GL_ARRAY_BUFFER, sizeof(float)*4*index, sizeof(float)*4*num, mesh.tangents); } break; case 5: // Update texcoords2 (vertex second texture coordinates) { glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[5]); - if (numVertex >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*numVertex, mesh.texcoords2, GL_DYNAMIC_DRAW); - else glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*2*numVertex, mesh.texcoords2); + if (index == 0 && num >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*num, mesh.texcoords2, GL_DYNAMIC_DRAW); + else if (index + num >= mesh.vertexCount) break; + else glBufferSubData(GL_ARRAY_BUFFER, sizeof(float)*2*index, sizeof(float)*2*num, 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 && num >= mesh.triangleCount) + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(*indices)*num*3, indices, GL_DYNAMIC_DRAW); + else if (index + num >= mesh.triangleCount) + break; + else + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, sizeof(*indices)*index*3, sizeof(*indices)*num*3, indices); } break; default: break; } @@ -2599,8 +2664,16 @@ void rlDrawMesh(Mesh mesh, Material material, Matrix transform) Matrix matView = modelview; // View matrix (camera) Matrix matProjection = projection; // Projection matrix (perspective) - // Calculate model-view matrix combining matModel and matView - Matrix matModelView = MatrixMultiply(transform, matView); // Transform to camera-space coordinates + // TODO: Matrix nightmare! Trying to combine stack matrices with view matrix and local model transform matrix.. + // There is some problem in the order matrices are multiplied... it requires some time to figure out... + Matrix matStackTransform = MatrixIdentity(); + + // TODO: Consider possible transform matrices in the stack + // Is this the right order? or should we start with the first stored matrix instead of the last one? + //for (int i = stackCounter; i > 0; i--) matStackTransform = MatrixMultiply(stack[i], matStackTransform); + + Matrix matModel = MatrixMultiply(transform, matStackTransform); // Apply local model transformation + Matrix matModelView = MatrixMultiply(matModel, matView); // Transform to camera-space coordinates //----------------------------------------------------- // Bind active texture maps (if available) @@ -2620,8 +2693,6 @@ void rlDrawMesh(Mesh mesh, Material material, Matrix transform) if (vaoSupported) glBindVertexArray(mesh.vaoId); else { - // TODO: Simplify VBO binding into a for loop - // 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); @@ -2727,49 +2798,49 @@ void rlDrawMesh(Mesh mesh, Material material, Matrix transform) } // Unload mesh data from CPU and GPU -void rlUnloadMesh(Mesh *mesh) +void rlUnloadMesh(Mesh mesh) { - if (mesh->vertices != NULL) free(mesh->vertices); - if (mesh->texcoords != NULL) free(mesh->texcoords); - if (mesh->normals != NULL) free(mesh->normals); - if (mesh->colors != NULL) free(mesh->colors); - if (mesh->tangents != NULL) free(mesh->tangents); - if (mesh->texcoords2 != NULL) free(mesh->texcoords2); - if (mesh->indices != NULL) free(mesh->indices); + 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); - if (mesh->baseVertices != NULL) free(mesh->baseVertices); - if (mesh->baseNormals != NULL) free(mesh->baseNormals); - if (mesh->weightBias != NULL) free(mesh->weightBias); - if (mesh->weightId != NULL) free(mesh->weightId); + 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 + 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); + rlDeleteVertexArrays(mesh.vaoId); } // Read screen pixel data (color buffer) unsigned char *rlReadScreenPixels(int width, int height) { - unsigned char *screenData = (unsigned char *)calloc(width*height*4, sizeof(unsigned char)); + unsigned char *screenData = (unsigned char *)RL_CALLOC(width*height*4, sizeof(unsigned char)); - // NOTE: glReadPixels returns image flipped vertically -> (0,0) is the bottom left corner of the framebuffer + // NOTE 1: glReadPixels returns image flipped vertically -> (0,0) is the bottom left corner of the framebuffer + // NOTE 2: We are getting alpha channel! Be careful, it can be transparent if not cleared properly! glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, screenData); // Flip image vertically! - unsigned char *imgData = (unsigned char *)malloc(width*height*sizeof(unsigned char)*4); + unsigned char *imgData = (unsigned char *)RL_MALLOC(width*height*sizeof(unsigned char)*4); for (int y = height - 1; y >= 0; y--) { for (int x = 0; x < (width*4); x++) { - // Flip line - imgData[((height - 1) - y)*width*4 + x] = screenData[(y*width*4) + x]; + imgData[((height - 1) - y)*width*4 + x] = screenData[(y*width*4) + x]; // Flip line // Set alpha component value to 255 (no trasparent image retrieval) // NOTE: Alpha value has already been applied to RGB in framebuffer, we don't need it! @@ -2777,14 +2848,12 @@ unsigned char *rlReadScreenPixels(int width, int height) } } - free(screenData); + RL_FREE(screenData); return imgData; // NOTE: image data should be freed } // Read texture pixel data -// NOTE: 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. void *rlReadTexturePixels(Texture2D texture) { void *pixels = NULL; @@ -2793,13 +2862,11 @@ void *rlReadTexturePixels(Texture2D texture) glBindTexture(GL_TEXTURE_2D, texture.id); // NOTE: Using texture.id, we can retrieve some texture info (but not on OpenGL ES 2.0) - /* - 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); - // Other texture info: GL_TEXTURE_RED_SIZE, GL_TEXTURE_GREEN_SIZE, GL_TEXTURE_BLUE_SIZE, GL_TEXTURE_ALPHA_SIZE - */ + // 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. @@ -2813,7 +2880,7 @@ void *rlReadTexturePixels(Texture2D texture) if ((glInternalFormat != -1) && (texture.format < COMPRESSED_DXT1_RGB)) { - pixels = (unsigned char *)malloc(size); + pixels = (unsigned char *)RL_MALLOC(size); glGetTexImage(GL_TEXTURE_2D, 0, glFormat, glType, pixels); } else TraceLog(LOG_WARNING, "Texture data retrieval not suported for pixel format"); @@ -2822,90 +2889,44 @@ void *rlReadTexturePixels(Texture2D texture) #endif #if defined(GRAPHICS_API_OPENGL_ES2) - RenderTexture2D fbo = rlLoadRenderTexture(texture.width, texture.height); - - // NOTE: Two possible Options: + // 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); -#define GET_TEXTURE_FBO_OPTION_1 // It works -#if defined(GET_TEXTURE_FBO_OPTION_1) glBindFramebuffer(GL_FRAMEBUFFER, fbo.id); glBindTexture(GL_TEXTURE_2D, 0); - // Attach our texture to FBO -> Texture must be RGBA + // Attach our texture to FBO // NOTE: Previoust attached texture is automatically detached glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.id, 0); - pixels = (unsigned char *)malloc(texture.width*texture.height*4*sizeof(unsigned char)); + // Allocate enough memory to read back our texture data + pixels = (unsigned char *)RL_MALLOC(GetPixelDataSize(texture.width, texture.height, texture.format)); + + // Get OpenGL internal formats and data type from our texture format + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(texture.format, &glInternalFormat, &glFormat, &glType); // NOTE: We read data as RGBA because FBO texture is configured as RGBA, despite binding a RGB texture... - glReadPixels(0, 0, texture.width, texture.height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + glReadPixels(0, 0, texture.width, texture.height, glFormat, glType, pixels); // Re-attach internal FBO color texture before deleting it glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo.texture.id, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); -#elif defined(GET_TEXTURE_FBO_OPTION_2) - // Render texture to fbo - glBindFramebuffer(GL_FRAMEBUFFER, fbo.id); - - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - glClearDepthf(1.0f); - //glDisable(GL_TEXTURE_2D); - glEnable(GL_DEPTH_TEST); - //glDisable(GL_BLEND); - - glViewport(0, 0, texture.width, texture.height); - rlOrtho(0.0, texture.width, texture.height, 0.0, 0.0, 1.0); - - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glUseProgram(GetShaderDefault().id); - glBindTexture(GL_TEXTURE_2D, texture.id); - GenDrawQuad(); - glBindTexture(GL_TEXTURE_2D, 0); - glUseProgram(0); - - pixels = (unsigned char *)malloc(texture.width*texture.height*4*sizeof(unsigned char)); - - glReadPixels(0, 0, texture.width, texture.height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - - // Bind framebuffer 0, which means render to back buffer - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - // Reset viewport dimensions to default - glViewport(0, 0, screenWidth, screenHeight); - -#endif // GET_TEXTURE_FBO_OPTION - // Clean up temporal fbo rlDeleteRenderTextures(fbo); - #endif return pixels; } -/* -// TODO: Record draw calls to be processed in batch -// NOTE: Global state must be kept -void rlRecordDraw(void) -{ - // TODO: Before adding a new draw, check if anything changed from last stored draw -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - draws[drawsCounter].vertexCount = currentState.vertexCount; - draws[drawsCounter].vaoId = currentState.vaoId; // lines.id, trangles.id, quads.id? - draws[drawsCounter].textureId = currentState.textureId; // whiteTexture? - draws[drawsCounter].shaderId = currentState.shaderId; // defaultShader.id - draws[drawsCounter].projection = projection; - draws[drawsCounter].modelview = modelview; - - drawsCounter++; -#endif -} -*/ - //---------------------------------------------------------------------------------- // Module Functions Definition - Shaders Functions // NOTE: Those functions are exposed directly to the user in raylib.h @@ -2914,14 +2935,14 @@ void rlRecordDraw(void) // Get default internal texture (white texture) Texture2D GetTextureDefault(void) { - Texture2D texture; - - texture.id = whiteTexture; + Texture2D texture = { 0 }; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + texture.id = defaultTextureId; texture.width = 1; texture.height = 1; texture.mipmaps = 1; texture.format = UNCOMPRESSED_R8G8B8A8; - +#endif return texture; } @@ -2940,11 +2961,9 @@ Shader GetShaderDefault(void) // NOTE: text chars array should be freed manually char *LoadText(const char *fileName) { - FILE *textFile; + FILE *textFile = NULL; char *text = NULL; - int count = 0; - if (fileName != NULL) { textFile = fopen(fileName,"rt"); @@ -2952,13 +2971,13 @@ char *LoadText(const char *fileName) if (textFile != NULL) { fseek(textFile, 0, SEEK_END); - count = ftell(textFile); - rewind(textFile); + int size = ftell(textFile); + fseek(textFile, 0, SEEK_SET); - if (count > 0) + if (size > 0) { - text = (char *)malloc(sizeof(char)*(count + 1)); - count = fread(text, sizeof(char), count, textFile); + text = (char *)RL_MALLOC(sizeof(char)*(size + 1)); + int count = fread(text, sizeof(char), size, textFile); text[count] = '\0'; } @@ -2976,6 +2995,8 @@ Shader LoadShader(const char *vsFileName, const char *fsFileName) { Shader shader = { 0 }; + // NOTE: Shader.locs is allocated by LoadShaderCode() + char *vShaderStr = NULL; char *fShaderStr = NULL; @@ -2984,17 +3005,18 @@ Shader LoadShader(const char *vsFileName, const char *fsFileName) shader = LoadShaderCode(vShaderStr, fShaderStr); - if (vShaderStr != NULL) free(vShaderStr); - if (fShaderStr != NULL) free(fShaderStr); + if (vShaderStr != NULL) RL_FREE(vShaderStr); + if (fShaderStr != NULL) RL_FREE(fShaderStr); return shader; } // Load shader from code strings // NOTE: If shader string is NULL, using default vertex/fragment shaders -Shader LoadShaderCode(char *vsCode, char *fsCode) +Shader LoadShaderCode(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; @@ -3030,7 +3052,7 @@ Shader LoadShaderCode(char *vsCode, char *fsCode) glGetProgramiv(shader.id, GL_ACTIVE_UNIFORMS, &uniformCount); - for(int i = 0; i < uniformCount; i++) + for (int i = 0; i < uniformCount; i++) { int namelen = -1; int num = -1; @@ -3043,7 +3065,7 @@ Shader LoadShaderCode(char *vsCode, char *fsCode) name[namelen] = 0; // Get the location of the named uniform - GLuint location = glGetUniformLocation(shader.id, name); + unsigned int location = glGetUniformLocation(shader.id, name); TraceLog(LOG_DEBUG, "[SHDR ID %i] Active uniform [%s] set at location: %i", shader.id, name, location); } @@ -3060,6 +3082,8 @@ void UnloadShader(Shader shader) rlDeleteShader(shader.id); TraceLog(LOG_INFO, "[SHDR ID %i] Unloaded shader program data", shader.id); } + + RL_FREE(shader.locs); } // Begin custom shader mode @@ -3089,43 +3113,42 @@ int GetShaderLocation(Shader shader, const char *uniformName) #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) location = glGetUniformLocation(shader.id, uniformName); - if (location == -1) TraceLog(LOG_WARNING, "[SHDR ID %i] Shader uniform [%s] COULD NOT BE FOUND", shader.id, uniformName); - else TraceLog(LOG_INFO, "[SHDR ID %i] Shader uniform [%s] set at location: %i", shader.id, uniformName, location); + if (location == -1) TraceLog(LOG_WARNING, "[SHDR ID %i][%s] Shader uniform could not be found", shader.id, uniformName); + else TraceLog(LOG_INFO, "[SHDR ID %i][%s] Shader uniform set at location: %i", shader.id, uniformName, location); #endif return location; } -// Set shader uniform value (float) -void SetShaderValue(Shader shader, int uniformLoc, const float *value, int size) +// 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); - if (size == 1) glUniform1fv(uniformLoc, 1, value); // Shader uniform type: float - else if (size == 2) glUniform2fv(uniformLoc, 1, value); // Shader uniform type: vec2 - else if (size == 3) glUniform3fv(uniformLoc, 1, value); // Shader uniform type: vec3 - else if (size == 4) glUniform4fv(uniformLoc, 1, value); // Shader uniform type: vec4 - else TraceLog(LOG_WARNING, "Shader value float array size not supported"); + 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 uniform could not be set data type not recognized"); + } //glUseProgram(0); // Avoid reseting current shader program, in case other uniforms are set #endif } -// Set shader uniform value (int) -void SetShaderValuei(Shader shader, int uniformLoc, const int *value, int size) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glUseProgram(shader.id); - - if (size == 1) glUniform1iv(uniformLoc, 1, value); // Shader uniform type: int - else if (size == 2) glUniform2iv(uniformLoc, 1, value); // Shader uniform type: ivec2 - else if (size == 3) glUniform3iv(uniformLoc, 1, value); // Shader uniform type: ivec3 - else if (size == 4) glUniform4iv(uniformLoc, 1, value); // Shader uniform type: ivec4 - else TraceLog(LOG_WARNING, "Shader value int array size not supported"); - - //glUseProgram(0); -#endif -} // Set shader uniform value (matrix 4x4) void SetShaderValueMatrix(Shader shader, int uniformLoc, Matrix mat) @@ -3139,6 +3162,18 @@ void SetShaderValueMatrix(Shader shader, int uniformLoc, Matrix mat) #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 proj) { @@ -3147,6 +3182,23 @@ void SetMatrixProjection(Matrix proj) #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 projection; +#endif +# +} + // Set a custom modelview matrix (replaces internal modelview matrix) void SetMatrixModelview(Matrix view) { @@ -3156,12 +3208,16 @@ void SetMatrixModelview(Matrix view) } // Return internal modelview matrix -Matrix GetMatrixModelview() +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 = modelview; #endif @@ -3173,7 +3229,7 @@ Matrix GetMatrixModelview() Texture2D GenTextureCubemap(Shader shader, Texture2D skyHDR, int size) { Texture2D cubemap = { 0 }; -#if defined(GRAPHICS_API_OPENGL_33) // || defined(GRAPHICS_API_OPENGL_ES2) +#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 @@ -3183,22 +3239,32 @@ Texture2D GenTextureCubemap(Shader shader, Texture2D skyHDR, int size) 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 with 16 bit floating point values + // 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++) - glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, size, size, 0, GL_RGB, GL_FLOAT, NULL); + { +#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 (texFloatSupported) 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) @@ -3207,9 +3273,8 @@ Texture2D GenTextureCubemap(Shader shader, Texture2D skyHDR, int size) 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 + // Create projection and different views for each face Matrix fboProjection = MatrixPerspective(90.0*DEG2RAD, 1.0, 0.01, 1000.0); - //MatrixTranspose(&fboProjection); 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 }), @@ -3229,7 +3294,7 @@ Texture2D GenTextureCubemap(Shader shader, Texture2D skyHDR, int size) glViewport(0, 0, size, size); glBindFramebuffer(GL_FRAMEBUFFER, fbo); - for (unsigned int i = 0; i < 6; i++) + 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); @@ -3241,11 +3306,14 @@ Texture2D GenTextureCubemap(Shader shader, Texture2D skyHDR, int size) glBindFramebuffer(GL_FRAMEBUFFER, 0); // Reset viewport dimensions to default - glViewport(0, 0, screenWidth, screenHeight); + glViewport(0, 0, framebufferWidth, 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; } @@ -3273,7 +3341,10 @@ Texture2D GenTextureIrradiance(Shader shader, Texture2D cubemap, int size) 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); @@ -3282,7 +3353,6 @@ Texture2D GenTextureIrradiance(Shader shader, Texture2D cubemap, int size) // Create projection (transposed) and different views for each face Matrix fboProjection = MatrixPerspective(90.0*DEG2RAD, 1.0, 0.01, 1000.0); - //MatrixTranspose(&fboProjection); 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 }), @@ -3302,7 +3372,7 @@ Texture2D GenTextureIrradiance(Shader shader, Texture2D cubemap, int size) glViewport(0, 0, size, size); glBindFramebuffer(GL_FRAMEBUFFER, fbo); - for (unsigned int i = 0; i < 6; i++) + 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); @@ -3314,10 +3384,12 @@ Texture2D GenTextureIrradiance(Shader shader, Texture2D cubemap, int size) glBindFramebuffer(GL_FRAMEBUFFER, 0); // Reset viewport dimensions to default - glViewport(0, 0, screenWidth, screenHeight); + glViewport(0, 0, framebufferWidth, framebufferHeight); irradiance.width = size; irradiance.height = size; + irradiance.mipmaps = 1; + //irradiance.format = UNCOMPRESSED_R16G16B16; #endif return irradiance; } @@ -3347,7 +3419,10 @@ Texture2D GenTexturePrefilter(Shader shader, Texture2D cubemap, int size) 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); @@ -3359,7 +3434,6 @@ Texture2D GenTexturePrefilter(Shader shader, Texture2D cubemap, int size) // Create projection (transposed) and different views for each face Matrix fboProjection = MatrixPerspective(90.0*DEG2RAD, 1.0, 0.01, 1000.0); - //MatrixTranspose(&fboProjection); 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 }), @@ -3379,11 +3453,11 @@ Texture2D GenTexturePrefilter(Shader shader, Texture2D cubemap, int size) #define MAX_MIPMAP_LEVELS 5 // Max number of prefilter texture mipmaps - for (unsigned int mip = 0; mip < MAX_MIPMAP_LEVELS; mip++) + 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); + 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); @@ -3392,7 +3466,7 @@ Texture2D GenTexturePrefilter(Shader shader, Texture2D cubemap, int size) float roughness = (float)mip/(float)(MAX_MIPMAP_LEVELS - 1); glUniform1f(roughnessLoc, roughness); - for (unsigned int i = 0; i < 6; ++i) + 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); @@ -3405,24 +3479,32 @@ Texture2D GenTexturePrefilter(Shader shader, Texture2D cubemap, int size) glBindFramebuffer(GL_FRAMEBUFFER, 0); // Reset viewport dimensions to default - glViewport(0, 0, screenWidth, screenHeight); + glViewport(0, 0, framebufferWidth, 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 -// TODO: OpenGL ES 2.0 does not support GL_RGB16F texture format, neither GL_DEPTH_COMPONENT24 -Texture2D GenTextureBRDF(Shader shader, Texture2D cubemap, int size) +// 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) +#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); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RG16F, size, size, 0, GL_RG, GL_FLOAT, 0); +#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 (texFloatSupported) 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); @@ -3434,7 +3516,11 @@ Texture2D GenTextureBRDF(Shader shader, Texture2D cubemap, int size) 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); @@ -3445,11 +3531,17 @@ Texture2D GenTextureBRDF(Shader shader, Texture2D cubemap, int size) // 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, screenWidth, screenHeight); + glViewport(0, 0, framebufferWidth, framebufferHeight); brdf.width = size; brdf.height = size; + brdf.mipmaps = 1; + brdf.format = UNCOMPRESSED_R32G32B32; #endif return brdf; } @@ -3481,118 +3573,116 @@ void EndBlendMode(void) } #if defined(SUPPORT_VR_SIMULATOR) -// Get VR device information for some standard devices -VrDeviceInfo GetVrDeviceInfo(int vrDeviceType) -{ - VrDeviceInfo hmd = { 0 }; // Current VR device info - - switch (vrDeviceType) - { - case HMD_DEFAULT_DEVICE: - case HMD_OCULUS_RIFT_CV1: - { - // Oculus Rift CV1 parameters - // NOTE: CV1 represents a complete HMD redesign compared to previous versions, - // new Fresnel-hybrid-asymmetric lenses have been added and, consequently, - // previous parameters (DK2) and distortion shader (DK2) doesn't work any more. - // I just defined a set of parameters for simulator that approximate to CV1 stereo rendering - // but result is not the same obtained with Oculus PC SDK. - hmd.hResolution = 2160; // HMD horizontal resolution in pixels - hmd.vResolution = 1200; // HMD vertical resolution in pixels - hmd.hScreenSize = 0.133793f; // HMD horizontal size in meters - hmd.vScreenSize = 0.0669f; // HMD vertical size in meters - hmd.vScreenCenter = 0.04678f; // HMD screen center in meters - hmd.eyeToScreenDistance = 0.041f; // HMD distance between eye and display in meters - hmd.lensSeparationDistance = 0.07f; // HMD lens separation distance in meters - hmd.interpupillaryDistance = 0.07f; // HMD IPD (distance between pupils) in meters - hmd.lensDistortionValues[0] = 1.0f; // HMD lens distortion constant parameter 0 - hmd.lensDistortionValues[1] = 0.22f; // HMD lens distortion constant parameter 1 - hmd.lensDistortionValues[2] = 0.24f; // HMD lens distortion constant parameter 2 - hmd.lensDistortionValues[3] = 0.0f; // HMD lens distortion constant parameter 3 - hmd.chromaAbCorrection[0] = 0.996f; // HMD chromatic aberration correction parameter 0 - hmd.chromaAbCorrection[1] = -0.004f; // HMD chromatic aberration correction parameter 1 - hmd.chromaAbCorrection[2] = 1.014f; // HMD chromatic aberration correction parameter 2 - hmd.chromaAbCorrection[3] = 0.0f; // HMD chromatic aberration correction parameter 3 - - TraceLog(LOG_INFO, "Initializing VR Simulator (Oculus Rift CV1)"); - } break; - case HMD_OCULUS_RIFT_DK2: - { - // Oculus Rift DK2 parameters - hmd.hResolution = 1280; // HMD horizontal resolution in pixels - hmd.vResolution = 800; // HMD vertical resolution in pixels - hmd.hScreenSize = 0.14976f; // HMD horizontal size in meters - hmd.vScreenSize = 0.09356f; // HMD vertical size in meters - hmd.vScreenCenter = 0.04678f; // HMD screen center in meters - hmd.eyeToScreenDistance = 0.041f; // HMD distance between eye and display in meters - hmd.lensSeparationDistance = 0.0635f; // HMD lens separation distance in meters - hmd.interpupillaryDistance = 0.064f; // HMD IPD (distance between pupils) in meters - hmd.lensDistortionValues[0] = 1.0f; // HMD lens distortion constant parameter 0 - hmd.lensDistortionValues[1] = 0.22f; // HMD lens distortion constant parameter 1 - hmd.lensDistortionValues[2] = 0.24f; // HMD lens distortion constant parameter 2 - hmd.lensDistortionValues[3] = 0.0f; // HMD lens distortion constant parameter 3 - hmd.chromaAbCorrection[0] = 0.996f; // HMD chromatic aberration correction parameter 0 - hmd.chromaAbCorrection[1] = -0.004f; // HMD chromatic aberration correction parameter 1 - hmd.chromaAbCorrection[2] = 1.014f; // HMD chromatic aberration correction parameter 2 - hmd.chromaAbCorrection[3] = 0.0f; // HMD chromatic aberration correction parameter 3 - - TraceLog(LOG_INFO, "Initializing VR Simulator (Oculus Rift DK2)"); - } break; - case HMD_OCULUS_GO: - { - // TODO: Provide device display and lens parameters - } - case HMD_VALVE_HTC_VIVE: - { - // TODO: Provide device display and lens parameters - } - case HMD_SONY_PSVR: - { - // TODO: Provide device display and lens parameters - } - default: break; - } - - return hmd; -} - // Init VR simulator for selected device parameters -// NOTE: It modifies the global variable: VrStereoConfig vrConfig -void InitVrSimulator(VrDeviceInfo info) +// NOTE: It modifies the global variable: 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 - vrConfig.stereoFbo = rlLoadRenderTexture(screenWidth, screenHeight); - -#if defined(SUPPORT_DISTORTION_SHADER) - // Load distortion shader - vrConfig.distortionShader = LoadShaderCode(NULL, distortionFShaderStr); - - if (vrConfig.distortionShader.id > 0) SetShaderDefaultLocations(&vrConfig.distortionShader); -#endif - - // Set VR configutarion parameters, including distortion shader - SetStereoConfig(info); + stereoFbo = rlLoadRenderTexture(framebufferWidth, framebufferHeight, UNCOMPRESSED_R8G8B8A8, 24, false); vrSimulatorReady = true; -#endif - -#if defined(GRAPHICS_API_OPENGL_11) +#else TraceLog(LOG_WARNING, "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 (vrSimulatorReady) + if (vrSimulatorReady) rlDeleteRenderTextures(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 vrConfig for a new values assignment + memset(&vrConfig, 0, sizeof(vrConfig)); + + // Assign distortion shader + vrConfig.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 = (float)fabs(-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; + + TraceLog(LOG_DEBUG, "VR: 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 }; + + TraceLog(LOG_DEBUG, "VR: Distortion Shader: LeftLensCenter = { %f, %f }", leftLensCenter[0], leftLensCenter[1]); + TraceLog(LOG_DEBUG, "VR: Distortion Shader: RightLensCenter = { %f, %f }", rightLensCenter[0], rightLensCenter[1]); + TraceLog(LOG_DEBUG, "VR: Distortion Shader: Scale = { %f, %f }", scale[0], scale[1]); + TraceLog(LOG_DEBUG, "VR: Distortion Shader: ScaleIn = { %f, %f }", scaleIn[0], scaleIn[1]); + + // Fovy is normally computed with: 2*atan2(hmd.vScreenSize, 2*hmd.eyeToScreenDistance) + // ...but with lens distortion it is increased (see Oculus SDK Documentation) + //float fovy = 2.0f*atan2(hmd.vScreenSize*0.5f*distortionScale, hmd.eyeToScreenDistance); // Really need distortionScale? + float fovy = 2.0f*(float)atan2(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, 0.01, 1000.0); + vrConfig.eyesProjection[0] = MatrixMultiply(proj, MatrixTranslate(projOffset, 0.0f, 0.0f)); + vrConfig.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. + vrConfig.eyesViewOffset[0] = MatrixTranslate(-hmd.interpupillaryDistance*0.5f, 0.075f, 0.045f); + vrConfig.eyesViewOffset[1] = MatrixTranslate(hmd.interpupillaryDistance*0.5f, 0.075f, 0.045f); + + // Compute eyes Viewports + vrConfig.eyeViewportRight[2] = hmd.hResolution/2; + vrConfig.eyeViewportRight[3] = hmd.vResolution; + + vrConfig.eyeViewportLeft[0] = hmd.hResolution/2; + vrConfig.eyeViewportLeft[1] = 0; + vrConfig.eyeViewportLeft[2] = hmd.hResolution/2; + vrConfig.eyeViewportLeft[3] = hmd.vResolution; + + if (vrConfig.distortionShader.id > 0) { - rlDeleteRenderTextures(vrConfig.stereoFbo); // Unload stereo framebuffer and texture - #if defined(SUPPORT_DISTORTION_SHADER) - UnloadShader(vrConfig.distortionShader); // Unload distortion shader - #endif + // Update distortion shader with lens and distortion-scale parameters + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "leftLensCenter"), leftLensCenter, UNIFORM_VEC2); + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "rightLensCenter"), rightLensCenter, UNIFORM_VEC2); + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "leftScreenCenter"), leftScreenCenter, UNIFORM_VEC2); + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "rightScreenCenter"), rightScreenCenter, UNIFORM_VEC2); + + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "scale"), scale, UNIFORM_VEC2); + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "scaleIn"), scaleIn, UNIFORM_VEC2); + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "hmdWarpParam"), hmd.lensDistortionValues, UNIFORM_VEC4); + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "chromaAbParam"), hmd.chromaAbCorrection, UNIFORM_VEC4); } #endif } @@ -3607,17 +3697,6 @@ bool IsVrSimulatorReady(void) #endif } -// Set VR distortion shader for stereoscopic rendering -// TODO: Review VR system to be more flexible, move distortion shader to user side -void SetVrDistortionShader(Shader shader) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - vrConfig.distortionShader = shader; - - //SetStereoConfig(info); // TODO: Must be reviewed to set new distortion shader uniform values... -#endif -} - // Enable/Disable VR experience (device or simulator) void ToggleVrMode(void) { @@ -3629,45 +3708,33 @@ void ToggleVrMode(void) vrStereoRender = false; // Reset viewport and default projection-modelview matrices - rlViewport(0, 0, screenWidth, screenHeight); - projection = MatrixOrtho(0.0, screenWidth, screenHeight, 0.0, 0.0, 1.0); + rlViewport(0, 0, framebufferWidth, framebufferHeight); + projection = MatrixOrtho(0.0, framebufferWidth, framebufferHeight, 0.0, 0.0, 1.0); modelview = MatrixIdentity(); } else vrStereoRender = true; #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 -} - -// Begin Oculus drawing configuration +// Begin VR drawing configuration void BeginVrDrawing(void) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) if (vrSimulatorReady) { - // Setup framebuffer for stereo rendering - rlEnableRenderTexture(vrConfig.stereoFbo.id); - // NOTE: If your application is configured to treat the texture as a linear format (e.g. GL_RGBA) - // and performs linear-to-gamma conversion in GLSL or does not care about gamma-correction, then: - // - Require OculusBuffer format to be OVR_FORMAT_R8G8B8A8_UNORM_SRGB - // - Do NOT enable GL_FRAMEBUFFER_SRGB - //glEnable(GL_FRAMEBUFFER_SRGB); + rlEnableRenderTexture(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(s) + //glViewport(0, 0, buffer.width, buffer.height); // Useful if rendering to separate framebuffers (every eye) + rlClearScreenBuffers(); // Clear current framebuffer vrStereoRender = true; } #endif } -// End Oculus drawing process (and desktop mirror) +// End VR drawing process (and desktop mirror) void EndVrDrawing(void) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) @@ -3680,23 +3747,20 @@ void EndVrDrawing(void) rlClearScreenBuffers(); // Clear current framebuffer // Set viewport to default framebuffer size (screen size) - rlViewport(0, 0, screenWidth, screenHeight); + rlViewport(0, 0, framebufferWidth, framebufferHeight); // Let rlgl reconfigure internal matrices rlMatrixMode(RL_PROJECTION); // Enable internal projection matrix rlLoadIdentity(); // Reset internal projection matrix - rlOrtho(0.0, screenWidth, screenHeight, 0.0, 0.0, 1.0); // Recalculate internal projection matrix + rlOrtho(0.0, framebufferWidth, framebufferHeight, 0.0, 0.0, 1.0); // Recalculate internal projection matrix rlMatrixMode(RL_MODELVIEW); // Enable internal modelview matrix rlLoadIdentity(); // Reset internal modelview matrix -#if defined(SUPPORT_DISTORTION_SHADER) - // Draw RenderTexture (stereoFbo) using distortion shader - currentShader = vrConfig.distortionShader; -#else - currentShader = GetShaderDefault(); -#endif + // Draw RenderTexture (stereoFbo) using distortion shader if available + if (vrConfig.distortionShader.id > 0) currentShader = vrConfig.distortionShader; + else currentShader = GetShaderDefault(); - rlEnableTexture(vrConfig.stereoFbo.texture.id); + rlEnableTexture(stereoFbo.texture.id); rlPushMatrix(); rlBegin(RL_QUADS); @@ -3709,15 +3773,15 @@ void EndVrDrawing(void) // Bottom-right corner for texture and quad rlTexCoord2f(0.0f, 0.0f); - rlVertex2f(0.0f, (float)vrConfig.stereoFbo.texture.height); + rlVertex2f(0.0f, (float)stereoFbo.texture.height); // Top-right corner for texture and quad rlTexCoord2f(1.0f, 0.0f); - rlVertex2f( (float)vrConfig.stereoFbo.texture.width, (float)vrConfig.stereoFbo.texture.height); + rlVertex2f( (float)stereoFbo.texture.width, (float)stereoFbo.texture.height); // Top-left corner for texture and quad rlTexCoord2f(1.0f, 1.0f); - rlVertex2f( (float)vrConfig.stereoFbo.texture.width, 0.0f); + rlVertex2f( (float)stereoFbo.texture.width, 0.0f); rlEnd(); rlPopMatrix(); @@ -3731,8 +3795,8 @@ void EndVrDrawing(void) currentShader = defaultShader; // Reset viewport and default projection-modelview matrices - rlViewport(0, 0, screenWidth, screenHeight); - projection = MatrixOrtho(0.0, screenWidth, screenHeight, 0.0, 0.0, 1.0); + rlViewport(0, 0, framebufferWidth, framebufferHeight); + projection = MatrixOrtho(0.0, framebufferWidth, framebufferHeight, 0.0, 0.0, 1.0); modelview = MatrixIdentity(); rlDisableDepthTest(); @@ -3764,7 +3828,7 @@ static unsigned int CompileShader(const char *shaderStr, int type) glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); #if defined(_MSC_VER) - char *log = malloc(maxLength); + char *log = RL_MALLOC(maxLength); #else char log[maxLength]; #endif @@ -3773,7 +3837,7 @@ static unsigned int CompileShader(const char *shaderStr, int type) TraceLog(LOG_INFO, "%s", log); #if defined(_MSC_VER) - free(log); + RL_FREE(log); #endif } else TraceLog(LOG_INFO, "[SHDR ID %i] Shader compiled successfully", shader); @@ -3820,7 +3884,7 @@ static unsigned int LoadShaderProgram(unsigned int vShaderId, unsigned int fShad glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); #if defined(_MSC_VER) - char *log = malloc(maxLength); + char *log = RL_MALLOC(maxLength); #else char log[maxLength]; #endif @@ -3829,7 +3893,7 @@ static unsigned int LoadShaderProgram(unsigned int vShaderId, unsigned int fShad TraceLog(LOG_INFO, "%s", log); #if defined(_MSC_VER) - free(log); + RL_FREE(log); #endif glDeleteProgram(program); @@ -3842,16 +3906,17 @@ static unsigned int LoadShaderProgram(unsigned int vShaderId, unsigned int fShad // Load default shader (just vertex positioning and texture coloring) -// NOTE: This shader program is used for batch buffers (lines, triangles, quads) +// NOTE: This shader program is used for internal buffers static Shader LoadShaderDefault(void) { 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; // Vertex shader directly defined, no external file required - char defaultVShaderStr[] = + const char *defaultVShaderStr = #if defined(GRAPHICS_API_OPENGL_21) "#version 120 \n" #elif defined(GRAPHICS_API_OPENGL_ES2) @@ -3880,7 +3945,7 @@ static Shader LoadShaderDefault(void) "} \n"; // Fragment shader directly defined, no external file required - char defaultFShaderStr[] = + const char *defaultFShaderStr = #if defined(GRAPHICS_API_OPENGL_21) "#version 120 \n" #elif defined(GRAPHICS_API_OPENGL_ES2) @@ -3983,171 +4048,93 @@ static void UnloadShaderDefault(void) glDeleteProgram(defaultShader.id); } -// Load default internal buffers (lines, triangles, quads) +// Load default internal buffers static void LoadBuffersDefault(void) { - // [CPU] Allocate and initialize float array buffers to store vertex data (lines, triangles, quads) + // Initialize CPU (RAM) arrays (vertex position, texcoord, color data and indexes) //-------------------------------------------------------------------------------------------- - - // Lines - Initialize arrays (vertex position and color data) - lines.vertices = (float *)malloc(sizeof(float)*3*2*MAX_LINES_BATCH); // 3 float by vertex, 2 vertex by line - lines.colors = (unsigned char *)malloc(sizeof(unsigned char)*4*2*MAX_LINES_BATCH); // 4 float by color, 2 colors by line - lines.texcoords = NULL; - lines.indices = NULL; - - for (int i = 0; i < (3*2*MAX_LINES_BATCH); i++) lines.vertices[i] = 0.0f; - for (int i = 0; i < (4*2*MAX_LINES_BATCH); i++) lines.colors[i] = 0; - - lines.vCounter = 0; - lines.cCounter = 0; - lines.tcCounter = 0; - - // Triangles - Initialize arrays (vertex position and color data) - triangles.vertices = (float *)malloc(sizeof(float)*3*3*MAX_TRIANGLES_BATCH); // 3 float by vertex, 3 vertex by triangle - triangles.colors = (unsigned char *)malloc(sizeof(unsigned char)*4*3*MAX_TRIANGLES_BATCH); // 4 float by color, 3 colors by triangle - triangles.texcoords = NULL; - triangles.indices = NULL; - - for (int i = 0; i < (3*3*MAX_TRIANGLES_BATCH); i++) triangles.vertices[i] = 0.0f; - for (int i = 0; i < (4*3*MAX_TRIANGLES_BATCH); i++) triangles.colors[i] = 0; - - triangles.vCounter = 0; - triangles.cCounter = 0; - triangles.tcCounter = 0; - - // Quads - Initialize arrays (vertex position, texcoord, color data and indexes) - quads.vertices = (float *)malloc(sizeof(float)*3*4*MAX_QUADS_BATCH); // 3 float by vertex, 4 vertex by quad - quads.texcoords = (float *)malloc(sizeof(float)*2*4*MAX_QUADS_BATCH); // 2 float by texcoord, 4 texcoord by quad - quads.colors = (unsigned char *)malloc(sizeof(unsigned char)*4*4*MAX_QUADS_BATCH); // 4 float by color, 4 colors by quad + for (int i = 0; i < MAX_BATCH_BUFFERING; i++) + { + vertexData[i].vertices = (float *)RL_MALLOC(sizeof(float)*3*4*MAX_BATCH_ELEMENTS); // 3 float by vertex, 4 vertex by quad + vertexData[i].texcoords = (float *)RL_MALLOC(sizeof(float)*2*4*MAX_BATCH_ELEMENTS); // 2 float by texcoord, 4 texcoord by quad + vertexData[i].colors = (unsigned char *)RL_MALLOC(sizeof(unsigned char)*4*4*MAX_BATCH_ELEMENTS); // 4 float by color, 4 colors by quad #if defined(GRAPHICS_API_OPENGL_33) - quads.indices = (unsigned int *)malloc(sizeof(unsigned int)*6*MAX_QUADS_BATCH); // 6 int by quad (indices) + vertexData[i].indices = (unsigned int *)RL_MALLOC(sizeof(unsigned int)*6*MAX_BATCH_ELEMENTS); // 6 int by quad (indices) #elif defined(GRAPHICS_API_OPENGL_ES2) - quads.indices = (unsigned short *)malloc(sizeof(unsigned short)*6*MAX_QUADS_BATCH); // 6 int by quad (indices) + vertexData[i].indices = (unsigned short *)RL_MALLOC(sizeof(unsigned short)*6*MAX_BATCH_ELEMENTS); // 6 int by quad (indices) #endif - for (int i = 0; i < (3*4*MAX_QUADS_BATCH); i++) quads.vertices[i] = 0.0f; - for (int i = 0; i < (2*4*MAX_QUADS_BATCH); i++) quads.texcoords[i] = 0.0f; - for (int i = 0; i < (4*4*MAX_QUADS_BATCH); i++) quads.colors[i] = 0; + for (int j = 0; j < (3*4*MAX_BATCH_ELEMENTS); j++) vertexData[i].vertices[j] = 0.0f; + for (int j = 0; j < (2*4*MAX_BATCH_ELEMENTS); j++) vertexData[i].texcoords[j] = 0.0f; + for (int j = 0; j < (4*4*MAX_BATCH_ELEMENTS); j++) vertexData[i].colors[j] = 0; - int k = 0; + int k = 0; - // Indices can be initialized right now - for (int i = 0; i < (6*MAX_QUADS_BATCH); i+=6) - { - quads.indices[i] = 4*k; - quads.indices[i+1] = 4*k+1; - quads.indices[i+2] = 4*k+2; - quads.indices[i+3] = 4*k; - quads.indices[i+4] = 4*k+2; - quads.indices[i+5] = 4*k+3; + // Indices can be initialized right now + for (int j = 0; j < (6*MAX_BATCH_ELEMENTS); j += 6) + { + vertexData[i].indices[j] = 4*k; + vertexData[i].indices[j + 1] = 4*k + 1; + vertexData[i].indices[j + 2] = 4*k + 2; + vertexData[i].indices[j + 3] = 4*k; + vertexData[i].indices[j + 4] = 4*k + 2; + vertexData[i].indices[j + 5] = 4*k + 3; - k++; + k++; + } + + vertexData[i].vCounter = 0; + vertexData[i].tcCounter = 0; + vertexData[i].cCounter = 0; } - quads.vCounter = 0; - quads.tcCounter = 0; - quads.cCounter = 0; - - TraceLog(LOG_INFO, "[CPU] Default buffers initialized successfully (lines, triangles, quads)"); + TraceLog(LOG_INFO, "Internal buffers initialized successfully (CPU)"); //-------------------------------------------------------------------------------------------- - // [GPU] Upload vertex data and initialize VAOs/VBOs (lines, triangles, quads) - // NOTE: Default buffers are linked to use currentShader (defaultShader) + // Upload to GPU (VRAM) vertex data and initialize VAOs/VBOs //-------------------------------------------------------------------------------------------- - - // Upload and link lines vertex buffers - if (vaoSupported) + for (int i = 0; i < MAX_BATCH_BUFFERING; i++) { - // Initialize Lines VAO - glGenVertexArrays(1, &lines.vaoId); - glBindVertexArray(lines.vaoId); - } + if (vaoSupported) + { + // Initialize Quads VAO + glGenVertexArrays(1, &vertexData[i].vaoId); + glBindVertexArray(vertexData[i].vaoId); + } - // Lines - Vertex buffers binding and attributes enable - // Vertex position buffer (shader-location = 0) - glGenBuffers(2, &lines.vboId[0]); - glBindBuffer(GL_ARRAY_BUFFER, lines.vboId[0]); - glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*2*MAX_LINES_BATCH, lines.vertices, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_POSITION]); - glVertexAttribPointer(currentShader.locs[LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); + // Quads - Vertex buffers binding and attributes enable + // Vertex position buffer (shader-location = 0) + glGenBuffers(1, &vertexData[i].vboId[0]); + glBindBuffer(GL_ARRAY_BUFFER, vertexData[i].vboId[0]); + glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*MAX_BATCH_ELEMENTS, vertexData[i].vertices, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_POSITION]); + glVertexAttribPointer(currentShader.locs[LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); - // Vertex color buffer (shader-location = 3) - glGenBuffers(2, &lines.vboId[1]); - glBindBuffer(GL_ARRAY_BUFFER, lines.vboId[1]); - glBufferData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*2*MAX_LINES_BATCH, lines.colors, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_COLOR]); - glVertexAttribPointer(currentShader.locs[LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + // Vertex texcoord buffer (shader-location = 1) + glGenBuffers(1, &vertexData[i].vboId[1]); + glBindBuffer(GL_ARRAY_BUFFER, vertexData[i].vboId[1]); + glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*MAX_BATCH_ELEMENTS, vertexData[i].texcoords, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_TEXCOORD01]); + glVertexAttribPointer(currentShader.locs[LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); - if (vaoSupported) TraceLog(LOG_INFO, "[VAO ID %i] Default buffers VAO initialized successfully (lines)", lines.vaoId); - else TraceLog(LOG_INFO, "[VBO ID %i][VBO ID %i] Default buffers VBOs initialized successfully (lines)", lines.vboId[0], lines.vboId[1]); + // Vertex color buffer (shader-location = 3) + glGenBuffers(1, &vertexData[i].vboId[2]); + glBindBuffer(GL_ARRAY_BUFFER, vertexData[i].vboId[2]); + glBufferData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*4*MAX_BATCH_ELEMENTS, vertexData[i].colors, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_COLOR]); + glVertexAttribPointer(currentShader.locs[LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - // Upload and link triangles vertex buffers - if (vaoSupported) - { - // Initialize Triangles VAO - glGenVertexArrays(1, &triangles.vaoId); - glBindVertexArray(triangles.vaoId); - } - - // Triangles - Vertex buffers binding and attributes enable - // Vertex position buffer (shader-location = 0) - glGenBuffers(1, &triangles.vboId[0]); - glBindBuffer(GL_ARRAY_BUFFER, triangles.vboId[0]); - glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*3*MAX_TRIANGLES_BATCH, triangles.vertices, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_POSITION]); - glVertexAttribPointer(currentShader.locs[LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); - - // Vertex color buffer (shader-location = 3) - glGenBuffers(1, &triangles.vboId[1]); - glBindBuffer(GL_ARRAY_BUFFER, triangles.vboId[1]); - glBufferData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*3*MAX_TRIANGLES_BATCH, triangles.colors, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_COLOR]); - glVertexAttribPointer(currentShader.locs[LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - - if (vaoSupported) TraceLog(LOG_INFO, "[VAO ID %i] Default buffers VAO initialized successfully (triangles)", triangles.vaoId); - else TraceLog(LOG_INFO, "[VBO ID %i][VBO ID %i] Default buffers VBOs initialized successfully (triangles)", triangles.vboId[0], triangles.vboId[1]); - - // Upload and link quads vertex buffers - if (vaoSupported) - { - // Initialize Quads VAO - glGenVertexArrays(1, &quads.vaoId); - glBindVertexArray(quads.vaoId); - } - - // Quads - Vertex buffers binding and attributes enable - // Vertex position buffer (shader-location = 0) - glGenBuffers(1, &quads.vboId[0]); - glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[0]); - glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*MAX_QUADS_BATCH, quads.vertices, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_POSITION]); - glVertexAttribPointer(currentShader.locs[LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); - - // Vertex texcoord buffer (shader-location = 1) - glGenBuffers(1, &quads.vboId[1]); - glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[1]); - glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*MAX_QUADS_BATCH, quads.texcoords, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_TEXCOORD01]); - glVertexAttribPointer(currentShader.locs[LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); - - // Vertex color buffer (shader-location = 3) - glGenBuffers(1, &quads.vboId[2]); - glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[2]); - glBufferData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*4*MAX_QUADS_BATCH, quads.colors, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_COLOR]); - glVertexAttribPointer(currentShader.locs[LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - - // Fill index buffer - glGenBuffers(1, &quads.vboId[3]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quads.vboId[3]); + // Fill index buffer + glGenBuffers(1, &vertexData[i].vboId[3]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vertexData[i].vboId[3]); #if defined(GRAPHICS_API_OPENGL_33) - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int)*6*MAX_QUADS_BATCH, quads.indices, GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int)*6*MAX_BATCH_ELEMENTS, vertexData[i].indices, GL_STATIC_DRAW); #elif defined(GRAPHICS_API_OPENGL_ES2) - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(short)*6*MAX_QUADS_BATCH, quads.indices, GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(short)*6*MAX_BATCH_ELEMENTS, vertexData[i].indices, GL_STATIC_DRAW); #endif + } - if (vaoSupported) TraceLog(LOG_INFO, "[VAO ID %i] Default buffers VAO initialized successfully (quads)", quads.vaoId); - else TraceLog(LOG_INFO, "[VBO ID %i][VBO ID %i][VBO ID %i][VBO ID %i] Default buffers VBOs initialized successfully (quads)", quads.vboId[0], quads.vboId[1], quads.vboId[2], quads.vboId[3]); + TraceLog(LOG_INFO, "Internal buffers uploaded successfully (GPU)"); // Unbind the current VAO if (vaoSupported) glBindVertexArray(0); @@ -4159,74 +4146,48 @@ static void LoadBuffersDefault(void) // TODO: If no data changed on the CPU arrays --> No need to re-update GPU arrays (change flag required) static void UpdateBuffersDefault(void) { - // Update lines vertex buffers - if (lines.vCounter > 0) + // Update vertex buffers data + if (vertexData[currentBuffer].vCounter > 0) { - // Activate Lines VAO - if (vaoSupported) glBindVertexArray(lines.vaoId); + // Activate elements VAO + if (vaoSupported) glBindVertexArray(vertexData[currentBuffer].vaoId); - // Lines - vertex positions buffer - glBindBuffer(GL_ARRAY_BUFFER, lines.vboId[0]); - //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*2*MAX_LINES_BATCH, lines.vertices, GL_DYNAMIC_DRAW); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*3*lines.vCounter, lines.vertices); // target - offset (in bytes) - size (in bytes) - data pointer + // Vertex positions buffer + glBindBuffer(GL_ARRAY_BUFFER, vertexData[currentBuffer].vboId[0]); + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*3*vertexData[currentBuffer].vCounter, vertexData[currentBuffer].vertices); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*MAX_BATCH_ELEMENTS, vertexData[currentBuffer].vertices, GL_DYNAMIC_DRAW); // Update all buffer - // Lines - colors buffer - glBindBuffer(GL_ARRAY_BUFFER, lines.vboId[1]); - //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*2*MAX_LINES_BATCH, lines.colors, GL_DYNAMIC_DRAW); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(unsigned char)*4*lines.cCounter, lines.colors); + // Texture coordinates buffer + glBindBuffer(GL_ARRAY_BUFFER, vertexData[currentBuffer].vboId[1]); + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*2*vertexData[currentBuffer].vCounter, vertexData[currentBuffer].texcoords); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*MAX_BATCH_ELEMENTS, vertexData[currentBuffer].texcoords, GL_DYNAMIC_DRAW); // Update all buffer + + // Colors buffer + glBindBuffer(GL_ARRAY_BUFFER, vertexData[currentBuffer].vboId[2]); + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(unsigned char)*4*vertexData[currentBuffer].vCounter, vertexData[currentBuffer].colors); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*4*MAX_BATCH_ELEMENTS, vertexData[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... + // vertexData[currentBuffer].vertices = (float *)glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE); + // if (vertexData[currentBuffer].vertices) + // { + // Update vertex data + // } + // glUnmapBuffer(GL_ARRAY_BUFFER); + + // Unbind the current VAO + if (vaoSupported) glBindVertexArray(0); } - - // Update triangles vertex buffers - if (triangles.vCounter > 0) - { - // Activate Triangles VAO - if (vaoSupported) glBindVertexArray(triangles.vaoId); - - // Triangles - vertex positions buffer - glBindBuffer(GL_ARRAY_BUFFER, triangles.vboId[0]); - //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*3*MAX_TRIANGLES_BATCH, triangles.vertices, GL_DYNAMIC_DRAW); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*3*triangles.vCounter, triangles.vertices); - - // Triangles - colors buffer - glBindBuffer(GL_ARRAY_BUFFER, triangles.vboId[1]); - //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*3*MAX_TRIANGLES_BATCH, triangles.colors, GL_DYNAMIC_DRAW); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(unsigned char)*4*triangles.cCounter, triangles.colors); - } - - // Update quads vertex buffers - if (quads.vCounter > 0) - { - // Activate Quads VAO - if (vaoSupported) glBindVertexArray(quads.vaoId); - - // Quads - vertex positions buffer - glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[0]); - //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*MAX_QUADS_BATCH, quads.vertices, GL_DYNAMIC_DRAW); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*3*quads.vCounter, quads.vertices); - - // Quads - texture coordinates buffer - glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[1]); - //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*MAX_QUADS_BATCH, quads.texcoords, GL_DYNAMIC_DRAW); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*2*quads.vCounter, quads.texcoords); - - // Quads - colors buffer - glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[2]); - //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*4*MAX_QUADS_BATCH, quads.colors, GL_DYNAMIC_DRAW); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(unsigned char)*4*quads.vCounter, quads.colors); - - // Another option would be using buffer mapping... - //quads.vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE); - // Now we can modify vertices - //glUnmapBuffer(GL_ARRAY_BUFFER); - } - //-------------------------------------------------------------- - - // Unbind the current VAO - if (vaoSupported) glBindVertexArray(0); } // Draw default internal buffers vertex data -// NOTE: We draw in this order: lines, triangles, quads static void DrawBuffersDefault(void) { Matrix matProjection = projection; @@ -4239,13 +4200,14 @@ static void DrawBuffersDefault(void) for (int eye = 0; eye < eyesCount; eye++) { - #if defined(SUPPORT_VR_SIMULATOR) +#if defined(SUPPORT_VR_SIMULATOR) if (eyesCount == 2) SetStereoView(eye, matProjection, matModelView); - #endif +#endif - // Set current shader and upload current MVP matrix - if ((lines.vCounter > 0) || (triangles.vCounter > 0) || (quads.vCounter > 0)) + // Draw buffers + if (vertexData[currentBuffer].vCounter > 0) { + // Set current shader and upload current MVP matrix glUseProgram(currentShader.id); // Create modelview-projection matrix @@ -4253,120 +4215,61 @@ static void DrawBuffersDefault(void) glUniformMatrix4fv(currentShader.locs[LOC_MATRIX_MVP], 1, false, MatrixToFloat(matMVP)); glUniform4f(currentShader.locs[LOC_COLOR_DIFFUSE], 1.0f, 1.0f, 1.0f, 1.0f); - glUniform1i(currentShader.locs[LOC_MAP_DIFFUSE], 0); + glUniform1i(currentShader.locs[LOC_MAP_DIFFUSE], 0); // Provided value refers to the texture unit (active) - // NOTE: Additional map textures not considered for default buffers drawing - } + // TODO: Support additional texture units on custom shader + //if (currentShader->locs[LOC_MAP_SPECULAR] > 0) glUniform1i(currentShader.locs[LOC_MAP_SPECULAR], 1); + //if (currentShader->locs[LOC_MAP_NORMAL] > 0) glUniform1i(currentShader.locs[LOC_MAP_NORMAL], 2); - // Draw lines buffers - if (lines.vCounter > 0) - { - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, whiteTexture); + // NOTE: Right now additional map textures not considered for default buffers drawing - if (vaoSupported) - { - glBindVertexArray(lines.vaoId); - } + int vertexOffset = 0; + + if (vaoSupported) glBindVertexArray(vertexData[currentBuffer].vaoId); else { // Bind vertex attrib: position (shader-location = 0) - glBindBuffer(GL_ARRAY_BUFFER, lines.vboId[0]); - glVertexAttribPointer(currentShader.locs[LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_POSITION]); - - // Bind vertex attrib: color (shader-location = 3) - glBindBuffer(GL_ARRAY_BUFFER, lines.vboId[1]); - glVertexAttribPointer(currentShader.locs[LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_COLOR]); - } - - glDrawArrays(GL_LINES, 0, lines.vCounter); - - if (!vaoSupported) glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindTexture(GL_TEXTURE_2D, 0); - } - - // Draw triangles buffers - if (triangles.vCounter > 0) - { - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, whiteTexture); - - if (vaoSupported) - { - glBindVertexArray(triangles.vaoId); - } - else - { - // Bind vertex attrib: position (shader-location = 0) - glBindBuffer(GL_ARRAY_BUFFER, triangles.vboId[0]); - glVertexAttribPointer(currentShader.locs[LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_POSITION]); - - // Bind vertex attrib: color (shader-location = 3) - glBindBuffer(GL_ARRAY_BUFFER, triangles.vboId[1]); - glVertexAttribPointer(currentShader.locs[LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_COLOR]); - } - - glDrawArrays(GL_TRIANGLES, 0, triangles.vCounter); - - if (!vaoSupported) glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindTexture(GL_TEXTURE_2D, 0); - } - - // Draw quads buffers - if (quads.vCounter > 0) - { - int quadsCount = 0; - int numIndicesToProcess = 0; - int indicesOffset = 0; - - if (vaoSupported) - { - glBindVertexArray(quads.vaoId); - } - else - { - // Bind vertex attrib: position (shader-location = 0) - glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[0]); + glBindBuffer(GL_ARRAY_BUFFER, vertexData[currentBuffer].vboId[0]); glVertexAttribPointer(currentShader.locs[LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_POSITION]); // Bind vertex attrib: texcoord (shader-location = 1) - glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[1]); + glBindBuffer(GL_ARRAY_BUFFER, vertexData[currentBuffer].vboId[1]); glVertexAttribPointer(currentShader.locs[LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_TEXCOORD01]); // Bind vertex attrib: color (shader-location = 3) - glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[2]); + glBindBuffer(GL_ARRAY_BUFFER, vertexData[currentBuffer].vboId[2]); glVertexAttribPointer(currentShader.locs[LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_COLOR]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quads.vboId[3]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vertexData[currentBuffer].vboId[3]); } + glActiveTexture(GL_TEXTURE0); + for (int i = 0; i < drawsCounter; i++) { - quadsCount = draws[i].vertexCount/4; - numIndicesToProcess = quadsCount*6; // Get number of Quads*6 index by Quad - - //TraceLog(LOG_DEBUG, "Quads to render: %i - Vertex Count: %i", quadsCount, draws[i].vertexCount); - - glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, draws[i].textureId); - // 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 - #if defined(GRAPHICS_API_OPENGL_33) - glDrawElements(GL_TRIANGLES, numIndicesToProcess, GL_UNSIGNED_INT, (GLvoid *)(sizeof(GLuint)*indicesOffset)); - #elif defined(GRAPHICS_API_OPENGL_ES2) - glDrawElements(GL_TRIANGLES, numIndicesToProcess, GL_UNSIGNED_SHORT, (GLvoid *)(sizeof(GLushort)*indicesOffset)); - #endif - //GLenum err; - //if ((err = glGetError()) != GL_NO_ERROR) TraceLog(LOG_INFO, "OpenGL error: %i", (int)err); //GL_INVALID_ENUM! + // TODO: Find some way to bind additional textures --> Use global texture IDs? Register them on draw[i]? + //if (currentShader->locs[LOC_MAP_SPECULAR] > 0) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, textureUnit1_id); } + //if (currentShader->locs[LOC_MAP_SPECULAR] > 0) { glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, textureUnit2_id); } - indicesOffset += draws[i].vertexCount/4*6; + if ((draws[i].mode == RL_LINES) || (draws[i].mode == RL_TRIANGLES)) glDrawArrays(draws[i].mode, vertexOffset, 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, draws[i].vertexCount/4*6, GL_UNSIGNED_INT, (GLvoid *)(sizeof(GLuint)*vertexOffset/4*6)); +#elif defined(GRAPHICS_API_OPENGL_ES2) + glDrawElements(GL_TRIANGLES, draws[i].vertexCount/4*6, GL_UNSIGNED_SHORT, (GLvoid *)(sizeof(GLushort)*vertexOffset/4*6)); +#endif + } + + vertexOffset += (draws[i].vertexCount + draws[i].vertexAlignment); } if (!vaoSupported) @@ -4384,13 +4287,9 @@ static void DrawBuffersDefault(void) } // Reset vertex counters for next frame - lines.vCounter = 0; - lines.cCounter = 0; - triangles.vCounter = 0; - triangles.cCounter = 0; - quads.vCounter = 0; - quads.tcCounter = 0; - quads.cCounter = 0; + vertexData[currentBuffer].vCounter = 0; + vertexData[currentBuffer].tcCounter = 0; + vertexData[currentBuffer].cCounter = 0; // Reset depth for next draw currentDepth = -1.0f; @@ -4399,10 +4298,19 @@ static void DrawBuffersDefault(void) projection = matProjection; modelview = matModelView; - // Reset draws counter + // Reset draws array + for (int i = 0; i < MAX_DRAWCALL_REGISTERED; i++) + { + draws[i].mode = RL_QUADS; + draws[i].vertexCount = 0; + draws[i].textureId = defaultTextureId; + } + drawsCounter = 1; - draws[0].textureId = whiteTexture; - draws[0].vertexCount = 0; + + // Change to next buffer in the list + currentBuffer++; + if (currentBuffer >= MAX_BATCH_BUFFERING) currentBuffer = 0; } // Unload default internal buffers vertex data from CPU and GPU @@ -4417,35 +4325,23 @@ static void UnloadBuffersDefault(void) glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - // Delete VBOs from GPU (VRAM) - glDeleteBuffers(1, &lines.vboId[0]); - glDeleteBuffers(1, &lines.vboId[1]); - glDeleteBuffers(1, &triangles.vboId[0]); - glDeleteBuffers(1, &triangles.vboId[1]); - glDeleteBuffers(1, &quads.vboId[0]); - glDeleteBuffers(1, &quads.vboId[1]); - glDeleteBuffers(1, &quads.vboId[2]); - glDeleteBuffers(1, &quads.vboId[3]); - - if (vaoSupported) + for (int i = 0; i < MAX_BATCH_BUFFERING; i++) { + // Delete VBOs from GPU (VRAM) + glDeleteBuffers(1, &vertexData[i].vboId[0]); + glDeleteBuffers(1, &vertexData[i].vboId[1]); + glDeleteBuffers(1, &vertexData[i].vboId[2]); + glDeleteBuffers(1, &vertexData[i].vboId[3]); + // Delete VAOs from GPU (VRAM) - glDeleteVertexArrays(1, &lines.vaoId); - glDeleteVertexArrays(1, &triangles.vaoId); - glDeleteVertexArrays(1, &quads.vaoId); + if (vaoSupported) glDeleteVertexArrays(1, &vertexData[i].vaoId); + + // Free vertex arrays memory from CPU (RAM) + RL_FREE(vertexData[i].vertices); + RL_FREE(vertexData[i].texcoords); + RL_FREE(vertexData[i].colors); + RL_FREE(vertexData[i].indices); } - - // Free vertex arrays memory from CPU (RAM) - free(lines.vertices); - free(lines.colors); - - free(triangles.vertices); - free(triangles.colors); - - free(quads.vertices); - free(quads.texcoords); - free(quads.colors); - free(quads.indices); } // Renders a 1x1 XY quad in NDC @@ -4560,77 +4456,6 @@ static void GenDrawCube(void) } #if defined(SUPPORT_VR_SIMULATOR) -// Configure stereo rendering (including distortion shader) with HMD device parameters -// NOTE: It modifies the global variable: VrStereoConfig vrConfig -static void SetStereoConfig(VrDeviceInfo hmd) -{ - // 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 = (float)fabs(-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; - - TraceLog(LOG_DEBUG, "VR: 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 }; - - TraceLog(LOG_DEBUG, "VR: Distortion Shader: LeftLensCenter = { %f, %f }", leftLensCenter[0], leftLensCenter[1]); - TraceLog(LOG_DEBUG, "VR: Distortion Shader: RightLensCenter = { %f, %f }", rightLensCenter[0], rightLensCenter[1]); - TraceLog(LOG_DEBUG, "VR: Distortion Shader: Scale = { %f, %f }", scale[0], scale[1]); - TraceLog(LOG_DEBUG, "VR: Distortion Shader: ScaleIn = { %f, %f }", scaleIn[0], scaleIn[1]); - -#if defined(SUPPORT_DISTORTION_SHADER) - // Update distortion shader with lens and distortion-scale parameters - SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "leftLensCenter"), leftLensCenter, 2); - SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "rightLensCenter"), rightLensCenter, 2); - SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "leftScreenCenter"), leftScreenCenter, 2); - SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "rightScreenCenter"), rightScreenCenter, 2); - - SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "scale"), scale, 2); - SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "scaleIn"), scaleIn, 2); - SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "hmdWarpParam"), hmd.lensDistortionValues, 4); - SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "chromaAbParam"), hmd.chromaAbCorrection, 4); -#endif - - // Fovy is normally computed with: 2*atan2(hmd.vScreenSize, 2*hmd.eyeToScreenDistance) - // ...but with lens distortion it is increased (see Oculus SDK Documentation) - //float fovy = 2.0f*atan2(hmd.vScreenSize*0.5f*distortionScale, hmd.eyeToScreenDistance); // Really need distortionScale? - float fovy = 2.0f*(float)atan2(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, 0.01, 1000.0); - vrConfig.eyesProjection[0] = MatrixMultiply(proj, MatrixTranslate(projOffset, 0.0f, 0.0f)); - vrConfig.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. - vrConfig.eyesViewOffset[0] = MatrixTranslate(-hmd.interpupillaryDistance*0.5f, 0.075f, 0.045f); - vrConfig.eyesViewOffset[1] = MatrixTranslate(hmd.interpupillaryDistance*0.5f, 0.075f, 0.045f); - - // Compute eyes Viewports - vrConfig.eyesViewport[0] = (Rectangle){ 0.0f, 0.0f, (float)hmd.hResolution/2, (float)hmd.vResolution }; - vrConfig.eyesViewport[1] = (Rectangle){ hmd.hResolution/2.0f, 0.0f, (float)hmd.hResolution/2, (float) hmd.vResolution }; -} - // Set internal projection and modelview matrix depending on eyes tracking data static void SetStereoView(int eye, Matrix matProjection, Matrix matModelView) { @@ -4638,7 +4463,7 @@ static void SetStereoView(int eye, Matrix matProjection, Matrix matModelView) Matrix eyeModelView = matModelView; // Setup viewport and projection/modelview matrices using tracking data - rlViewport(eye*screenWidth/2, 0, screenWidth/2, screenHeight); + rlViewport(eye*framebufferWidth/2, 0, framebufferWidth/2, framebufferHeight); // Apply view offset to modelview matrix eyeModelView = MatrixMultiply(matModelView, vrConfig.eyesViewOffset[eye]); @@ -4649,9 +4474,9 @@ static void SetStereoView(int eye, Matrix matProjection, Matrix matModelView) SetMatrixModelview(eyeModelView); SetMatrixProjection(eyeProjection); } -#endif // defined(SUPPORT_VR_SIMULATOR) +#endif // SUPPORT_VR_SIMULATOR -#endif //defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 #if defined(GRAPHICS_API_OPENGL_11) // Mipmaps data is generated after image data @@ -4690,7 +4515,7 @@ static int GenerateMipmaps(unsigned char *data, int baseWidth, int baseHeight) // Generate mipmaps // NOTE: Every mipmap data is stored after data - Color *image = (Color *)malloc(width*height*sizeof(Color)); + Color *image = (Color *)RL_MALLOC(width*height*sizeof(Color)); Color *mipmap = NULL; int offset = 0; int j = 0; @@ -4727,13 +4552,13 @@ static int GenerateMipmaps(unsigned char *data, int baseWidth, int baseHeight) j++; } - free(image); + RL_FREE(image); image = mipmap; mipmap = NULL; } - free(mipmap); // free mipmap data + RL_FREE(mipmap); // free mipmap data return mipmapCount; } @@ -4747,7 +4572,7 @@ static Color *GenNextMipmap(Color *srcData, int srcWidth, int srcHeight) int width = srcWidth/2; int height = srcHeight/2; - Color *mipmap = (Color *)malloc(width*height*sizeof(Color)); + Color *mipmap = (Color *)RL_MALLOC(width*height*sizeof(Color)); // Scaling algorithm works perfectly (box-filter) for (int y = 0; y < height; y++) @@ -4840,8 +4665,16 @@ int GetPixelDataSize(int width, int height, int format) 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; + } + return dataSize; } -#endif +#endif // RLGL_STANDALONE -#endif // RLGL_IMPLEMENTATION \ No newline at end of file +#endif // RLGL_IMPLEMENTATION diff --git a/raylib/shaders.go b/raylib/shaders.go index 5e097e0..a4c09bb 100644 --- a/raylib/shaders.go +++ b/raylib/shaders.go @@ -87,20 +87,11 @@ func GetShaderLocation(shader Shader, uniformName string) int32 { func SetShaderValue(shader Shader, uniformLoc int32, value []float32, size int32) { cshader := shader.cptr() cuniformLoc := (C.int)(uniformLoc) - cvalue := (*C.float)(unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(&value)).Data)) + cvalue := unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(&value)).Data) csize := (C.int)(size) C.SetShaderValue(*cshader, cuniformLoc, cvalue, csize) } -// SetShaderValuei - Set shader uniform value (int) -func SetShaderValuei(shader Shader, uniformLoc int32, value []int32, size int32) { - cshader := shader.cptr() - cuniformLoc := (C.int)(uniformLoc) - cvalue := (*C.int)(unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(&value)).Data)) - csize := (C.int)(size) - C.SetShaderValuei(*cshader, cuniformLoc, cvalue, csize) -} - // SetShaderValueMatrix - Set shader uniform value (matrix 4x4) func SetShaderValueMatrix(shader Shader, uniformLoc int32, mat Matrix) { cshader := shader.cptr() @@ -155,12 +146,11 @@ func GenTexturePrefilter(shader Shader, cubemap Texture2D, size int) Texture2D { } // GenTextureBRDF - Generate BRDF texture using cubemap data -func GenTextureBRDF(shader Shader, cubemap Texture2D, size int) Texture2D { +func GenTextureBRDF(shader Shader, size int) Texture2D { cshader := shader.cptr() - ccubemap := cubemap.cptr() csize := (C.int)(size) - ret := C.GenTextureBRDF(*cshader, *ccubemap, csize) + ret := C.GenTextureBRDF(*cshader, csize) v := newTexture2DFromPointer(unsafe.Pointer(&ret)) return v } @@ -187,18 +177,9 @@ func EndBlendMode() { C.EndBlendMode() } -// GetVrDeviceInfo - Get VR device information for some standard devices -func GetVrDeviceInfo(vrDevice VrDevice) VrDeviceInfo { - cvrDevice := (C.int)(vrDevice) - ret := C.GetVrDeviceInfo(cvrDevice) - v := newVrDeviceInfoFromPointer(unsafe.Pointer(&ret)) - return v -} - // InitVrSimulator - Init VR simulator for selected device -func InitVrSimulator(vrDeviceInfo VrDeviceInfo) { - cvrDeviceInfo := vrDeviceInfo.cptr() - C.InitVrSimulator(*cvrDeviceInfo) +func InitVrSimulator() { + C.InitVrSimulator() } // CloseVrSimulator - Close VR simulator for current device diff --git a/raylib/shapes.c b/raylib/shapes.c index eb63d5c..5aef973 100644 --- a/raylib/shapes.c +++ b/raylib/shapes.c @@ -14,7 +14,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2018 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2019 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. @@ -33,9 +33,13 @@ * **********************************************************************************************/ -#include "config.h" // Defines module configuration flags #include "raylib.h" // Declares module functions +// Check if config flags have been externally provided on compilation line +#if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags +#endif + #include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 2.1, 3.3+ or ES2 #include // Required for: abs(), fabs() @@ -126,9 +130,9 @@ void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color) rlEnableTexture(GetShapesTexture().id); rlPushMatrix(); - rlTranslatef((float)startPos.x, (float)startPos.y, 0); - rlRotatef(RAD2DEG*angle, 0, 0, 1); - rlTranslatef(0, (thick > 1.0f) ? -thick/2.0f : -1.0f, 0); + 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); @@ -139,7 +143,7 @@ void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color) rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); rlVertex2f(0.0f, thick); - + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); rlVertex2f(d, thick); @@ -172,23 +176,199 @@ void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color) } } +// Draw lines sequence +void DrawLineStrip(Vector2 *points, int pointsCount, Color color) +{ + if (pointsCount >= 2) + { + if (rlCheckBufferLimit(pointsCount)) rlglDraw(); + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 0; i < pointsCount - 1; i++) + { + rlVertex2f(points[i].x, points[i].y); + rlVertex2f(points[i + 1].x, points[i + 1].y); + } + rlEnd(); + } +} + // Draw a color-filled circle void DrawCircle(int centerX, int centerY, float radius, Color color) { DrawCircleV((Vector2){ (float)centerX, (float)centerY }, radius, color); } +// Draw a piece of a circle +void DrawCircleSector(Vector2 center, float radius, int startAngle, int endAngle, int segments, Color color) +{ + if (radius <= 0.0f) radius = 0.1f; // Avoid div by zero + + // Function expects (endAngle > startAngle) + if (endAngle < startAngle) + { + // Swap values + int tmp = startAngle; + startAngle = endAngle; + endAngle = tmp; + } + + if (segments < 4) + { + // Calculate how many segments we need to draw a smooth circle, taken from https://stackoverflow.com/a/2244088 + #define CIRCLE_ERROR_RATE 0.5f + + // Calculate the maximum angle between segments based on the error rate. + float th = acosf(2*powf(1 - CIRCLE_ERROR_RATE/radius, 2) - 1); + segments = (endAngle - startAngle)*ceilf(2*PI/th)/360; + + if (segments <= 0) segments = 4; + } + + float stepLength = (float)(endAngle - startAngle)/(float)segments; + float angle = startAngle; + +#if defined(SUPPORT_QUADS_DRAW_MODE) + if (rlCheckBufferLimit(4*segments/2)) rlglDraw(); + + rlEnableTexture(GetShapesTexture().id); + + rlBegin(RL_QUADS); + // 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(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); + + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.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(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); + + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(center.x, center.y); + } + rlEnd(); + + rlDisableTexture(); +#else + if (rlCheckBufferLimit(3*segments)) rlglDraw(); + + rlBegin(RL_TRIANGLES); + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); + + angle += stepLength; + } + rlEnd(); +#endif +} + +void DrawCircleSectorLines(Vector2 center, float radius, int startAngle, int endAngle, int segments, Color color) +{ + if (radius <= 0.0f) radius = 0.1f; // Avoid div by zero issue + + // Function expects (endAngle > startAngle) + if (endAngle < startAngle) + { + // Swap values + int tmp = startAngle; + startAngle = endAngle; + endAngle = tmp; + } + + if (segments < 4) + { + // Calculate how many segments we need to draw a smooth circle, taken from https://stackoverflow.com/a/2244088 + #ifndef CIRCLE_ERROR_RATE + #define CIRCLE_ERROR_RATE 0.5f + #endif + + // Calculate the maximum angle between segments based on the error rate. + float th = acosf(2*powf(1 - CIRCLE_ERROR_RATE/radius, 2) - 1); + segments = (endAngle - startAngle)*ceilf(2*PI/th)/360; + + if (segments <= 0) segments = 4; + } + + float stepLength = (float)(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 (rlCheckBufferLimit(limit)) rlglDraw(); + + rlBegin(RL_LINES); + if (showCapLines) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + } + + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); + + angle += stepLength; + } + + if (showCapLines) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + } + rlEnd(); +} + // Draw a gradient-filled circle // NOTE: Gradient goes from center (color1) to border (color2) void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2) { - if (rlCheckBufferLimit(RL_TRIANGLES, 3*36)) rlglDraw(); + if (rlCheckBufferLimit(3*36)) rlglDraw(); rlBegin(RL_TRIANGLES); for (int i = 0; i < 360; i += 10) { rlColor4ub(color1.r, color1.g, color1.b, color1.a); - rlVertex2i(centerX, centerY); + rlVertex2f(centerX, centerY); rlColor4ub(color2.r, color2.g, color2.b, color2.a); rlVertex2f(centerX + sinf(DEG2RAD*i)*radius, centerY + cosf(DEG2RAD*i)*radius); rlColor4ub(color2.r, color2.g, color2.b, color2.a); @@ -201,51 +381,13 @@ void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Co // NOTE: On OpenGL 3.3 and ES2 we use QUADS to avoid drawing order issues (view rlglDraw) void DrawCircleV(Vector2 center, float radius, Color color) { -#if defined(SUPPORT_QUADS_DRAW_MODE) - if (rlCheckBufferLimit(RL_QUADS, 4*(36/2))) rlglDraw(); - - rlEnableTexture(GetShapesTexture().id); - - rlBegin(RL_QUADS); - for (int i = 0; i < 360; i += 20) - { - rlColor4ub(color.r, color.g, color.b, color.a); - - rlTexCoord2f(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); - rlVertex2f(center.x, center.y); - - rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); - rlVertex2f(center.x + sinf(DEG2RAD*i)*radius, center.y + cosf(DEG2RAD*i)*radius); - - rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); - rlVertex2f(center.x + sinf(DEG2RAD*(i + 10))*radius, center.y + cosf(DEG2RAD*(i + 10))*radius); - - rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); - rlVertex2f(center.x + sinf(DEG2RAD*(i + 20))*radius, center.y + cosf(DEG2RAD*(i + 20))*radius); - } - rlEnd(); - - rlDisableTexture(); -#else - if (rlCheckBufferLimit(RL_TRIANGLES, 3*(36/2))) rlglDraw(); - - rlBegin(RL_TRIANGLES); - for (int i = 0; i < 360; i += 10) - { - rlColor4ub(color.r, color.g, color.b, color.a); - - rlVertex2f(center.x, center.y); - rlVertex2f(center.x + sinf(DEG2RAD*i)*radius, center.y + cosf(DEG2RAD*i)*radius); - rlVertex2f(center.x + sinf(DEG2RAD*(i + 10))*radius, center.y + cosf(DEG2RAD*(i + 10))*radius); - } - rlEnd(); -#endif + DrawCircleSector(center, radius, 0, 360, 36, color); } // Draw circle outline void DrawCircleLines(int centerX, int centerY, float radius, Color color) { - if (rlCheckBufferLimit(RL_LINES, 2*36)) rlglDraw(); + if (rlCheckBufferLimit(2*36)) rlglDraw(); rlBegin(RL_LINES); rlColor4ub(color.r, color.g, color.b, color.a); @@ -259,6 +401,184 @@ void DrawCircleLines(int centerX, int centerY, float radius, Color color) rlEnd(); } +void DrawRing(Vector2 center, float innerRadius, float outerRadius, int startAngle, int endAngle, int segments, Color color) +{ + if (startAngle == endAngle) return; + + // Function expects (outerRadius > innerRadius) + if (outerRadius < innerRadius) + { + float tmp = outerRadius; + outerRadius = innerRadius; + innerRadius = tmp; + + if (outerRadius <= 0.0f) outerRadius = 0.1f; + } + + // Function expects (endAngle > startAngle) + if (endAngle < startAngle) + { + // Swap values + int tmp = startAngle; + startAngle = endAngle; + endAngle = tmp; + } + + if (segments < 4) + { + // Calculate how many segments we need to draw a smooth circle, taken from https://stackoverflow.com/a/2244088 + #ifndef CIRCLE_ERROR_RATE + #define CIRCLE_ERROR_RATE 0.5f + #endif + + // Calculate the maximum angle between segments based on the error rate. + float th = acosf(2*powf(1 - CIRCLE_ERROR_RATE/outerRadius, 2) - 1); + segments = (endAngle - startAngle)*ceilf(2*PI/th)/360; + + if (segments <= 0) segments = 4; + } + + // Not a ring + if (innerRadius <= 0.0f) + { + DrawCircleSector(center, outerRadius, startAngle, endAngle, segments, color); + return; + } + + float stepLength = (float)(endAngle - startAngle)/(float)segments; + float angle = startAngle; + +#if defined(SUPPORT_QUADS_DRAW_MODE) + if (rlCheckBufferLimit(4*segments)) rlglDraw(); + + rlEnableTexture(GetShapesTexture().id); + + rlBegin(RL_QUADS); + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlTexCoord2f(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); + + rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*outerRadius); + + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); + + angle += stepLength; + } + rlEnd(); + + rlDisableTexture(); +#else + if (rlCheckBufferLimit(6*segments)) rlglDraw(); + + rlBegin(RL_TRIANGLES); + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); + + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*outerRadius); + + angle += stepLength; + } + rlEnd(); +#endif +} + +void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, int startAngle, int endAngle, int segments, Color color) +{ + if (startAngle == endAngle) return; + + // Function expects (outerRadius > innerRadius) + if (outerRadius < innerRadius) + { + float tmp = outerRadius; + outerRadius = innerRadius; + innerRadius = tmp; + + if (outerRadius <= 0.0f) outerRadius = 0.1f; + } + + // Function expects (endAngle > startAngle) + if (endAngle < startAngle) + { + // Swap values + int tmp = startAngle; + startAngle = endAngle; + endAngle = tmp; + } + + if (segments < 4) + { + // Calculate how many segments we need to draw a smooth circle, taken from https://stackoverflow.com/a/2244088 + #ifndef CIRCLE_ERROR_RATE + #define CIRCLE_ERROR_RATE 0.5f + #endif + + // Calculate the maximum angle between segments based on the error rate. + float th = acosf(2*powf(1 - CIRCLE_ERROR_RATE/outerRadius, 2) - 1); + segments = (endAngle - startAngle)*ceilf(2*PI/th)/360; + + if (segments <= 0) segments = 4; + } + + if (innerRadius <= 0.0f) + { + DrawCircleSectorLines(center, outerRadius, startAngle, endAngle, segments, color); + return; + } + + float stepLength = (float)(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 (rlCheckBufferLimit(limit)) rlglDraw(); + + rlBegin(RL_LINES); + if (showCapLines) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); + } + + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*outerRadius); + + rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); + + angle += stepLength; + } + + if (showCapLines) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); + } + rlEnd(); +} + // Draw a color-filled rectangle void DrawRectangle(int posX, int posY, int width, int height, Color color) { @@ -269,48 +589,40 @@ void DrawRectangle(int posX, int posY, int width, int height, Color color) // NOTE: On OpenGL 3.3 and ES2 we use QUADS to avoid drawing order issues (view rlglDraw) void DrawRectangleV(Vector2 position, Vector2 size, Color color) { - Color colors[4] = { color, color, color, color }; - - DrawRectanglePro((Rectangle){ position.x, position.y, size.x, size.y }, (Vector2){ 0.0f, 0.0f }, 0.0f, colors); + DrawRectanglePro((Rectangle){ position.x, position.y, size.x, size.y }, (Vector2){ 0.0f, 0.0f }, 0.0f, color); } // Draw a color-filled rectangle void DrawRectangleRec(Rectangle rec, Color color) { - Color colors[4] = { color, color, color, color }; - - DrawRectanglePro(rec, (Vector2){ 0.0f, 0.0f }, 0.0f, colors); + DrawRectanglePro(rec, (Vector2){ 0.0f, 0.0f }, 0.0f, color); } // Draw a color-filled rectangle with pro parameters -void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color colors[4]) +void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color) { rlEnableTexture(GetShapesTexture().id); rlPushMatrix(); - //rlTranslatef(rec.x, rec.y, 0); // Already considered on vertex position - rlRotatef(rotation, 0, 0, 1); - rlTranslatef(-origin.x, -origin.y, 0); + rlTranslatef(rec.x, rec.y, 0.0f); + rlRotatef(rotation, 0.0f, 0.0f, 1.0f); + rlTranslatef(-origin.x, -origin.y, 0.0f); rlBegin(RL_QUADS); rlNormal3f(0.0f, 0.0f, 1.0f); + rlColor4ub(color.r, color.g, color.b, color.a); - // NOTE: Default raylib font character 95 is a white square - rlColor4ub(colors[0].r, colors[0].g, colors[0].b, colors[0].a); rlTexCoord2f(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); - rlVertex2f(rec.x, rec.y); + rlVertex2f(0.0f, 0.0f); - rlColor4ub(colors[1].r, colors[1].g, colors[1].b, colors[1].a); rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); - rlVertex2f(rec.x, rec.y + rec.height); + rlVertex2f(0.0f, rec.height); - rlColor4ub(colors[2].r, colors[2].g, colors[2].b, colors[2].a); rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); - rlVertex2f(rec.x + rec.width, rec.y + rec.height); + rlVertex2f(rec.width, rec.height); - rlColor4ub(colors[3].r, colors[3].g, colors[3].b, colors[3].a); rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); - rlVertex2f(rec.x + rec.width, rec.y); + rlVertex2f(rec.width, 0.0f); rlEnd(); rlPopMatrix(); @@ -335,9 +647,32 @@ 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) { - Color colors[4] = { col1, col2, col3, col4 }; + rlEnableTexture(GetShapesTexture().id); - DrawRectanglePro(rec, (Vector2){ 0.0f, 0.0f }, 0.0f, colors); + rlPushMatrix(); + rlBegin(RL_QUADS); + rlNormal3f(0.0f, 0.0f, 1.0f); + + // NOTE: Default raylib font character 95 is a white square + rlColor4ub(col1.r, col1.g, col1.b, col1.a); + rlTexCoord2f(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(rec.x, rec.y); + + rlColor4ub(col2.r, col2.g, col2.b, col2.a); + rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(rec.x, rec.y + rec.height); + + rlColor4ub(col3.r, col3.g, col3.b, col3.a); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(rec.x + rec.width, rec.y + rec.height); + + rlColor4ub(col4.r, col4.g, col4.b, col4.a); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(rec.x + rec.width, rec.y); + rlEnd(); + rlPopMatrix(); + + rlDisableTexture(); } // Draw rectangle outline @@ -372,7 +707,7 @@ void DrawRectangleLinesEx(Rectangle rec, int lineThick, Color color) { if (lineThick > rec.width || lineThick > rec.height) { - if(rec.width > rec.height) lineThick = (int)rec.height/2; + if (rec.width > rec.height) lineThick = (int)rec.height/2; else if (rec.width < rec.height) lineThick = (int)rec.width/2; } @@ -382,9 +717,473 @@ void DrawRectangleLinesEx(Rectangle rec, int lineThick, Color color) DrawRectangle( (int)rec.x, (int)(rec.y + lineThick), lineThick, (int)(rec.height - lineThick*2), color); } +// Draw rectangle with rounded edges +void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color) +{ + // Not a rounded rectangle + if ((roundness <= 0.0f) || (rec.width < 1) || (rec.height < 1 )) + { + DrawRectangleRec(rec, color); + return; + } + + if (roundness >= 1.0f) roundness = 1.0f; + + // Calculate corner radius + float radius = (rec.width > rec.height)? (rec.height*roundness)/2 : (rec.width*roundness)/2; + if (radius <= 0.0f) return; + + // Calculate number of segments to use for the corners + if (segments < 4) + { + // Calculate how many segments we need to draw a smooth circle, taken from https://stackoverflow.com/a/2244088 + #ifndef CIRCLE_ERROR_RATE + #define CIRCLE_ERROR_RATE 0.5f + #endif + // Calculate the maximum angle between segments based on the error rate. + float th = acosf(2*powf(1 - CIRCLE_ERROR_RATE/radius, 2) - 1); + segments = ceilf(2*PI/th)/4; + if (segments <= 0) segments = 4; + } + + 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 + */ + + 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 + {(float)(rec.x + rec.width) - radius, (float)(rec.y + rec.height) - radius}, {(float)rec.x + radius, (float)(rec.y + rec.height) - radius} // P10, P11 + }; + + const Vector2 centers[4] = { point[8], point[9], point[10], point[11] }; + 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(); + + rlEnableTexture(GetShapesTexture().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 + for (int k = 0; k < 4; ++k) // Hope the compiler is smart enough to unroll this loop + { + 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(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(center.x, center.y); + rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.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(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(center.x, center.y); + rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(center.x, center.y); + } + } + + // [2] Upper Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(point[0].x, point[0].y); + rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(point[8].x, point[8].y); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(point[9].x, point[9].y); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(point[1].x, point[1].y); + + // [4] Right Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(point[2].x, point[2].y); + rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(point[9].x, point[9].y); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(point[10].x, point[10].y); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(point[3].x, point[3].y); + + // [6] Bottom Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(point[11].x, point[11].y); + rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(point[5].x, point[5].y); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(point[4].x, point[4].y); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(point[10].x, point[10].y); + + // [8] Left Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(point[7].x, point[7].y); + rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(point[6].x, point[6].y); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(point[11].x, point[11].y); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(point[8].x, point[8].y); + + // [9] Middle Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(point[8].x, point[8].y); + rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(point[11].x, point[11].y); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(point[10].x, point[10].y); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(point[9].x, point[9].y); + + rlEnd(); + rlDisableTexture(); +#else + if (rlCheckBufferLimit(12*segments + 5*6)) rlglDraw(); // 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 + { + float angle = angles[k]; + const Vector2 center = centers[k]; + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); + angle += stepLength; + } + } + + // [2] Upper Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[0].x, point[0].y); + rlVertex2f(point[8].x, point[8].y); + rlVertex2f(point[9].x, point[9].y); + rlVertex2f(point[1].x, point[1].y); + rlVertex2f(point[0].x, point[0].y); + rlVertex2f(point[9].x, point[9].y); + + // [4] Right Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[9].x, point[9].y); + rlVertex2f(point[10].x, point[10].y); + rlVertex2f(point[3].x, point[3].y); + rlVertex2f(point[2].x, point[2].y); + rlVertex2f(point[9].x, point[9].y); + rlVertex2f(point[3].x, point[3].y); + + // [6] Bottom Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[11].x, point[11].y); + rlVertex2f(point[5].x, point[5].y); + rlVertex2f(point[4].x, point[4].y); + rlVertex2f(point[10].x, point[10].y); + rlVertex2f(point[11].x, point[11].y); + rlVertex2f(point[4].x, point[4].y); + + // [8] Left Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[7].x, point[7].y); + rlVertex2f(point[6].x, point[6].y); + rlVertex2f(point[11].x, point[11].y); + rlVertex2f(point[8].x, point[8].y); + rlVertex2f(point[7].x, point[7].y); + rlVertex2f(point[11].x, point[11].y); + + // [9] Middle Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[8].x, point[8].y); + rlVertex2f(point[11].x, point[11].y); + rlVertex2f(point[10].x, point[10].y); + rlVertex2f(point[9].x, point[9].y); + rlVertex2f(point[8].x, point[8].y); + rlVertex2f(point[10].x, point[10].y); + rlEnd(); +#endif +} + +// Draw rectangle with rounded edges outline +void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, int lineThick, Color color) +{ + if (lineThick < 0) lineThick = 0; + + // Not a rounded rectangle + if (roundness <= 0.0f) + { + DrawRectangleLinesEx((Rectangle){rec.x-lineThick, rec.y-lineThick, rec.width+2*lineThick, rec.height+2*lineThick}, lineThick, color); + return; + } + + if (roundness >= 1.0f) roundness = 1.0f; + + // Calculate corner radius + float radius = (rec.width > rec.height)? (rec.height*roundness)/2 : (rec.width*roundness)/2; + if (radius <= 0.0f) return; + + // Calculate number of segments to use for the corners + if (segments < 4) + { + // Calculate how many segments we need to draw a smooth circle, taken from https://stackoverflow.com/a/2244088 + #ifndef CIRCLE_ERROR_RATE + #define CIRCLE_ERROR_RATE 0.5f + #endif + // Calculate the maximum angle between segments based on the error rate. + float th = acosf(2*powf(1 - CIRCLE_ERROR_RATE/radius, 2) - 1); + segments = ceilf(2*PI/th)/2; + if (segments <= 0) segments = 4; + } + + float stepLength = 90.0f/(float)segments; + const float outerRadius = radius + (float)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 + */ + 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 + {(float)rec.x + innerRadius, rec.y + rec.height + lineThick}, { rec.x - lineThick, (float)(rec.y + rec.height) - innerRadius}, {rec.x - lineThick, (float)rec.y + innerRadius}, // P5, P6, P7 + {(float)rec.x + innerRadius, rec.y}, {(float)(rec.x + rec.width) - innerRadius, rec.y}, // P8, P9 + { rec.x + rec.width, (float)rec.y + innerRadius }, {rec.x + rec.width, (float)(rec.y + rec.height) - innerRadius}, // P10, P11 + {(float)(rec.x + rec.width) - innerRadius, rec.y + rec.height}, {(float)rec.x + innerRadius, rec.y + rec.height}, // P12, P13 + { rec.x, (float)(rec.y + rec.height) - innerRadius}, {rec.x, (float)rec.y + innerRadius} // P14, P15 + }; + + const Vector2 centers[4] = { + {(float)rec.x + innerRadius, (float)rec.y + innerRadius}, {(float)(rec.x + rec.width) - innerRadius, (float)rec.y + innerRadius}, // P16, P17 + {(float)(rec.x + rec.width) - innerRadius, (float)(rec.y + rec.height) - innerRadius}, {(float)rec.x + innerRadius, (float)(rec.y + rec.height) - innerRadius} // P18, P19 + }; + + const float angles[4] = { 180.0f, 90.0f, 0.0f, 270.0f }; + + 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 + + rlEnableTexture(GetShapesTexture().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 + { + float angle = angles[k]; + const Vector2 center = centers[k]; + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); + rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*outerRadius); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); + + angle += stepLength; + } + } + + // Upper rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(point[0].x, point[0].y); + rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(point[8].x, point[8].y); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(point[9].x, point[9].y); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(point[1].x, point[1].y); + + // Right rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(point[2].x, point[2].y); + rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(point[10].x, point[10].y); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(point[11].x, point[11].y); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(point[3].x, point[3].y); + + // Lower rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(point[13].x, point[13].y); + rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(point[5].x, point[5].y); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(point[4].x, point[4].y); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(point[12].x, point[12].y); + + // Left rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(point[15].x, point[15].y); + rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(point[7].x, point[7].y); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(point[6].x, point[6].y); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(point[14].x, point[14].y); + + rlEnd(); + rlDisableTexture(); +#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 + + rlBegin(RL_TRIANGLES); + + // 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 + { + float angle = angles[k]; + const Vector2 center = centers[k]; + + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); + + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*outerRadius); + + angle += stepLength; + } + } + + // Upper rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[0].x, point[0].y); + rlVertex2f(point[8].x, point[8].y); + rlVertex2f(point[9].x, point[9].y); + rlVertex2f(point[1].x, point[1].y); + rlVertex2f(point[0].x, point[0].y); + rlVertex2f(point[9].x, point[9].y); + + // Right rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[10].x, point[10].y); + rlVertex2f(point[11].x, point[11].y); + rlVertex2f(point[3].x, point[3].y); + rlVertex2f(point[2].x, point[2].y); + rlVertex2f(point[10].x, point[10].y); + rlVertex2f(point[3].x, point[3].y); + + // Lower rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[13].x, point[13].y); + rlVertex2f(point[5].x, point[5].y); + rlVertex2f(point[4].x, point[4].y); + rlVertex2f(point[12].x, point[12].y); + rlVertex2f(point[13].x, point[13].y); + rlVertex2f(point[4].x, point[4].y); + + // Left rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[7].x, point[7].y); + rlVertex2f(point[6].x, point[6].y); + rlVertex2f(point[14].x, point[14].y); + rlVertex2f(point[15].x, point[15].y); + rlVertex2f(point[7].x, point[7].y); + rlVertex2f(point[14].x, point[14].y); + rlEnd(); +#endif + } + 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 + + rlBegin(RL_LINES); + + // 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 + { + float angle = angles[k]; + const Vector2 center = centers[k]; + + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*outerRadius); + angle += stepLength; + } + } + // And now the remaining 4 lines + 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(); + } +} + // Draw a triangle +// NOTE: Vertex must be provided in counter-clockwise order void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color) { + if (rlCheckBufferLimit(4)) rlglDraw(); + #if defined(SUPPORT_QUADS_DRAW_MODE) rlEnableTexture(GetShapesTexture().id); @@ -416,8 +1215,11 @@ void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color) } // Draw a triangle using lines +// NOTE: Vertex must be provided in counter-clockwise order void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color) { + if (rlCheckBufferLimit(6)) rlglDraw(); + rlBegin(RL_LINES); rlColor4ub(color.r, color.g, color.b, color.a); rlVertex2f(v1.x, v1.y); @@ -431,22 +1233,84 @@ void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color) rlEnd(); } +// Draw a triangle fan defined by points +// NOTE: First vertex provided is the center, shared by all triangles +void DrawTriangleFan(Vector2 *points, int pointsCount, Color color) +{ + if (pointsCount >= 3) + { + if (rlCheckBufferLimit((pointsCount - 2)*4)) rlglDraw(); + + rlEnableTexture(GetShapesTexture().id); + rlBegin(RL_QUADS); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 1; i < pointsCount - 1; i++) + { + rlTexCoord2f(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(points[0].x, points[0].y); + + rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(points[i].x, points[i].y); + + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(points[i + 1].x, points[i + 1].y); + + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(points[i + 1].x, points[i + 1].y); + } + rlEnd(); + rlDisableTexture(); + } +} + +// Draw a triangle strip defined by points +// NOTE: Every new vertex connects with previous two +void DrawTriangleStrip(Vector2 *points, int pointsCount, Color color) +{ + if (pointsCount >= 3) + { + if (rlCheckBufferLimit(pointsCount)) rlglDraw(); + + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 2; i < pointsCount; i++) + { + if ((i%2) == 0) + { + rlVertex2f(points[i].x, points[i].y); + rlVertex2f(points[i - 2].x, points[i - 2].y); + rlVertex2f(points[i - 1].x, points[i - 1].y); + } + else + { + rlVertex2f(points[i].x, points[i].y); + rlVertex2f(points[i - 1].x, points[i - 1].y); + rlVertex2f(points[i - 2].x, points[i - 2].y); + } + } + rlEnd(); + } +} + // Draw a regular polygon of n sides (Vector version) void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color color) { if (sides < 3) sides = 3; + float centralAngle = 0.0f; - if (rlCheckBufferLimit(RL_QUADS, 4*(360/sides))) rlglDraw(); + if (rlCheckBufferLimit(4*(360/sides))) rlglDraw(); rlPushMatrix(); - rlTranslatef(center.x, center.y, 0.0); - rlRotatef(rotation, 0, 0, 1); + rlTranslatef(center.x, center.y, 0.0f); + rlRotatef(rotation, 0.0f, 0.0f, 1.0f); - #if defined(SUPPORT_QUADS_DRAW_MODE) +#if defined(SUPPORT_QUADS_DRAW_MODE) rlEnableTexture(GetShapesTexture().id); rlBegin(RL_QUADS); - for (int i = 0; i < 360; i += 360/sides) + for (int i = 0; i < sides; i++) { rlColor4ub(color.r, color.g, color.b, color.a); @@ -454,87 +1318,34 @@ void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color col rlVertex2f(0, 0); rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); - rlVertex2f(sinf(DEG2RAD*i)*radius, cosf(DEG2RAD*i)*radius); + rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); - rlVertex2f(sinf(DEG2RAD*i)*radius, cosf(DEG2RAD*i)*radius); + rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); + centralAngle += 360.0f/(float)sides; rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); - rlVertex2f(sinf(DEG2RAD*(i + 360/sides))*radius, cosf(DEG2RAD*(i + 360/sides))*radius); + rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); } rlEnd(); rlDisableTexture(); - #else +#else rlBegin(RL_TRIANGLES); - for (int i = 0; i < 360; i += 360/sides) + for (int i = 0; i < sides; i++) { rlColor4ub(color.r, color.g, color.b, color.a); rlVertex2f(0, 0); - rlVertex2f(sinf(DEG2RAD*i)*radius, cosf(DEG2RAD*i)*radius); - rlVertex2f(sinf(DEG2RAD*(i + 360/sides))*radius, cosf(DEG2RAD*(i + 360/sides))*radius); + rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); + + centralAngle += 360.0f/(float)sides; + rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); } rlEnd(); - #endif +#endif rlPopMatrix(); } -// Draw a closed polygon defined by points -void DrawPolyEx(Vector2 *points, int pointsCount, Color color) -{ - if (pointsCount >= 3) - { - if (rlCheckBufferLimit(RL_QUADS, pointsCount)) rlglDraw(); - - #if defined(SUPPORT_QUADS_DRAW_MODE) - rlEnableTexture(GetShapesTexture().id); - - rlBegin(RL_QUADS); - rlColor4ub(color.r, color.g, color.b, color.a); - - for (int i = 1; i < pointsCount - 1; i++) - { - rlVertex2f(points[0].x, points[0].y); - rlVertex2f(points[i].x, points[i].y); - rlVertex2f(points[i].x, points[i].y); - rlVertex2f(points[i + 1].x, points[i + 1].y); - } - rlEnd(); - rlDisableTexture(); - #else - rlBegin(RL_TRIANGLES); - rlColor4ub(color.r, color.g, color.b, color.a); - - for (int i = 1; i < pointsCount - 1; i++) - { - rlVertex2f(points[0].x, points[0].y); - rlVertex2f(points[i].x, points[i].y); - rlVertex2f(points[i + 1].x, points[i + 1].y); - } - rlEnd(); - #endif - } -} - -// Draw polygon using lines -void DrawPolyExLines(Vector2 *points, int pointsCount, Color color) -{ - if (pointsCount >= 2) - { - if (rlCheckBufferLimit(RL_LINES, pointsCount)) rlglDraw(); - - rlBegin(RL_LINES); - rlColor4ub(color.r, color.g, color.b, color.a); - - for (int i = 0; i < pointsCount - 1; i++) - { - rlVertex2f(points[i].x, points[i].y); - rlVertex2f(points[i + 1].x, points[i + 1].y); - } - rlEnd(); - } -} - // Define default texture used to draw shapes void SetShapesTexture(Texture2D texture, Rectangle source) { @@ -585,8 +1396,8 @@ bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2) { bool collision = false; - if ((rec1.x <= (rec2.x + rec2.width) && (rec1.x + rec1.width) >= rec2.x) && - (rec1.y <= (rec2.y + rec2.height) && (rec1.y + rec1.height) >= rec2.y)) collision = true; + if ((rec1.x < (rec2.x + rec2.width) && (rec1.x + rec1.width) > rec2.x) && + (rec1.y < (rec2.y + rec2.height) && (rec1.y + rec1.height) > rec2.y)) collision = true; return collision; } @@ -623,7 +1434,7 @@ bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec) if (dy <= (rec.height/2.0f)) { return true; } float cornerDistanceSq = (dx - rec.width/2.0f)*(dx - rec.width/2.0f) + - (dy - rec.height/2.0f)*(dy - rec.height/2.0f); + (dy - rec.height/2.0f)*(dy - rec.height/2.0f); return (cornerDistanceSq <= (radius*radius)); } @@ -703,23 +1514,26 @@ Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2) // NOTE: Required for DrawLineBezier() static float EaseCubicInOut(float t, float b, float c, float d) { - if ((t /= 0.5f*d) < 1) - return 0.5f*c*t*t*t + b; + if ((t /= 0.5f*d) < 1) return 0.5f*c*t*t*t + b; + t -= 2; + return 0.5f*c*(t*t*t + 2.0f) + b; } // Get texture to draw shapes (RAII) static Texture2D GetShapesTexture(void) { - if (texShapes.id <= 0) + if (texShapes.id == 0) { #if defined(SUPPORT_FONT_TEXTURE) texShapes = GetFontDefault().texture; // Use font texture white character - recTexShapes = GetFontDefault().chars[95].rec; + Rectangle rec = GetFontDefault().recs[95]; + // NOTE: We setup a 1px padding on char rectangle to avoid texture bleeding on MSAA filtering + recTexShapes = (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }; #else texShapes = GetTextureDefault(); // Use default white texture - recTexShapes = { 0.0f, 0.0f, 1.0f, 1.0f }; + recTexShapes = (Rectangle){ 0.0f, 0.0f, 1.0f, 1.0f }; #endif } diff --git a/raylib/shapes.go b/raylib/shapes.go index 3b74e68..facb3e8 100644 --- a/raylib/shapes.go +++ b/raylib/shapes.go @@ -117,7 +117,7 @@ func DrawRectanglePro(rec Rectangle, origin Vector2, rotation float32, colors [] corigin := origin.cptr() crotation := (C.float)(rotation) ccolor := (*C.Color)(unsafe.Pointer(&colors[0])) - C.DrawRectanglePro(*crec, *corigin, crotation, ccolor) + C.DrawRectanglePro(*crec, *corigin, crotation, *ccolor) } // DrawRectangleGradientV - Draw a vertical-gradient-filled rectangle @@ -206,22 +206,6 @@ func DrawPoly(center Vector2, sides int32, radius, rotation float32, color Color C.DrawPoly(*ccenter, csides, cradius, crotation, *ccolor) } -// DrawPolyEx - Draw a closed polygon defined by points -func DrawPolyEx(points []Vector2, numPoints int32, color Color) { - cpoints := points[0].cptr() - cnumPoints := (C.int)(numPoints) - ccolor := color.cptr() - C.DrawPolyEx(cpoints, cnumPoints, *ccolor) -} - -// DrawPolyExLines - Draw polygon lines -func DrawPolyExLines(points []Vector2, numPoints int32, color Color) { - cpoints := points[0].cptr() - cnumPoints := (C.int)(numPoints) - ccolor := color.cptr() - C.DrawPolyExLines(cpoints, cnumPoints, *ccolor) -} - // CheckCollisionRecs - Check collision between two rectangles func CheckCollisionRecs(rec1, rec2 Rectangle) bool { crec1 := rec1.cptr() diff --git a/raylib/text.c b/raylib/text.c index 07a2596..d8756b9 100644 --- a/raylib/text.c +++ b/raylib/text.c @@ -10,14 +10,24 @@ * supported by default, to remove support, just comment unrequired #define in this module * * #define SUPPORT_DEFAULT_FONT +* Load default raylib font on initialization to be used by DrawText() and MeasureText(). +* If no default font loaded, DrawTextEx() and MeasureTextEx() are required. +* +* #define TEXTSPLIT_MAX_TEXT_BUFFER_LENGTH +* TextSplit() function static buffer max size +* +* #define TEXTSPLIT_MAX_SUBSTRINGS_COUNT +* TextSplit() function static substrings pointers array (pointing to static buffer) +* * * DEPENDENCIES: -* stb_truetype - Load TTF file and rasterize characters data +* stb_truetype - Load TTF file and rasterize characters data +* stb_rect_pack - Rectangles packing algorythms, required for font atlas generation * * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2018 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2019 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. @@ -36,13 +46,18 @@ * **********************************************************************************************/ -#include "config.h" // Defines module configuration flags #include "raylib.h" // Declares module functions +// Check if config flags have been externally provided on compilation line +#if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags +#endif + #include // Required for: malloc(), free() #include // Required for: strlen() #include // Required for: va_list, va_start(), vfprintf(), va_end() #include // Required for: FILE, fopen(), fclose(), fscanf(), feof(), rewind(), fgets() +#include // Required for: toupper(), tolower() #include "utils.h" // Required for: fopen() Android mapping @@ -58,8 +73,18 @@ //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -#define MAX_FORMATTEXT_LENGTH 256 -#define MAX_SUBTEXT_LENGTH 256 +#define MAX_TEXT_BUFFER_LENGTH 1024 // Size of internal static buffers used on some functions: + // TextFormat(), TextSubtext(), TextToUpper(), TextToLower(), TextToPascal() + +#define MAX_TEXT_UNICODE_CHARS 512 // Maximum number of unicode codepoints + +#if !defined(TEXTSPLIT_MAX_TEXT_BUFFER_LENGTH) + #define TEXTSPLIT_MAX_TEXT_BUFFER_LENGTH 1024 // Size of static buffer: TextSplit() +#endif + +#if !defined(TEXTSPLIT_MAX_SUBSTRINGS_COUNT) + #define TEXTSPLIT_MAX_SUBSTRINGS_COUNT 128 // Size of static pointers array: TextSplit() +#endif //---------------------------------------------------------------------------------- // Types and Structures Definition @@ -70,7 +95,7 @@ // Global variables //---------------------------------------------------------------------------------- #if defined(SUPPORT_DEFAULT_FONT) -static Font defaultFont; // Default font provided by raylib +static Font defaultFont = { 0 }; // Default font provided by raylib // NOTE: defaultFont is loaded on InitWindow and disposed on CloseWindow [module: core] #endif @@ -82,14 +107,13 @@ static Font defaultFont; // Default font provided by raylib //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- -static Font LoadImageFont(Image image, Color key, int firstChar); // Load a Image font file (XNA style) #if defined(SUPPORT_FILEFORMAT_FNT) static Font LoadBMFont(const char *fileName); // Load a BMFont file (AngelCode font file) #endif #if defined(SUPPORT_DEFAULT_FONT) -extern void LoadDefaultFont(void); -extern void UnloadDefaultFont(void); +extern void LoadFontDefault(void); +extern void UnloadFontDefault(void); #endif //---------------------------------------------------------------------------------- @@ -98,7 +122,7 @@ extern void UnloadDefaultFont(void); #if defined(SUPPORT_DEFAULT_FONT) // Load raylib default font -extern void LoadDefaultFont(void) +extern void LoadFontDefault(void) { #define BIT_CHECK(a,b) ((a) & (1u << (b))) @@ -110,7 +134,7 @@ extern void LoadDefaultFont(void) // Default font is directly defined here (data generated from a sprite font image) // This way, we reconstruct Font without creating large global variables // This data is automatically allocated to Stack and automatically deallocated at the end of this function - int defaultFontData[512] = { + unsigned int defaultFontData[512] = { 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00200020, 0x0001b000, 0x00000000, 0x00000000, 0x8ef92520, 0x00020a00, 0x7dbe8000, 0x1f7df45f, 0x4a2bf2a0, 0x0852091e, 0x41224000, 0x10041450, 0x2e292020, 0x08220812, 0x41222000, 0x10041450, 0x10f92020, 0x3efa084c, 0x7d22103c, 0x107df7de, 0xe8a12020, 0x08220832, 0x05220800, 0x10450410, 0xa4a3f000, 0x08520832, 0x05220400, 0x10450410, 0xe2f92020, 0x0002085e, 0x7d3e0281, 0x107df41f, @@ -171,7 +195,7 @@ extern void LoadDefaultFont(void) int imWidth = 128; int imHeight = 128; - Color *imagePixels = (Color *)malloc(imWidth*imHeight*sizeof(Color)); + Color *imagePixels = (Color *)RL_MALLOC(imWidth*imHeight*sizeof(Color)); for (int i = 0; i < imWidth*imHeight; i++) imagePixels[i] = BLANK; // Initialize array @@ -190,20 +214,20 @@ extern void LoadDefaultFont(void) if (counter > 512) counter = 0; // Security check... } - Image image = LoadImageEx(imagePixels, imWidth, imHeight); - ImageFormat(&image, UNCOMPRESSED_GRAY_ALPHA); + Image imFont = LoadImageEx(imagePixels, imWidth, imHeight); + ImageFormat(&imFont, UNCOMPRESSED_GRAY_ALPHA); - free(imagePixels); + RL_FREE(imagePixels); - defaultFont.texture = LoadTextureFromImage(image); - UnloadImage(image); + defaultFont.texture = LoadTextureFromImage(imFont); // Reconstruct charSet using charsWidth[], charsHeight, charsDivisor, charsCount //------------------------------------------------------------------------------ // Allocate space for our characters info data // NOTE: This memory should be freed at end! --> CloseWindow() - defaultFont.chars = (CharInfo *)malloc(defaultFont.charsCount*sizeof(CharInfo)); + defaultFont.chars = (CharInfo *)RL_MALLOC(defaultFont.charsCount*sizeof(CharInfo)); + defaultFont.recs = (Rectangle *)RL_MALLOC(defaultFont.charsCount*sizeof(Rectangle)); int currentLine = 0; int currentPosX = charsDivisor; @@ -213,12 +237,12 @@ extern void LoadDefaultFont(void) { defaultFont.chars[i].value = 32 + i; // First char is 32 - defaultFont.chars[i].rec.x = (float)currentPosX; - defaultFont.chars[i].rec.y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor)); - defaultFont.chars[i].rec.width = (float)charsWidth[i]; - defaultFont.chars[i].rec.height = (float)charsHeight; + defaultFont.recs[i].x = (float)currentPosX; + defaultFont.recs[i].y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor)); + defaultFont.recs[i].width = (float)charsWidth[i]; + defaultFont.recs[i].height = (float)charsHeight; - testPosX += (int)(defaultFont.chars[i].rec.width + (float)charsDivisor); + testPosX += (int)(defaultFont.recs[i].width + (float)charsDivisor); if (testPosX >= defaultFont.texture.width) { @@ -226,8 +250,8 @@ extern void LoadDefaultFont(void) currentPosX = 2*charsDivisor + charsWidth[i]; testPosX = currentPosX; - defaultFont.chars[i].rec.x = (float)charsDivisor; - defaultFont.chars[i].rec.y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor)); + defaultFont.recs[i].x = (float)charsDivisor; + defaultFont.recs[i].y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor)); } else currentPosX = testPosX; @@ -235,18 +259,25 @@ extern void LoadDefaultFont(void) defaultFont.chars[i].offsetX = 0; defaultFont.chars[i].offsetY = 0; defaultFont.chars[i].advanceX = 0; + + // Fill character image data from fontClear data + defaultFont.chars[i].image = ImageFromImage(imFont, defaultFont.recs[i]); } - defaultFont.baseSize = (int)defaultFont.chars[0].rec.height; + UnloadImage(imFont); + + defaultFont.baseSize = (int)defaultFont.recs[0].height; TraceLog(LOG_INFO, "[TEX ID %i] Default font loaded successfully", defaultFont.texture.id); } // Unload raylib default font -extern void UnloadDefaultFont(void) +extern void UnloadFontDefault(void) { + for (int i = 0; i < defaultFont.charsCount; i++) UnloadImage(defaultFont.chars[i].image); UnloadTexture(defaultFont.texture); - free(defaultFont.chars); + RL_FREE(defaultFont.chars); + RL_FREE(defaultFont.recs); } #endif // SUPPORT_DEFAULT_FONT @@ -272,7 +303,7 @@ Font LoadFont(const char *fileName) Font font = { 0 }; #if defined(SUPPORT_FILEFORMAT_TTF) - if (IsFileExtension(fileName, ".ttf")) font = LoadFontEx(fileName, DEFAULT_TTF_FONTSIZE, DEFAULT_TTF_NUMCHARS, NULL); + if (IsFileExtension(fileName, ".ttf;.otf")) font = LoadFontEx(fileName, DEFAULT_TTF_FONTSIZE, NULL, DEFAULT_TTF_NUMCHARS); else #endif #if defined(SUPPORT_FILEFORMAT_FNT) @@ -281,7 +312,7 @@ Font LoadFont(const char *fileName) #endif { Image image = LoadImage(fileName); - if (image.data != NULL) font = LoadImageFont(image, MAGENTA, DEFAULT_FIRST_CHAR); + if (image.data != NULL) font = LoadFontFromImage(image, MAGENTA, DEFAULT_FIRST_CHAR); UnloadImage(image); } @@ -298,543 +329,40 @@ Font LoadFont(const char *fileName) // Load Font from TTF font file with generation parameters // NOTE: You can pass an array with desired characters, those characters should be available in the font // if array is NULL, default char set is selected 32..126 -Font LoadFontEx(const char *fileName, int fontSize, int charsCount, int *fontChars) +Font LoadFontEx(const char *fileName, int fontSize, int *fontChars, int charsCount) { Font font = { 0 }; font.baseSize = fontSize; - font.charsCount = (charsCount > 0) ? charsCount : 95; + font.charsCount = (charsCount > 0)? charsCount : 95; font.chars = LoadFontData(fileName, font.baseSize, fontChars, font.charsCount, FONT_DEFAULT); +#if defined(SUPPORT_FILEFORMAT_TTF) if (font.chars != NULL) { - Image atlas = GenImageFontAtlas(font.chars, font.charsCount, font.baseSize, 2, 0); + Image atlas = GenImageFontAtlas(font.chars, &font.recs, font.charsCount, font.baseSize, 2, 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 + UnloadFont(font); + 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: Using some SDF generation default values, - // trades off precision with ability to handle *smaller* sizes - #define SDF_CHAR_PADDING 4 - #define SDF_ON_EDGE_VALUE 128 - #define SDF_PIXEL_DIST_SCALE 64.0f - - #define BITMAP_ALPHA_THRESHOLD 80 - - CharInfo *chars = NULL; - - // Load font data (including pixel data) from TTF file - // NOTE: Loaded information should be enough to generate font image atlas, - // using any packaging method - FILE *fontFile = fopen(fileName, "rb"); // Load font file - - if (fontFile != NULL) - { - fseek(fontFile, 0, SEEK_END); - long size = ftell(fontFile); // Get file size - fseek(fontFile, 0, SEEK_SET); // Reset file pointer - - unsigned char *fontBuffer = (unsigned char *)malloc(size); - - fread(fontBuffer, size, 1, fontFile); - fclose(fontFile); - - // Init font for data reading - stbtt_fontinfo fontInfo; - if (!stbtt_InitFont(&fontInfo, fontBuffer, 0)) TraceLog(LOG_WARNING, "Failed to init font!"); - - // Calculate font scale factor - float scaleFactor = stbtt_ScaleForPixelHeight(&fontInfo, (float)fontSize); - - // Calculate font basic metrics - // NOTE: ascent is equivalent to font baseline - int ascent, descent, lineGap; - stbtt_GetFontVMetrics(&fontInfo, &ascent, &descent, &lineGap); - - // In case no chars count provided, default to 95 - charsCount = (charsCount > 0) ? charsCount : 95; - - // Fill fontChars in case not provided externally - // NOTE: By default we fill charsCount consecutevely, starting at 32 (Space) - int genFontChars = false; - if (fontChars == NULL) - { - fontChars = (int *)malloc(charsCount*sizeof(int)); - for (int i = 0; i < charsCount; i++) fontChars[i] = i + 32; - genFontChars = true; - } - - chars = (CharInfo *)malloc(charsCount*sizeof(CharInfo)); - - // NOTE: Using simple packaging, one char after another - for (int i = 0; i < charsCount; i++) - { - int chw = 0, chh = 0; // Character width and height (on generation) - int ch = fontChars[i]; // Character value to get info for - chars[i].value = ch; - - // Render a unicode codepoint to a bitmap - // stbtt_GetCodepointBitmap() -- allocates and returns a bitmap - // stbtt_GetCodepointBitmapBox() -- how big the bitmap must be - // stbtt_MakeCodepointBitmap() -- renders into bitmap you provide - - if (type != FONT_SDF) chars[i].data = stbtt_GetCodepointBitmap(&fontInfo, scaleFactor, scaleFactor, ch, &chw, &chh, &chars[i].offsetX, &chars[i].offsetY); - else if (ch != 32) chars[i].data = stbtt_GetCodepointSDF(&fontInfo, scaleFactor, ch, SDF_CHAR_PADDING, SDF_ON_EDGE_VALUE, SDF_PIXEL_DIST_SCALE, &chw, &chh, &chars[i].offsetX, &chars[i].offsetY); - - if (type == FONT_BITMAP) - { - // Aliased bitmap (black & white) font generation, avoiding anti-aliasing - // NOTE: For optimum results, bitmap font should be generated at base pixel size - for (int p = 0; p < chw*chh; p++) - { - if (chars[i].data[p] < BITMAP_ALPHA_THRESHOLD) chars[i].data[p] = 0; - else chars[i].data[p] = 255; - } - } - - chars[i].rec.width = (float)chw; - chars[i].rec.height = (float)chh; - chars[i].offsetY += (int)((float)ascent*scaleFactor); - - // Get bounding box for character (may be offset to account for chars that dip above or below the line) - int chX1, chY1, chX2, chY2; - stbtt_GetCodepointBitmapBox(&fontInfo, ch, scaleFactor, scaleFactor, &chX1, &chY1, &chX2, &chY2); - - TraceLog(LOG_DEBUG, "Character box measures: %i, %i, %i, %i", chX1, chY1, chX2 - chX1, chY2 - chY1); - TraceLog(LOG_DEBUG, "Character offsetY: %i", (int)((float)ascent*scaleFactor) + chY1); - - stbtt_GetCodepointHMetrics(&fontInfo, ch, &chars[i].advanceX, NULL); - chars[i].advanceX *= scaleFactor; - } - - free(fontBuffer); - if (genFontChars) free(fontChars); - } - else TraceLog(LOG_WARNING, "[%s] TTF file could not be opened", fileName); - - return chars; -} - -// Generate image font atlas using chars info -// NOTE: Packing method: 0-Default, 1-Skyline -Image GenImageFontAtlas(CharInfo *chars, int charsCount, int fontSize, int padding, int packMethod) -{ - Image atlas = { 0 }; - - // In case no chars count provided we suppose default of 95 - charsCount = (charsCount > 0) ? charsCount : 95; - - // Calculate image size based on required pixel area - // NOTE 1: Image is forced to be squared and POT... very conservative! - // NOTE 2: SDF font characters already contain an internal padding, - // so image size would result bigger than default font type - float requiredArea = 0; - for (int i = 0; i < charsCount; i++) requiredArea += ((chars[i].rec.width + 2*padding)*(chars[i].rec.height + 2*padding)); - float guessSize = sqrtf(requiredArea)*1.25f; - int imageSize = (int)powf(2, ceilf(logf((float)guessSize)/logf(2))); // Calculate next POT - - atlas.width = imageSize; // Atlas bitmap width - atlas.height = imageSize; // Atlas bitmap height - atlas.data = (unsigned char *)calloc(1, atlas.width*atlas.height); // Create a bitmap to store characters (8 bpp) - atlas.format = UNCOMPRESSED_GRAYSCALE; - atlas.mipmaps = 1; - - // DEBUG: We can see padding in the generated image setting a gray background... - //for (int i = 0; i < atlas.width*atlas.height; i++) ((unsigned char *)atlas.data)[i] = 100; - - if (packMethod == 0) // Use basic packing algorythm - { - int offsetX = padding; - int offsetY = padding; - - // NOTE: Using simple packaging, one char after another - for (int i = 0; i < charsCount; i++) - { - // Copy pixel data from fc.data to atlas - for (int y = 0; y < (int)chars[i].rec.height; y++) - { - for (int x = 0; x < (int)chars[i].rec.width; x++) - { - ((unsigned char *)atlas.data)[(offsetY + y)*atlas.width + (offsetX + x)] = chars[i].data[y*(int)chars[i].rec.width + x]; - } - } - - chars[i].rec.x = (float)offsetX; - chars[i].rec.y = (float)offsetY; - - // Move atlas position X for next character drawing - offsetX += ((int)chars[i].rec.width + 2*padding); - - if (offsetX >= (atlas.width - (int)chars[i].rec.width - padding)) - { - offsetX = padding; - - // NOTE: Be careful on offsetY for SDF fonts, by default SDF - // use an internal padding of 4 pixels, it means char rectangle - // height is bigger than fontSize, it could be up to (fontSize + 8) - offsetY += (fontSize + 2*padding); - - if (offsetY > (atlas.height - fontSize - padding)) break; - } - } - } - else if (packMethod == 1) // Use Skyline rect packing algorythm (stb_pack_rect) - { - TraceLog(LOG_DEBUG, "Using Skyline packing algorythm!"); - - stbrp_context *context = (stbrp_context *)malloc(sizeof(*context)); - stbrp_node *nodes = (stbrp_node *)malloc(charsCount*sizeof(*nodes)); - - stbrp_init_target(context, atlas.width, atlas.height, nodes, charsCount); - stbrp_rect *rects = (stbrp_rect *)malloc(charsCount*sizeof(stbrp_rect)); - - // Fill rectangles for packaging - for (int i = 0; i < charsCount; i++) - { - rects[i].id = i; - rects[i].w = (int)chars[i].rec.width + 2*padding; - rects[i].h = (int)chars[i].rec.height + 2*padding; - } - - // Package rectangles into atlas - stbrp_pack_rects(context, rects, charsCount); - - for (int i = 0; i < charsCount; i++) - { - chars[i].rec.x = rects[i].x + (float)padding; - chars[i].rec.y = rects[i].y + (float)padding; - - if (rects[i].was_packed) - { - // Copy pixel data from fc.data to atlas - for (int y = 0; y < (int)chars[i].rec.height; y++) - { - for (int x = 0; x < (int)chars[i].rec.width; x++) - { - ((unsigned char *)atlas.data)[(rects[i].y + padding + y)*atlas.width + (rects[i].x + padding + x)] = chars[i].data[y*(int)chars[i].rec.width + x]; - } - } - } - else TraceLog(LOG_WARNING, "Character could not be packed: %i", i); - } - - free(rects); - free(nodes); - free(context); - } - - // TODO: Crop image if required for smaller size - - // Convert image data from GRAYSCALE to GRAY_ALPHA - // WARNING: ImageAlphaMask(&atlas, atlas) does not work in this case, requires manual operation - unsigned char *dataGrayAlpha = (unsigned char *)malloc(imageSize*imageSize*sizeof(unsigned char)*2); // Two channels - - for (int i = 0, k = 0; i < atlas.width*atlas.height; i++, k += 2) - { - dataGrayAlpha[k] = 255; - dataGrayAlpha[k + 1] = ((unsigned char *)atlas.data)[i]; - } - - free(atlas.data); - atlas.data = dataGrayAlpha; - atlas.format = UNCOMPRESSED_GRAY_ALPHA; - - return atlas; -} - -// Unload Font from GPU memory (VRAM) -void UnloadFont(Font font) -{ - // NOTE: Make sure spriteFont is not default font (fallback) - if (font.texture.id != GetFontDefault().texture.id) - { - UnloadTexture(font.texture); - free(font.chars); - - TraceLog(LOG_DEBUG, "Unloaded sprite font data"); - } -} - -// Shows current FPS on top-left corner -// NOTE: Uses default font -void DrawFPS(int posX, int posY) -{ - // NOTE: We are rendering fps every second for better viewing on high framerates - - static int fps = 0; - static int counter = 0; - static int refreshRate = 20; - - if (counter < refreshRate) counter++; - else - { - fps = GetFPS(); - refreshRate = fps; - counter = 0; - } - - // NOTE: We have rounding errors every frame, so it oscillates a lot - DrawText(FormatText("%2i FPS", fps), posX, posY, 20, LIME); -} - -// Draw text (using default font) -// NOTE: fontSize work like in any drawing program but if fontSize is lower than font-base-size, then font-base-size is used -// NOTE: chars spacing is proportional to fontSize -void DrawText(const char *text, int posX, int posY, int fontSize, Color color) -{ - // Check if default font has been loaded - if (GetFontDefault().texture.id != 0) - { - Vector2 position = { (float)posX, (float)posY }; - - int defaultFontSize = 10; // Default Font chars height in pixel - if (fontSize < defaultFontSize) fontSize = defaultFontSize; - int spacing = fontSize/defaultFontSize; - - DrawTextEx(GetFontDefault(), text, position, (float)fontSize, (float)spacing, color); - } -} - -// 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 = strlen(text); - int textOffsetX = 0; // Offset between characters - int textOffsetY = 0; // Required for line break! - float scaleFactor = 0.0f; - - unsigned char letter = 0; // Current character - int index = 0; // Index position in sprite font - - scaleFactor = fontSize/font.baseSize; - - // NOTE: Some ugly hacks are made to support Latin-1 Extended characters directly - // written in C code files (codified by default as UTF-8) - - for (int i = 0; i < length; i++) - { - if ((unsigned char)text[i] == '\n') - { - // NOTE: Fixed line spacing of 1.5 lines - textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor); - textOffsetX = 0; - } - else - { - if ((unsigned char)text[i] == 0xc2) // UTF-8 encoding identification HACK! - { - // Support UTF-8 encoded values from [0xc2 0x80] -> [0xc2 0xbf](¿) - letter = (unsigned char)text[i + 1]; - index = GetGlyphIndex(font, (int)letter); - i++; - } - else if ((unsigned char)text[i] == 0xc3) // UTF-8 encoding identification HACK! - { - // Support UTF-8 encoded values from [0xc3 0x80](À) -> [0xc3 0xbf](ÿ) - letter = (unsigned char)text[i + 1]; - index = GetGlyphIndex(font, (int)letter + 64); - i++; - } - else index = GetGlyphIndex(font, (unsigned char)text[i]); - - if ((unsigned char)text[i] != ' ') - { - DrawTexturePro(font.texture, font.chars[index].rec, - (Rectangle){ position.x + textOffsetX + font.chars[index].offsetX*scaleFactor, - position.y + textOffsetY + font.chars[index].offsetY*scaleFactor, - font.chars[index].rec.width*scaleFactor, - font.chars[index].rec.height*scaleFactor }, (Vector2){ 0, 0 }, 0.0f, tint); - } - - if (font.chars[index].advanceX == 0) textOffsetX += (int)(font.chars[index].rec.width*scaleFactor + spacing); - else textOffsetX += (int)(font.chars[index].advanceX*scaleFactor + spacing); - } - } -} - -// Measure string width for default font -int MeasureText(const char *text, int fontSize) -{ - Vector2 vec = { 0.0f, 0.0f }; - - // Check if default font has been loaded - if (GetFontDefault().texture.id != 0) - { - int defaultFontSize = 10; // Default Font chars height in pixel - if (fontSize < defaultFontSize) fontSize = defaultFontSize; - int spacing = fontSize/defaultFontSize; - - vec = MeasureTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing); - } - - return (int)vec.x; -} - -// Measure string size for Font -Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing) -{ - int len = strlen(text); - int tempLen = 0; // Used to count longer text line num chars - int lenCounter = 0; - - float textWidth = 0.0f; - float tempTextWidth = 0.0f; // Used to count longer text line width - - float textHeight = (float)font.baseSize; - float scaleFactor = fontSize/(float)font.baseSize; - - for (int i = 0; i < len; i++) - { - lenCounter++; - - if (text[i] != '\n') - { - int index = GetGlyphIndex(font, (int)text[i]); - - if (font.chars[index].advanceX != 0) textWidth += font.chars[index].advanceX; - else textWidth += (font.chars[index].rec.width + font.chars[index].offsetX); - } - else - { - if (tempTextWidth < textWidth) tempTextWidth = textWidth; - lenCounter = 0; - textWidth = 0; - textHeight += ((float)font.baseSize*1.5f); // NOTE: Fixed line spacing of 1.5 lines - } - - if (tempLen < lenCounter) tempLen = lenCounter; - } - - if (tempTextWidth < textWidth) tempTextWidth = textWidth; - - Vector2 vec; - vec.x = tempTextWidth*scaleFactor + (float)((tempLen - 1)*spacing); // Adds chars spacing to measure - vec.y = textHeight*scaleFactor; - - return vec; -} - -// Returns index position for a unicode character on spritefont -int GetGlyphIndex(Font font, int character) -{ -#define UNORDERED_CHARSET -#if defined(UNORDERED_CHARSET) - int index = 0; - - for (int i = 0; i < font.charsCount; i++) - { - if (font.chars[i].value == character) - { - index = i; - break; - } - } - - return index; -#else - return (character - 32); -#endif -} - -// Formatting of text with variables to 'embed' -const char *FormatText(const char *text, ...) -{ - static char buffer[MAX_FORMATTEXT_LENGTH]; - - va_list args; - va_start(args, text); - vsprintf(buffer, text, args); - va_end(args); - - return buffer; -} - -// Get a piece of a text string -const char *SubText(const char *text, int position, int length) -{ - static char buffer[MAX_SUBTEXT_LENGTH] = { 0 }; - int textLength = strlen(text); - - if (position >= textLength) - { - position = textLength - 1; - length = 0; - } - - if (length >= textLength) length = textLength; - - for (int c = 0 ; c < length ; c++) - { - *(buffer + c) = *(text + position); - text++; - } - - *(buffer + length) = '\0'; - - return buffer; -} - -// Split string into multiple strings -// NOTE: Files count is returned by parameters pointer -// NOTE: Allocated memory should be manually freed -char **SplitText(char *text, char delimiter, int *strCount) -{ - #define MAX_SUBSTRING_LENGTH 128 - - char **strings = NULL; - int len = strlen(text); - char *strDup = (char *)malloc(len + 1); - strcpy(strDup, text); - int counter = 1; - - // Count how many substrings we have on string - for (int i = 0; i < len; i++) if (text[i] == delimiter) counter++; - - // Memory allocation for substrings - strings = (char **)malloc(sizeof(char *)*counter); - for (int i = 0; i < counter; i++) strings[i] = (char *)malloc(sizeof(char)*MAX_SUBSTRING_LENGTH); - - char *substrPtr = NULL; - char delimiters[1] = { delimiter }; // Only caring for one delimiter - substrPtr = strtok(strDup, delimiters); - - for (int i = 0; (i < counter) && (substrPtr != NULL); i++) - { - strcpy(strings[i], substrPtr); - substrPtr = strtok(NULL, delimiters); - } - - *strCount = counter; - free(strDup); - - return strings; -} - -// Check if two text string are equal -bool IsEqualText(const char *text1, const char *text2) -{ - bool result = false; - - if (strcmp(text1, text2) == 0) result = true; - - return result; -} - -//---------------------------------------------------------------------------------- -// Module specific Functions Definition -//---------------------------------------------------------------------------------- - // Load an Image font file (XNA style) -static Font LoadImageFont(Image image, Color key, int firstChar) +Font LoadFontFromImage(Image image, Color key, int firstChar) { #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a)) @@ -916,7 +444,7 @@ static Font LoadImageFont(Image image, Color key, int firstChar) // Create a new image with the processed color data (key color replaced by BLANK) Image fontClear = LoadImageEx(pixels, image.width, image.height); - free(pixels); // Free pixels array memory + RL_FREE(pixels); // Free pixels array memory // Create spritefont with all data parsed from image Font spriteFont = { 0 }; @@ -924,30 +452,1160 @@ static Font LoadImageFont(Image image, Color key, int firstChar) spriteFont.texture = LoadTextureFromImage(fontClear); // Convert processed image to OpenGL texture spriteFont.charsCount = index; - UnloadImage(fontClear); // Unload processed image once converted to texture - // We got tempCharValues and tempCharsRecs populated with chars data // Now we move temp data to sized charValues and charRecs arrays - spriteFont.chars = (CharInfo *)malloc(spriteFont.charsCount*sizeof(CharInfo)); + spriteFont.chars = (CharInfo *)RL_MALLOC(spriteFont.charsCount*sizeof(CharInfo)); + spriteFont.recs = (Rectangle *)RL_MALLOC(spriteFont.charsCount*sizeof(Rectangle)); for (int i = 0; i < spriteFont.charsCount; i++) { spriteFont.chars[i].value = tempCharValues[i]; - spriteFont.chars[i].rec = tempCharRecs[i]; + + // Get character rectangle in the font atlas texture + spriteFont.recs[i] = tempCharRecs[i]; // NOTE: On image based fonts (XNA style), character offsets and xAdvance are not required (set to 0) spriteFont.chars[i].offsetX = 0; spriteFont.chars[i].offsetY = 0; spriteFont.chars[i].advanceX = 0; + + // Fill character image data from fontClear data + spriteFont.chars[i].image = ImageFromImage(fontClear, tempCharRecs[i]); } - spriteFont.baseSize = (int)spriteFont.chars[0].rec.height; + UnloadImage(fontClear); // Unload processed image once converted to texture + + spriteFont.baseSize = (int)spriteFont.recs[0].height; TraceLog(LOG_INFO, "Image file loaded correctly as Font"); return spriteFont; } +// 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: Using some SDF generation default values, + // trades off precision with ability to handle *smaller* sizes + #define SDF_CHAR_PADDING 4 + #define SDF_ON_EDGE_VALUE 128 + #define SDF_PIXEL_DIST_SCALE 64.0f + + #define BITMAP_ALPHA_THRESHOLD 80 + + 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 + FILE *fontFile = fopen(fileName, "rb"); // Load font file + + if (fontFile != NULL) + { + fseek(fontFile, 0, SEEK_END); + long size = ftell(fontFile); // Get file size + fseek(fontFile, 0, SEEK_SET); // Reset file pointer + + unsigned char *fontBuffer = (unsigned char *)RL_MALLOC(size); + + fread(fontBuffer, size, 1, fontFile); + fclose(fontFile); + + // Init font for data reading + stbtt_fontinfo fontInfo; + if (!stbtt_InitFont(&fontInfo, fontBuffer, 0)) TraceLog(LOG_WARNING, "Failed to init font!"); + + // Calculate font scale factor + float scaleFactor = stbtt_ScaleForPixelHeight(&fontInfo, (float)fontSize); + + // Calculate font basic metrics + // NOTE: ascent is equivalent to font baseline + int ascent, descent, lineGap; + stbtt_GetFontVMetrics(&fontInfo, &ascent, &descent, &lineGap); + + // In case no chars count provided, default to 95 + charsCount = (charsCount > 0)? charsCount : 95; + + // Fill fontChars in case not provided externally + // NOTE: By default we fill charsCount consecutevely, starting at 32 (Space) + int genFontChars = false; + if (fontChars == NULL) + { + fontChars = (int *)RL_MALLOC(charsCount*sizeof(int)); + for (int i = 0; i < charsCount; i++) fontChars[i] = i + 32; + genFontChars = true; + } + + chars = (CharInfo *)RL_MALLOC(charsCount*sizeof(CharInfo)); + + // NOTE: Using simple packaging, one char after another + for (int i = 0; i < charsCount; i++) + { + int chw = 0, chh = 0; // Character width and height (on generation) + int ch = fontChars[i]; // Character value to get info for + chars[i].value = ch; + + // Render a unicode codepoint to a bitmap + // stbtt_GetCodepointBitmap() -- allocates and returns a bitmap + // stbtt_GetCodepointBitmapBox() -- how big the bitmap must be + // stbtt_MakeCodepointBitmap() -- renders into bitmap you provide + + if (type != FONT_SDF) chars[i].image.data = stbtt_GetCodepointBitmap(&fontInfo, scaleFactor, scaleFactor, ch, &chw, &chh, &chars[i].offsetX, &chars[i].offsetY); + else if (ch != 32) chars[i].image.data = stbtt_GetCodepointSDF(&fontInfo, scaleFactor, ch, SDF_CHAR_PADDING, SDF_ON_EDGE_VALUE, SDF_PIXEL_DIST_SCALE, &chw, &chh, &chars[i].offsetX, &chars[i].offsetY); + else chars[i].image.data = NULL; + + if (type == FONT_BITMAP) + { + // Aliased bitmap (black & white) font generation, avoiding anti-aliasing + // NOTE: For optimum results, bitmap font should be generated at base pixel size + for (int p = 0; p < chw*chh; p++) + { + if (((unsigned char *)chars[i].image.data)[p] < BITMAP_ALPHA_THRESHOLD) ((unsigned char *)chars[i].image.data)[p] = 0; + else ((unsigned char *)chars[i].image.data)[p] = 255; + } + } + + // Load characters images + chars[i].image.width = chw; + chars[i].image.height = chh; + chars[i].image.mipmaps = 1; + chars[i].image.format = UNCOMPRESSED_GRAYSCALE; + + chars[i].offsetY += (int)((float)ascent*scaleFactor); + + // Get bounding box for character (may be offset to account for chars that dip above or below the line) + int chX1, chY1, chX2, chY2; + stbtt_GetCodepointBitmapBox(&fontInfo, ch, scaleFactor, scaleFactor, &chX1, &chY1, &chX2, &chY2); + + TraceLog(LOG_DEBUG, "Character box measures: %i, %i, %i, %i", chX1, chY1, chX2 - chX1, chY2 - chY1); + TraceLog(LOG_DEBUG, "Character offsetY: %i", (int)((float)ascent*scaleFactor) + chY1); + + stbtt_GetCodepointHMetrics(&fontInfo, ch, &chars[i].advanceX, NULL); + chars[i].advanceX *= scaleFactor; + } + + RL_FREE(fontBuffer); + if (genFontChars) RL_FREE(fontChars); + } + else TraceLog(LOG_WARNING, "[%s] TTF file could not be opened", fileName); +#else + TraceLog(LOG_WARNING, "[%s] TTF support is disabled", fileName); +#endif + + return chars; +} + +// Generate image font atlas using chars info +// NOTE: Packing method: 0-Default, 1-Skyline +#if defined(SUPPORT_FILEFORMAT_TTF) +Image GenImageFontAtlas(const CharInfo *chars, Rectangle **charRecs, int charsCount, int fontSize, int padding, int packMethod) +{ + Image atlas = { 0 }; + + *charRecs = NULL; + + // In case no chars count provided we suppose default of 95 + charsCount = (charsCount > 0)? charsCount : 95; + + // NOTE: Rectangles memory is loaded here! + Rectangle *recs = (Rectangle *)RL_MALLOC(charsCount*sizeof(Rectangle)); + + // Calculate image size based on required pixel area + // NOTE 1: Image is forced to be squared and POT... very conservative! + // NOTE 2: SDF font characters already contain an internal padding, + // so image size would result bigger than default font type + float requiredArea = 0; + for (int i = 0; i < charsCount; i++) requiredArea += ((chars[i].image.width + 2*padding)*(chars[i].image.height + 2*padding)); + float guessSize = sqrtf(requiredArea)*1.3f; + int imageSize = (int)powf(2, ceilf(logf((float)guessSize)/logf(2))); // Calculate next POT + + 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.mipmaps = 1; + + // DEBUG: We can see padding in the generated image setting a gray background... + //for (int i = 0; i < atlas.width*atlas.height; i++) ((unsigned char *)atlas.data)[i] = 100; + + if (packMethod == 0) // Use basic packing algorythm + { + int offsetX = padding; + int offsetY = padding; + + // NOTE: Using simple packaging, one char after another + for (int i = 0; i < charsCount; i++) + { + // Copy pixel data from fc.data to atlas + for (int y = 0; y < chars[i].image.height; y++) + { + for (int x = 0; x < chars[i].image.width; x++) + { + ((unsigned char *)atlas.data)[(offsetY + y)*atlas.width + (offsetX + x)] = ((unsigned char *)chars[i].image.data)[y*chars[i].image.width + x]; + } + } + + // Fill chars rectangles in atlas info + recs[i].x = (float)offsetX; + recs[i].y = (float)offsetY; + recs[i].width = (float)chars[i].image.width; + recs[i].height = (float)chars[i].image.height; + + // Move atlas position X for next character drawing + offsetX += (chars[i].image.width + 2*padding); + + if (offsetX >= (atlas.width - chars[i].image.width - padding)) + { + offsetX = padding; + + // NOTE: Be careful on offsetY for SDF fonts, by default SDF + // use an internal padding of 4 pixels, it means char rectangle + // height is bigger than fontSize, it could be up to (fontSize + 8) + offsetY += (fontSize + 2*padding); + + if (offsetY > (atlas.height - fontSize - padding)) break; + } + } + } + else if (packMethod == 1) // Use Skyline rect packing algorythm (stb_pack_rect) + { + TraceLog(LOG_DEBUG, "Using Skyline packing algorythm!"); + + stbrp_context *context = (stbrp_context *)RL_MALLOC(sizeof(*context)); + stbrp_node *nodes = (stbrp_node *)RL_MALLOC(charsCount*sizeof(*nodes)); + + stbrp_init_target(context, atlas.width, atlas.height, nodes, charsCount); + stbrp_rect *rects = (stbrp_rect *)RL_MALLOC(charsCount*sizeof(stbrp_rect)); + + // Fill rectangles for packaging + for (int i = 0; i < charsCount; i++) + { + rects[i].id = i; + rects[i].w = chars[i].image.width + 2*padding; + rects[i].h = chars[i].image.height + 2*padding; + } + + // Package rectangles into atlas + stbrp_pack_rects(context, rects, charsCount); + + for (int i = 0; i < charsCount; i++) + { + // It return char rectangles in atlas + recs[i].x = rects[i].x + (float)padding; + recs[i].y = rects[i].y + (float)padding; + recs[i].width = (float)chars[i].image.width; + recs[i].height = (float)chars[i].image.height; + + if (rects[i].was_packed) + { + // Copy pixel data from fc.data to atlas + for (int y = 0; y < chars[i].image.height; y++) + { + for (int x = 0; x < chars[i].image.width; x++) + { + ((unsigned char *)atlas.data)[(rects[i].y + padding + y)*atlas.width + (rects[i].x + padding + x)] = ((unsigned char *)chars[i].image.data)[y*chars[i].image.width + x]; + } + } + } + else TraceLog(LOG_WARNING, "Character could not be packed: %i", i); + } + + RL_FREE(rects); + RL_FREE(nodes); + RL_FREE(context); + } + + // TODO: Crop image if required for smaller size + + // Convert image data from GRAYSCALE to GRAY_ALPHA + // WARNING: ImageAlphaMask(&atlas, atlas) does not work in this case, requires manual operation + unsigned char *dataGrayAlpha = (unsigned char *)RL_MALLOC(atlas.width*atlas.height*sizeof(unsigned char)*2); // Two channels + + for (int i = 0, k = 0; i < atlas.width*atlas.height; i++, k += 2) + { + dataGrayAlpha[k] = 255; + dataGrayAlpha[k + 1] = ((unsigned char *)atlas.data)[i]; + } + + RL_FREE(atlas.data); + atlas.data = dataGrayAlpha; + atlas.format = UNCOMPRESSED_GRAY_ALPHA; + + *charRecs = recs; + + return atlas; +} +#endif + +// Unload Font from GPU memory (VRAM) +void UnloadFont(Font font) +{ + // NOTE: Make sure spriteFont 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); + + UnloadTexture(font.texture); + RL_FREE(font.chars); + RL_FREE(font.recs); + + TraceLog(LOG_DEBUG, "Unloaded sprite font data"); + } +} + +// Shows current FPS on top-left corner +// NOTE: Uses default font +void DrawFPS(int posX, int posY) +{ + // NOTE: We are rendering fps every second for better viewing on high framerates + + static int fps = 0; + static int counter = 0; + static int refreshRate = 20; + + if (counter < refreshRate) counter++; + else + { + fps = GetFPS(); + refreshRate = fps; + counter = 0; + } + + // NOTE: We have rounding errors every frame, so it oscillates a lot + DrawText(TextFormat("%2i FPS", fps), posX, posY, 20, LIME); +} + +// Draw text (using default font) +// NOTE: fontSize work like in any drawing program but if fontSize is lower than font-base-size, then font-base-size is used +// NOTE: chars spacing is proportional to fontSize +void DrawText(const char *text, int posX, int posY, int fontSize, Color color) +{ + // Check if default font has been loaded + if (GetFontDefault().texture.id != 0) + { + Vector2 position = { (float)posX, (float)posY }; + + int defaultFontSize = 10; // Default Font chars height in pixel + if (fontSize < defaultFontSize) fontSize = defaultFontSize; + int spacing = fontSize/defaultFontSize; + + DrawTextEx(GetFontDefault(), text, position, (float)fontSize, (float)spacing, color); + } +} + +// 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 = strlen(text); + int textOffsetY = 0; // Required for line break! + float textOffsetX = 0.0f; // Offset between characters + float scaleFactor = 0.0f; + + int letter = 0; // Current character + int index = 0; // Index position in sprite font + + scaleFactor = fontSize/font.baseSize; + + for (int i = 0; i < length; i++) + { + int next = 0; + letter = GetNextCodepoint(&text[i], &next); + index = GetGlyphIndex(font, letter); + + // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set 'next = 1' + if (letter == 0x3f) next = 1; + i += (next - 1); + + if (letter == '\n') + { + // NOTE: Fixed line spacing of 1.5 lines + textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor); + textOffsetX = 0.0f; + } + else + { + if (letter != ' ') + { + DrawTexturePro(font.texture, font.recs[index], + (Rectangle){ 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 }, (Vector2){ 0, 0 }, 0.0f, tint); + } + + if (font.chars[index].advanceX == 0) textOffsetX += ((float)font.recs[index].width*scaleFactor + spacing); + else textOffsetX += ((float)font.chars[index].advanceX*scaleFactor + spacing); + } + } +} + +// Draw text using font inside rectangle limits +void DrawTextRec(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint) +{ + DrawTextRecEx(font, text, rec, fontSize, spacing, wordWrap, tint, 0, 0, WHITE, WHITE); +} + +// 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 selectText, Color selectBack) +{ + int length = strlen(text); + int textOffsetX = 0; // Offset between characters + int textOffsetY = 0; // Required for line break! + float scaleFactor = 0.0f; + + int letter = 0; // Current character + int index = 0; // Index position in sprite font + + scaleFactor = fontSize/font.baseSize; + + enum { MEASURE_STATE = 0, DRAW_STATE = 1 }; + int state = wordWrap? MEASURE_STATE : DRAW_STATE; + int startLine = -1; // Index where to begin drawing (where a line begins) + int endLine = -1; // Index where to stop drawing (where a line ends) + int lastk = -1; // Holds last value of the character position + + for (int i = 0, k = 0; i < length; i++, k++) + { + int glyphWidth = 0; + int next = 0; + letter = GetNextCodepoint(&text[i], &next); + index = GetGlyphIndex(font, letter); + + // NOTE: normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set next = 1 + if (letter == 0x3f) next = 1; + i += next - 1; + + if (letter != '\n') + { + glyphWidth = (font.chars[index].advanceX == 0)? + (int)(font.recs[index].width*scaleFactor + spacing): + (int)(font.chars[index].advanceX*scaleFactor + spacing); + } + + // NOTE: When wordWrap is ON we first measure how much of the text we can draw before going outside of the rec container + // We store this info in startLine and endLine, then we change states, draw the text between those two variables + // and change states again and again recursively until the end of the text (or until we get outside of the container). + // When wordWrap is OFF we don't need the measure state so we go to the drawing state immediately + // and begin drawing on the next line before we can get outside the container. + if (state == MEASURE_STATE) + { + // TODO: there are multiple types of spaces in UNICODE, maybe it's a good idea to add support for more + // See: http://jkorpela.fi/chars/spaces.html + if ((letter == ' ') || (letter == '\t') || (letter == '\n')) endLine = i; + + if ((textOffsetX + glyphWidth + 1) >= rec.width) + { + endLine = (endLine < 1)? i : endLine; + if (i == endLine) endLine -= next; + if ((startLine + next) == endLine) endLine = i - next; + state = !state; + } + else if ((i + 1) == length) + { + endLine = i; + state = !state; + } + else if (letter == '\n') + { + state = !state; + } + + if (state == DRAW_STATE) + { + textOffsetX = 0; + i = startLine; + glyphWidth = 0; + + // Save character position when we switch states + int tmp = lastk; + lastk = k - 1; + k = tmp; + } + } + else + { + if (letter == '\n') + { + if (!wordWrap) + { + textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor); + textOffsetX = 0; + } + } + else + { + if (!wordWrap && ((textOffsetX + glyphWidth + 1) >= rec.width)) + { + textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor); + textOffsetX = 0; + } + + if ((textOffsetY + (int)(font.baseSize*scaleFactor)) > rec.height) break; + + // Draw selected + bool isGlyphSelected = false; + if ((selectStart >= 0) && (k >= selectStart) && (k < (selectStart + selectLength))) + { + Rectangle strec = {rec.x + textOffsetX-1, rec.y + textOffsetY, glyphWidth, font.baseSize*scaleFactor }; + DrawRectangleRec(strec, selectBack); + isGlyphSelected = true; + } + + // Draw glyph + if ((letter != ' ') && (letter != '\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 : selectText); + } + } + + if (wordWrap && (i == endLine)) + { + textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor); + textOffsetX = 0; + startLine = endLine; + endLine = -1; + glyphWidth = 0; + k = lastk; + state = !state; + } + } + + textOffsetX += glyphWidth; + } +} + +// Measure string width for default font +int MeasureText(const char *text, int fontSize) +{ + Vector2 vec = { 0.0f, 0.0f }; + + // Check if default font has been loaded + if (GetFontDefault().texture.id != 0) + { + int defaultFontSize = 10; // Default Font chars height in pixel + if (fontSize < defaultFontSize) fontSize = defaultFontSize; + int spacing = fontSize/defaultFontSize; + + vec = MeasureTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing); + } + + return (int)vec.x; +} + +// Measure string size for Font +Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing) +{ + int len = strlen(text); + int tempLen = 0; // Used to count longer text line num chars + int lenCounter = 0; + + float textWidth = 0.0f; + float tempTextWidth = 0.0f; // Used to count longer text line width + + float textHeight = (float)font.baseSize; + float scaleFactor = fontSize/(float)font.baseSize; + + int letter = 0; // Current character + int index = 0; // Index position in sprite font + + for (int i = 0; i < len; i++) + { + lenCounter++; + + int next = 0; + letter = GetNextCodepoint(&text[i], &next); + index = GetGlyphIndex(font, letter); + + // NOTE: normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set next = 1 + if (letter == 0x3f) next = 1; + i += next - 1; + + if (letter != '\n') + { + if (font.chars[index].advanceX != 0) textWidth += font.chars[index].advanceX; + else textWidth += (font.recs[index].width + font.chars[index].offsetX); + } + else + { + if (tempTextWidth < textWidth) tempTextWidth = textWidth; + lenCounter = 0; + textWidth = 0; + textHeight += ((float)font.baseSize*1.5f); // NOTE: Fixed line spacing of 1.5 lines + } + + if (tempLen < lenCounter) tempLen = lenCounter; + } + + if (tempTextWidth < textWidth) tempTextWidth = textWidth; + + Vector2 vec = { 0 }; + vec.x = tempTextWidth*scaleFactor + (float)((tempLen - 1)*spacing); // Adds chars spacing to measure + vec.y = textHeight*scaleFactor; + + return vec; +} + +// Returns index position for a unicode character on spritefont +int GetGlyphIndex(Font font, int character) +{ +#define UNORDERED_CHARSET +#if defined(UNORDERED_CHARSET) + int index = 0; + + for (int i = 0; i < font.charsCount; i++) + { + if (font.chars[i].value == character) + { + index = i; + break; + } + } + + return index; +#else + return (character - 32); +#endif +} + +// Text strings management functions +//---------------------------------------------------------------------------------- +// Check if two text string are equal +// REQUIRES: strcmp() +bool TextIsEqual(const char *text1, const char *text2) +{ + bool result = false; + + if (strcmp(text1, text2) == 0) result = true; + + return result; +} + +// Get text length in bytes, check for \0 character +unsigned int TextLength(const char *text) +{ + unsigned int length = 0; + + while (*text++) length++; + + return length; +} + +// Formatting of text with variables to 'embed' +// WARNING: String returned will expire after this function is called MAX_TEXTFORMAT_BUFFERS times +const char *TextFormat(const char *text, ...) +{ + #define MAX_TEXTFORMAT_BUFFERS 4 + + // 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; + + char *currentBuffer = buffers[index]; + + va_list args; + va_start(args, text); + vsprintf(currentBuffer, text, args); + va_end(args); + + index += 1; // Move to next buffer for next function call + if (index >= MAX_TEXTFORMAT_BUFFERS) index = 0; + + return currentBuffer; +} + +// Get a piece of a text string +// REQUIRES: strlen() +const char *TextSubtext(const char *text, int position, int length) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + + int textLength = strlen(text); + + if (position >= textLength) + { + position = textLength - 1; + length = 0; + } + + if (length >= textLength) length = textLength; + + for (int c = 0 ; c < length ; c++) + { + *(buffer + c) = *(text + position); + text++; + } + + *(buffer + length) = '\0'; + + return buffer; +} + +// Replace text string +// REQUIRES: strlen(), strstr(), strncpy(), strcpy() +// WARNING: Internally allocated memory must be freed by the user (if return != NULL) +char *TextReplace(char *text, const char *replace, const char *by) +{ + char *result; + + char *insertPoint; // Next insert point + char *temp; // Temp pointer + int replaceLen; // Replace string length of (the string to remove) + int byLen; // Replacement length (the string to replace replace by) + int lastReplacePos; // Distance between replace and end of last replace + int count; // Number of replacements + + // Sanity checks and initialization + if (!text || !replace) return NULL; + + replaceLen = strlen(replace); + if (replaceLen == 0) return NULL; // Empty replace causes infinite loop during count + + if (!by) by = ""; // Replace by nothing if not provided + byLen = strlen(by); + + // Count the number of replacements needed + insertPoint = text; + for (count = 0; (temp = strstr(insertPoint, replace)); count++) insertPoint = temp + replaceLen; + + // Allocate returning string and point temp to it + temp = result = RL_MALLOC(strlen(text) + (byLen - replaceLen)*count + 1); + + if (!result) return NULL; // Memory could not be allocated + + // First time through the loop, all the variable are set correctly from here on, + // temp points to the end of the result string + // insertPoint points to the next occurrence of replace in text + // text points to the remainder of text after "end of replace" + while (count--) + { + insertPoint = strstr(text, replace); + lastReplacePos = insertPoint - text; + temp = strncpy(temp, text, lastReplacePos) + lastReplacePos; + temp = strcpy(temp, by) + byLen; + text += lastReplacePos + replaceLen; // Move to next "end of replace" + } + + // Copy remaind text part after replacement to result (pointed by moving temp) + strcpy(temp, text); + + return result; +} + +// Insert text in a specific position, moves all text forward +// REQUIRES: strlen(), strcpy(), strtok() +// WARNING: Allocated memory should be manually freed +char *TextInsert(const char *text, const char *insert, int position) +{ + int textLen = strlen(text); + int insertLen = strlen(insert); + + char *result = (char *)RL_MALLOC(textLen + insertLen + 1); + + for (int i = 0; i < position; i++) result[i] = text[i]; + for (int i = position; i < insertLen + position; i++) result[i] = insert[i]; + for (int i = (insertLen + position); i < (textLen + insertLen); i++) result[i] = text[i]; + + result[textLen + insertLen] = '\0'; // Make sure text string is valid! + + return result; +} + +// Join text strings with delimiter +// REQUIRES: strcat() +const char *TextJoin(const char **textList, int count, const char *delimiter) +{ + static char text[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + memset(text, 0, MAX_TEXT_BUFFER_LENGTH); + + int totalLength = 0; + int delimiterLen = strlen(delimiter); + + for (int i = 0; i < count; i++) + { + int textListLength = strlen(textList[i]); + + // Make sure joined text could fit inside MAX_TEXT_BUFFER_LENGTH + if ((totalLength + textListLength) < MAX_TEXT_BUFFER_LENGTH) + { + strcat(text, textList[i]); + totalLength += textListLength; + + if ((delimiterLen > 0) && (i < (count - 1))) + { + strcat(text, delimiter); + totalLength += delimiterLen; + } + } + } + + return text; +} + +// Split string into multiple strings +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) + // inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated, + // all used memory is static... it has some limitations: + // 1. Maximum number of possible split strings is set by TEXTSPLIT_MAX_SUBSTRINGS_COUNT + // 2. Maximum size of text to split is TEXTSPLIT_MAX_TEXT_BUFFER_LENGTH + + static const char *result[TEXTSPLIT_MAX_SUBSTRINGS_COUNT] = { NULL }; + static char buffer[TEXTSPLIT_MAX_TEXT_BUFFER_LENGTH] = { 0 }; + memset(buffer, 0, TEXTSPLIT_MAX_TEXT_BUFFER_LENGTH); + + result[0] = buffer; + int counter = 0; + + if (text != NULL) + { + counter = 1; + + // Count how many substrings we have on text and point to every one + for (int i = 0; i < MAX_TEXT_BUFFER_LENGTH; i++) + { + buffer[i] = text[i]; + if (buffer[i] == '\0') break; + else if (buffer[i] == delimiter) + { + buffer[i] = '\0'; // Set an end of string at this point + result[counter] = buffer + i + 1; + counter++; + + if (counter == TEXTSPLIT_MAX_SUBSTRINGS_COUNT) break; + } + } + } + + *count = counter; + return result; +} + +// Append text at specific position and move cursor! +// REQUIRES: strcpy() +void TextAppend(char *text, const char *append, int *position) +{ + strcpy(text + *position, append); + *position += strlen(append); +} + +// Find first text occurrence within a string +// REQUIRES: strstr() +int TextFindIndex(const char *text, const char *find) +{ + int position = -1; + + char *ptr = strstr(text, find); + + if (ptr != NULL) position = ptr - text; + + return position; +} + +// Get upper case version of provided string +// REQUIRES: toupper() +const char *TextToUpper(const char *text) +{ + 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)toupper(text[i]); + else { buffer[i] = '\0'; break; } + } + + return buffer; +} + +// Get lower case version of provided string +// REQUIRES: tolower() +const char *TextToLower(const char *text) +{ + 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]); + else { buffer[i] = '\0'; break; } + } + + return buffer; +} + +// Get Pascal case notation version of provided string +// REQUIRES: toupper() +const char *TextToPascal(const char *text) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + + buffer[0] = (char)toupper(text[0]); + + for (int i = 1, j = 1; i < MAX_TEXT_BUFFER_LENGTH; i++, j++) + { + if (text[j] != '\0') + { + if (text[j] != '_') buffer[i] = text[j]; + else + { + j++; + buffer[i] = (char)toupper(text[j]); + } + } + else { buffer[i] = '\0'; break; } + } + + return buffer; +} + +// Get integer value from text +// NOTE: Negative values not supported +int TextToInteger(const char *text) +{ + int result = 0; + int len = strlen(text); + int units = 1; + + for (int i = len - 1; i >= 0; i--) + { + if ((text[i] > 47) && (text[i] < 58)) result += ((int)text[i] - 48)*units; + else { result = -1; break; } + + units *= 10; + } + + return result; +} + +// Encode text codepoint into utf8 text (memory must be freed!) +char *TextToUtf8(int *codepoints, int length) +{ + // We allocate enough memory fo fit all possible codepoints + // NOTE: 5 bytes for every codepoint should be enough + char *text = (char *)calloc(length*5, 1); + const char *utf8 = NULL; + int size = 0; + + for (int i = 0, bytes = 0; i < length; i++) + { + utf8 = CodepointToUtf8(codepoints[i], &bytes); + strncpy(text + size, utf8, bytes); + size += bytes; + } + + // Resize memory to text length + string NULL terminator + text = realloc(text, size + 1); + + return text; +} + +// Get all codepoints in a string, codepoints count returned by parameters +int *GetCodepoints(const char *text, int *count) +{ + static int codepoints[MAX_TEXT_UNICODE_CHARS] = { 0 }; + memset(codepoints, 0, MAX_TEXT_UNICODE_CHARS*sizeof(int)); + + int bytesProcessed = 0; + int textLength = strlen(text); + int codepointsCount = 0; + + for (int i = 0; i < textLength; codepointsCount++) + { + codepoints[codepointsCount] = GetNextCodepoint(text + i, &bytesProcessed); + i += bytesProcessed; + } + + *count = codepointsCount; + + return codepoints; +} + +// Returns total number of characters(codepoints) in a UTF8 encoded text, until '\0' is found +// NOTE: If an invalid UTF8 sequence is encountered a '?'(0x3f) codepoint is counted instead +int GetCodepointsCount(const char *text) +{ + unsigned int len = 0; + char *ptr = (char *)&text[0]; + + while (*ptr != '\0') + { + int next = 0; + int letter = GetNextCodepoint(ptr, &next); + + if (letter == 0x3f) ptr += 1; + else ptr += next; + + len++; + } + + return len; +} + + +// Returns next codepoint in a UTF8 encoded text, scanning until '\0' is found +// When a invalid UTF8 byte is encountered we exit as soon as possible and a '?'(0x3f) codepoint is returned +// 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!! +int GetNextCodepoint(const char *text, int *bytesProcessed) +{ +/* + UTF8 specs from https://www.ietf.org/rfc/rfc3629.txt + + Char. number range | UTF-8 octet sequence + (hexadecimal) | (binary) + --------------------+--------------------------------------------- + 0000 0000-0000 007F | 0xxxxxxx + 0000 0080-0000 07FF | 110xxxxx 10xxxxxx + 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx + 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx +*/ + // NOTE: on decode errors we return as soon as possible + + int code = 0x3f; // Codepoint (defaults to '?') + int octet = (unsigned char)(text[0]); // The first UTF8 octet + *bytesProcessed = 1; + + if (octet <= 0x7f) + { + // Only one octet (ASCII range x00-7F) + code = text[0]; + } + else if ((octet & 0xe0) == 0xc0) + { + // Two octets + // [0]xC2-DF [1]UTF8-tail(x80-BF) + unsigned char octet1 = text[1]; + + if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *bytesProcessed = 2; return code; } // Unexpected sequence + + if ((octet >= 0xc2) && (octet <= 0xdf)) + { + code = ((octet & 0x1f) << 6) | (octet1 & 0x3f); + *bytesProcessed = 2; + } + } + else if ((octet & 0xf0) == 0xe0) + { + // Three octets + unsigned char octet1 = text[1]; + unsigned char octet2 = '\0'; + + if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *bytesProcessed = 2; return code; } // Unexpected sequence + + octet2 = text[2]; + + if ((octet2 == '\0') || ((octet2 >> 6) != 2)) { *bytesProcessed = 3; return code; } // Unexpected sequence + + /* + [0]xE0 [1]xA0-BF [2]UTF8-tail(x80-BF) + [0]xE1-EC [1]UTF8-tail [2]UTF8-tail(x80-BF) + [0]xED [1]x80-9F [2]UTF8-tail(x80-BF) + [0]xEE-EF [1]UTF8-tail [2]UTF8-tail(x80-BF) + */ + + if (((octet == 0xe0) && !((octet1 >= 0xa0) && (octet1 <= 0xbf))) || + ((octet == 0xed) && !((octet1 >= 0x80) && (octet1 <= 0x9f)))) { *bytesProcessed = 2; return code; } + + if ((octet >= 0xe0) && (0 <= 0xef)) + { + code = ((octet & 0xf) << 12) | ((octet1 & 0x3f) << 6) | (octet2 & 0x3f); + *bytesProcessed = 3; + } + } + else if ((octet & 0xf8) == 0xf0) + { + // Four octets + if (octet > 0xf4) return code; + + unsigned char octet1 = text[1]; + unsigned char octet2 = '\0'; + unsigned char octet3 = '\0'; + + if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *bytesProcessed = 2; return code; } // Unexpected sequence + + octet2 = text[2]; + + if ((octet2 == '\0') || ((octet2 >> 6) != 2)) { *bytesProcessed = 3; return code; } // Unexpected sequence + + octet3 = text[3]; + + if ((octet3 == '\0') || ((octet3 >> 6) != 2)) { *bytesProcessed = 4; return code; } // Unexpected sequence + + /* + [0]xF0 [1]x90-BF [2]UTF8-tail [3]UTF8-tail + [0]xF1-F3 [1]UTF8-tail [2]UTF8-tail [3]UTF8-tail + [0]xF4 [1]x80-8F [2]UTF8-tail [3]UTF8-tail + */ + + if (((octet == 0xf0) && !((octet1 >= 0x90) && (octet1 <= 0xbf))) || + ((octet == 0xf4) && !((octet1 >= 0x80) && (octet1 <= 0x8f)))) { *bytesProcessed = 2; return code; } // Unexpected sequence + + if (octet >= 0xf0) + { + code = ((octet & 0x7) << 18) | ((octet1 & 0x3f) << 12) | ((octet2 & 0x3f) << 6) | (octet3 & 0x3f); + *bytesProcessed = 4; + } + } + + if (code > 0x10ffff) code = 0x3f; // Codepoints after U+10ffff are invalid + + return code; +} + +// Encode codepoint into utf8 text (char array length returned as parameter) +RLAPI const char *CodepointToUtf8(int codepoint, int *byteLength) +{ + static char utf8[6] = { 0 }; + int length = 0; + + if (codepoint <= 0x7f) + { + utf8[0] = (char)codepoint; + length = 1; + } + else if (codepoint <= 0x7ff) + { + utf8[0] = (char)(((codepoint >> 6) & 0x1f) | 0xc0); + utf8[1] = (char)((codepoint & 0x3f) | 0x80); + length = 2; + } + else if (codepoint <= 0xffff) + { + utf8[0] = (char)(((codepoint >> 12) & 0x0f) | 0xe0); + utf8[1] = (char)(((codepoint >> 6) & 0x3f) | 0x80); + utf8[2] = (char)((codepoint & 0x3f) | 0x80); + length = 3; + } + else if (codepoint <= 0x10ffff) + { + utf8[0] = (char)(((codepoint >> 18) & 0x07) | 0xf0); + utf8[1] = (char)(((codepoint >> 12) & 0x3f) | 0x80); + utf8[2] = (char)(((codepoint >> 6) & 0x3f) | 0x80); + utf8[3] = (char)((codepoint & 0x3f) | 0x80); + length = 4; + } + + *byteLength = length; + + return utf8; +} +//---------------------------------------------------------------------------------- + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- #if defined(SUPPORT_FILEFORMAT_FNT) // Load a BMFont file (AngelCode font file) static Font LoadBMFont(const char *fileName) @@ -1007,9 +1665,13 @@ static Font LoadBMFont(const char *fileName) char *lastSlash = NULL; lastSlash = strrchr(fileName, '/'); + if (lastSlash == NULL) + { + lastSlash = strrchr(fileName, '\\'); + } // NOTE: We need some extra space to avoid memory corruption on next allocations! - texPath = malloc(strlen(fileName) - strlen(lastSlash) + strlen(texFileName) + 4); + texPath = RL_MALLOC(strlen(fileName) - strlen(lastSlash) + strlen(texFileName) + 4); // NOTE: strcat() and strncat() required a '\0' terminated string to work! *texPath = '\0'; @@ -1022,24 +1684,20 @@ static Font LoadBMFont(const char *fileName) if (imFont.format == UNCOMPRESSED_GRAYSCALE) { - Image imCopy = ImageCopy(imFont); - - for (int i = 0; i < imCopy.width*imCopy.height; i++) ((unsigned char *)imCopy.data)[i] = 0xff; - - ImageAlphaMask(&imCopy, imFont); - font.texture = LoadTextureFromImage(imCopy); - UnloadImage(imCopy); + // Convert image to GRAYSCALE + ALPHA, using the mask as the alpha channel + ImageAlphaMask(&imFont, imFont); + for (int p = 0; p < (imFont.width*imFont.height*2); p += 2) ((unsigned char *)(imFont.data))[p] = 0xff; } - else font.texture = LoadTextureFromImage(imFont); - UnloadImage(imFont); - free(texPath); + font.texture = LoadTextureFromImage(imFont); + RL_FREE(texPath); // Fill font characters info data font.baseSize = fontSize; font.charsCount = charsCount; - font.chars = (CharInfo *)malloc(charsCount*sizeof(CharInfo)); + font.chars = (CharInfo *)RL_MALLOC(charsCount*sizeof(CharInfo)); + font.recs = (Rectangle *)RL_MALLOC(charsCount*sizeof(Rectangle)); int charId, charX, charY, charWidth, charHeight, charOffsetX, charOffsetY, charAdvanceX; @@ -1049,14 +1707,21 @@ static Font LoadBMFont(const char *fileName) sscanf(buffer, "char id=%i x=%i y=%i width=%i height=%i xoffset=%i yoffset=%i xadvance=%i", &charId, &charX, &charY, &charWidth, &charHeight, &charOffsetX, &charOffsetY, &charAdvanceX); + // Get character rectangle in the font atlas texture + font.recs[i] = (Rectangle){ (float)charX, (float)charY, (float)charWidth, (float)charHeight }; + // Save data properly in sprite font font.chars[i].value = charId; - font.chars[i].rec = (Rectangle){ (float)charX, (float)charY, (float)charWidth, (float)charHeight }; font.chars[i].offsetX = charOffsetX; font.chars[i].offsetY = charOffsetY; font.chars[i].advanceX = charAdvanceX; + + // Fill character image data from imFont data + font.chars[i].image = ImageFromImage(imFont, font.recs[i]); } + UnloadImage(imFont); + fclose(fntFile); if (font.texture.id == 0) @@ -1068,4 +1733,4 @@ static Font LoadBMFont(const char *fileName) return font; } -#endif \ No newline at end of file +#endif diff --git a/raylib/text.go b/raylib/text.go index 26b3cda..06afc8f 100644 --- a/raylib/text.go +++ b/raylib/text.go @@ -34,13 +34,13 @@ func LoadFont(fileName string) Font { } // LoadFontEx - Load Font from file with extended parameters -func LoadFontEx(fileName string, fontSize int32, charsCount int32, fontChars *int32) Font { +func LoadFontEx(fileName string, fontSize int32, fontChars *int32, charsCount int32) Font { cfileName := C.CString(fileName) defer C.free(unsafe.Pointer(cfileName)) cfontSize := (C.int)(fontSize) - ccharsCount := (C.int)(charsCount) cfontChars := (*C.int)(unsafe.Pointer(fontChars)) - ret := C.LoadFontEx(cfileName, cfontSize, ccharsCount, cfontChars) + ccharsCount := (C.int)(charsCount) + ret := C.LoadFontEx(cfileName, cfontSize, cfontChars, ccharsCount) v := newFontFromPointer(unsafe.Pointer(&ret)) return v } diff --git a/raylib/textures.c b/raylib/textures.c index 4345151..ed6b8e6 100644 --- a/raylib/textures.c +++ b/raylib/textures.c @@ -10,13 +10,14 @@ * #define SUPPORT_FILEFORMAT_JPG * #define SUPPORT_FILEFORMAT_GIF * #define SUPPORT_FILEFORMAT_PSD +* #define SUPPORT_FILEFORMAT_PIC * #define SUPPORT_FILEFORMAT_HDR * #define SUPPORT_FILEFORMAT_DDS * #define SUPPORT_FILEFORMAT_PKM * #define SUPPORT_FILEFORMAT_KTX * #define SUPPORT_FILEFORMAT_PVR * #define SUPPORT_FILEFORMAT_ASTC -* Selecte desired fileformats to be supported for image data loading. Some of those formats are +* Select desired fileformats to be supported for image data loading. Some of those formats are * supported by default, to remove support, just comment unrequired #define in this module * * #define SUPPORT_IMAGE_EXPORT @@ -37,7 +38,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2018 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2019 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. @@ -56,21 +57,23 @@ * **********************************************************************************************/ -#include "config.h" // Defines module configuration flags #include "raylib.h" // Declares module functions +// Check if config flags have been externally provided on compilation line +#if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags +#endif + #include // Required for: malloc(), free() #include // Required for: strlen() +#include // Required for: FILE, fopen(), fclose(), fread() + +#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(), // rlGenerateMipmaps(), some funcs for DrawTexturePro() -#include "utils.h" // Required for: fopen() Android mapping - -#define STB_PERLIN_IMPLEMENTATION -#include "external/stb_perlin.h"// Required for: stb_perlin_fbm_noise3 - // Support only desired texture formats on stb_image #if !defined(SUPPORT_FILEFORMAT_BMP) #define STBI_NO_BMP @@ -90,6 +93,9 @@ #if !defined(SUPPORT_FILEFORMAT_GIF) #define STBI_NO_GIF #endif +#if !defined(SUPPORT_FILEFORMAT_PIC) + #define STBI_NO_PIC +#endif #if !defined(SUPPORT_FILEFORMAT_HDR) #define STBI_NO_HDR #endif @@ -98,12 +104,22 @@ #define STBI_NO_PIC #define STBI_NO_PNM // Image format .ppm and .pgm -#if (defined(SUPPORT_FILEFORMAT_BMP) || defined(SUPPORT_FILEFORMAT_PNG) || defined(SUPPORT_FILEFORMAT_TGA) || \ - defined(SUPPORT_FILEFORMAT_JPG) || defined(SUPPORT_FILEFORMAT_PSD) || defined(SUPPORT_FILEFORMAT_GIF) || \ +#if (defined(SUPPORT_FILEFORMAT_BMP) || \ + defined(SUPPORT_FILEFORMAT_PNG) || \ + defined(SUPPORT_FILEFORMAT_TGA) || \ + defined(SUPPORT_FILEFORMAT_JPG) || \ + defined(SUPPORT_FILEFORMAT_PSD) || \ + defined(SUPPORT_FILEFORMAT_GIF) || \ + defined(SUPPORT_FILEFORMAT_PIC) || \ defined(SUPPORT_FILEFORMAT_HDR)) + + #define STBI_MALLOC RL_MALLOC + #define STBI_FREE RL_FREE + #define STBI_REALLOC(p,newsz) realloc(p,newsz) + #define STB_IMAGE_IMPLEMENTATION - #include "external/stb_image.h" // Required for: stbi_load_from_file() - // NOTE: Used to read image data (multiple formats support) + #include "external/stb_image.h" // Required for: stbi_load_from_file() + // NOTE: Used to read image data (multiple formats support) #endif #if defined(SUPPORT_IMAGE_EXPORT) @@ -117,6 +133,11 @@ // NOTE: Used for image scaling on ImageResize() #endif +#if defined(SUPPORT_IMAGE_GENERATION) + #define STB_PERLIN_IMPLEMENTATION + #include "external/stb_perlin.h" // Required for: stb_perlin_fbm_noise3 +#endif + //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- @@ -140,6 +161,9 @@ //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- +#if defined(SUPPORT_FILEFORMAT_GIF) +static Image LoadAnimatedGIF(const char *fileName, int *frames, int **delays); // Load animated GIF file +#endif #if defined(SUPPORT_FILEFORMAT_DDS) static Image LoadDDS(const char *fileName); // Load DDS file #endif @@ -147,7 +171,7 @@ static Image LoadDDS(const char *fileName); // Load DDS file static Image LoadPKM(const char *fileName); // Load PKM file #endif #if defined(SUPPORT_FILEFORMAT_KTX) -static Image LoadKTX(const char *fileName); // Load KTX file +static Image LoadKTX(const char *fileName); // Load KTX file static int SaveKTX(Image image, const char *fileName); // Save image data as KTX file #endif #if defined(SUPPORT_FILEFORMAT_PVR) @@ -166,7 +190,20 @@ Image LoadImage(const char *fileName) { Image image = { 0 }; +#if defined(SUPPORT_FILEFORMAT_PNG) || \ + defined(SUPPORT_FILEFORMAT_BMP) || \ + defined(SUPPORT_FILEFORMAT_TGA) || \ + defined(SUPPORT_FILEFORMAT_GIF) || \ + defined(SUPPORT_FILEFORMAT_PIC) || \ + defined(SUPPORT_FILEFORMAT_PSD) +#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 @@ -176,14 +213,18 @@ Image LoadImage(const char *fileName) #if defined(SUPPORT_FILEFORMAT_JPG) || (IsFileExtension(fileName, ".jpg")) #endif -#if defined(SUPPORT_FILEFORMAT_DDS) +#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 ) { +#if defined(STBI_REQUIRED) int imgWidth = 0; int imgHeight = 0; int imgBpp = 0; @@ -192,7 +233,7 @@ Image LoadImage(const char *fileName) if (imFile != NULL) { - // NOTE: Using stb_image to load images (Supports: BMP, TGA, PNG, JPG, ...) + // NOTE: Using stb_image to load images (Supports multiple image formats) image.data = stbi_load_from_file(imFile, &imgWidth, &imgHeight, &imgBpp, 0); fclose(imFile); @@ -206,6 +247,7 @@ Image LoadImage(const char *fileName) else if (imgBpp == 3) image.format = UNCOMPRESSED_R8G8B8; else if (imgBpp == 4) image.format = UNCOMPRESSED_R8G8B8A8; } +#endif } #if defined(SUPPORT_FILEFORMAT_HDR) else if (IsFileExtension(fileName, ".hdr")) @@ -214,13 +256,10 @@ Image LoadImage(const char *fileName) FILE *imFile = fopen(fileName, "rb"); - stbi_set_flip_vertically_on_load(true); - // Load 32 bit per channel floats data + //stbi_set_flip_vertically_on_load(true); image.data = stbi_loadf_from_file(imFile, &image.width, &image.height, &imgBpp, 0); - stbi_set_flip_vertically_on_load(false); - fclose(imFile); image.mipmaps = 1; @@ -271,7 +310,7 @@ Image LoadImageEx(Color *pixels, int width, int height) int k = 0; - image.data = (unsigned char *)malloc(image.width*image.height*4*sizeof(unsigned char)); + image.data = (unsigned char *)RL_MALLOC(image.width*image.height*4*sizeof(unsigned char)); for (int i = 0; i < image.width*image.height*4; i += 4) { @@ -319,7 +358,7 @@ Image LoadImageRaw(const char *fileName, int width, int height, int format, int unsigned int size = GetPixelDataSize(width, height, format); - image.data = malloc(size); // Allocate required memory in bytes + image.data = RL_MALLOC(size); // Allocate required memory in bytes // NOTE: fread() returns num read elements instead of bytes, // to get bytes we need to read (1 byte size, elements) instead of (x byte size, 1 element) @@ -330,7 +369,7 @@ Image LoadImageRaw(const char *fileName, int width, int height, int format, int { TraceLog(LOG_WARNING, "[%s] RAW image data can not be read, wrong requested format or size", fileName); - if (image.data != NULL) free(image.data); + RL_FREE(image.data); } else { @@ -369,7 +408,11 @@ Texture2D LoadTextureFromImage(Image image) { Texture2D texture = { 0 }; - texture.id = rlLoadTexture(image.data, image.width, image.height, image.format, image.mipmaps); + if ((image.data != NULL) && (image.width != 0) && (image.height != 0)) + { + texture.id = rlLoadTexture(image.data, image.width, image.height, image.format, image.mipmaps); + } + else TraceLog(LOG_WARNING, "Texture could not be loaded from Image"); texture.width = image.width; texture.height = image.height; @@ -380,9 +423,10 @@ Texture2D LoadTextureFromImage(Image image) } // Load texture for rendering (framebuffer) +// 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); + RenderTexture2D target = rlLoadRenderTexture(width, height, UNCOMPRESSED_R8G8B8A8, 24, false); return target; } @@ -390,10 +434,7 @@ RenderTexture2D LoadRenderTexture(int width, int height) // Unload image from CPU memory (RAM) void UnloadImage(Image image) { - if (image.data != NULL) free(image.data); - - // NOTE: It becomes anoying every time a texture is loaded - //TraceLog(LOG_INFO, "Unloaded image data"); + RL_FREE(image.data); } // Unload texture from GPU memory (VRAM) @@ -416,8 +457,10 @@ void UnloadRenderTexture(RenderTexture2D target) // Get pixel data from image in the form of Color struct array Color *GetImageData(Image image) { - Color *pixels = (Color *)malloc(image.width*image.height*sizeof(Color)); + Color *pixels = (Color *)RL_MALLOC(image.width*image.height*sizeof(Color)); + if (pixels == NULL) return pixels; + if (image.format >= COMPRESSED_DXT1_RGB) TraceLog(LOG_WARNING, "Pixel data retrieval not supported for compressed image formats"); else { @@ -510,7 +553,7 @@ Color *GetImageData(Image image) pixels[i].a = 255; k += 3; - } + } break; case UNCOMPRESSED_R32G32B32A32: { pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); @@ -519,7 +562,7 @@ Color *GetImageData(Image image) pixels[i].a = (unsigned char)(((float *)image.data)[k]*255.0f); k += 4; - } + } break; default: break; } } @@ -531,7 +574,7 @@ Color *GetImageData(Image image) // Get pixel data from image as Vector4 array (float normalized) Vector4 *GetImageDataNormalized(Image image) { - Vector4 *pixels = (Vector4 *)malloc(image.width*image.height*sizeof(Vector4)); + Vector4 *pixels = (Vector4 *)RL_MALLOC(image.width*image.height*sizeof(Vector4)); if (image.format >= COMPRESSED_DXT1_RGB) TraceLog(LOG_WARNING, "Pixel data retrieval not supported for compressed image formats"); else @@ -564,7 +607,7 @@ Vector4 *GetImageDataNormalized(Image image) pixels[i].x = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31); pixels[i].y = (float)((pixel & 0b0000011111000000) >> 6)*(1.0f/31); pixels[i].z = (float)((pixel & 0b0000000000111110) >> 1)*(1.0f/31); - pixels[i].w = ((pixel & 0b0000000000000001) == 0) ? 0.0f : 1.0f; + pixels[i].w = ((pixel & 0b0000000000000001) == 0)? 0.0f : 1.0f; } break; case UNCOMPRESSED_R5G6B5: @@ -621,7 +664,7 @@ Vector4 *GetImageDataNormalized(Image image) pixels[i].w = 1.0f; k += 3; - } + } break; case UNCOMPRESSED_R32G32B32A32: { pixels[i].x = ((float *)image.data)[k]; @@ -639,6 +682,42 @@ Vector4 *GetImageDataNormalized(Image image) return pixels; } +// Get image alpha border rectangle +Rectangle GetImageAlphaBorder(Image image, float threshold) +{ + Rectangle crop = { 0 }; + + Color *pixels = GetImageData(image); + + if (pixels != NULL) + { + int xMin = 65536; // Define a big enough number + int xMax = 0; + int yMin = 65536; + int yMax = 0; + + for (int y = 0; y < image.height; y++) + { + for (int x = 0; x < image.width; x++) + { + if (pixels[y*image.width + x].a > (unsigned char)(threshold*255.0f)) + { + if (x < xMin) xMin = x; + if (x > xMax) xMax = x; + if (y < yMin) yMin = y; + if (y > yMax) yMax = y; + } + } + } + + crop = (Rectangle){ xMin, yMin, (xMax + 1) - xMin, (yMax + 1) - yMin }; + + RL_FREE(pixels); + } + + return crop; +} + // Get pixel data size in bytes (image or texture) // NOTE: Size depends on pixel format int GetPixelDataSize(int width, int height, int format) @@ -708,6 +787,20 @@ Image GetTextureData(Texture2D texture) return image; } +// Get pixel data from GPU frontbuffer and return an Image (screenshot) +Image GetScreenData(void) +{ + Image image = { 0 }; + + image.width = GetScreenWidth(); + image.height = GetScreenHeight(); + image.mipmaps = 1; + image.format = UNCOMPRESSED_R8G8B8A8; + image.data = rlReadScreenPixels(image.width, image.height); + + return image; +} + // Update GPU texture with new data // NOTE: pixels data must match texture.format void UpdateTexture(Texture2D texture, const void *pixels) @@ -721,14 +814,27 @@ void ExportImage(Image image, const char *fileName) { int success = 0; +#if defined(SUPPORT_IMAGE_EXPORT) // NOTE: Getting Color array as RGBA unsigned char values unsigned char *imgData = (unsigned char *)GetImageData(image); +#if defined(SUPPORT_FILEFORMAT_PNG) if (IsFileExtension(fileName, ".png")) success = stbi_write_png(fileName, image.width, image.height, 4, imgData, image.width*4); +#else + if (false) {} +#endif +#if defined(SUPPORT_FILEFORMAT_BMP) else if (IsFileExtension(fileName, ".bmp")) success = stbi_write_bmp(fileName, image.width, image.height, 4, imgData); +#endif +#if defined(SUPPORT_FILEFORMAT_TGA) else if (IsFileExtension(fileName, ".tga")) success = stbi_write_tga(fileName, image.width, image.height, 4, imgData); +#endif +#if defined(SUPPORT_FILEFORMAT_JPG) else if (IsFileExtension(fileName, ".jpg")) success = stbi_write_jpg(fileName, image.width, image.height, 4, imgData, 80); // JPG quality: between 1 and 100 +#endif +#if defined(SUPPORT_FILEFORMAT_KTX) else if (IsFileExtension(fileName, ".ktx")) success = SaveKTX(image, fileName); +#endif else if (IsFileExtension(fileName, ".raw")) { // Export raw pixel data (without header) @@ -738,10 +844,11 @@ void ExportImage(Image image, const char *fileName) fclose(rawFile); } + RL_FREE(imgData); +#endif + if (success != 0) TraceLog(LOG_INFO, "Image exported successfully: %s", fileName); else TraceLog(LOG_WARNING, "Image could not be exported."); - - free(imgData); } // Export image as code file (.h) defining an array of bytes @@ -749,37 +856,40 @@ void ExportImageAsCode(Image image, const char *fileName) { #define BYTES_TEXT_PER_LINE 20 - char varFileName[256] = { 0 }; - int dataSize = GetPixelDataSize(image.width, image.height, image.format); - FILE *txtFile = fopen(fileName, "wt"); - fprintf(txtFile, "\n//////////////////////////////////////////////////////////////////////////////////////\n"); - fprintf(txtFile, "// //\n"); - fprintf(txtFile, "// ImageAsCode exporter v1.0 - Image pixel data exported as an array of bytes //\n"); - fprintf(txtFile, "// //\n"); - fprintf(txtFile, "// more info and bugs-report: github.com/raysan5/raylib //\n"); - fprintf(txtFile, "// feedback and support: ray[at]raylib.com //\n"); - fprintf(txtFile, "// //\n"); - fprintf(txtFile, "// Copyright (c) 2018 Ramon Santamaria (@raysan5) //\n"); - fprintf(txtFile, "// //\n"); - fprintf(txtFile, "////////////////////////////////////////////////////////////////////////////////////////\n\n"); + if (txtFile != NULL) + { + char varFileName[256] = { 0 }; + int dataSize = GetPixelDataSize(image.width, image.height, image.format); - // Get file name from path and convert variable name to uppercase - strcpy(varFileName, GetFileNameWithoutExt(fileName)); - for (int i = 0; varFileName[i] != '\0'; i++) if (varFileName[i] >= 'a' && varFileName[i] <= 'z') { varFileName[i] = varFileName[i] - 32; } + fprintf(txtFile, "////////////////////////////////////////////////////////////////////////////////////////\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "// ImageAsCode exporter v1.0 - Image pixel data exported as an array of bytes //\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "// more info and bugs-report: github.com/raysan5/raylib //\n"); + fprintf(txtFile, "// feedback and support: ray[at]raylib.com //\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "// Copyright (c) 2019 Ramon Santamaria (@raysan5) //\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "////////////////////////////////////////////////////////////////////////////////////////\n\n"); - // Add image information - fprintf(txtFile, "// Image data information\n"); - fprintf(txtFile, "#define %s_WIDTH %i\n", varFileName, image.width); - fprintf(txtFile, "#define %s_HEIGHT %i\n", varFileName, image.height); - fprintf(txtFile, "#define %s_FORMAT %i // raylib internal pixel format\n\n", varFileName, image.format); + // Get file name from path and convert variable name to uppercase + strcpy(varFileName, GetFileNameWithoutExt(fileName)); + for (int i = 0; varFileName[i] != '\0'; i++) if ((varFileName[i] >= 'a') && (varFileName[i] <= 'z')) { varFileName[i] = varFileName[i] - 32; } - fprintf(txtFile, "static unsigned char %s_DATA[%i] = { ", varFileName, dataSize); - for (int i = 0; i < dataSize - 1; i++) fprintf(txtFile, ((i%BYTES_TEXT_PER_LINE == 0) ? "0x%x,\n" : "0x%x, "), ((unsigned char *)image.data)[i]); - fprintf(txtFile, "0x%x };\n", ((unsigned char *)image.data)[dataSize - 1]); + // Add image information + fprintf(txtFile, "// Image data information\n"); + fprintf(txtFile, "#define %s_WIDTH %i\n", varFileName, image.width); + fprintf(txtFile, "#define %s_HEIGHT %i\n", varFileName, image.height); + fprintf(txtFile, "#define %s_FORMAT %i // raylib internal pixel format\n\n", varFileName, image.format); - fclose(txtFile); + fprintf(txtFile, "static unsigned char %s_DATA[%i] = { ", varFileName, dataSize); + for (int i = 0; i < dataSize - 1; i++) fprintf(txtFile, ((i%BYTES_TEXT_PER_LINE == 0)? "0x%x,\n" : "0x%x, "), ((unsigned char *)image.data)[i]); + fprintf(txtFile, "0x%x };\n", ((unsigned char *)image.data)[dataSize - 1]); + + fclose(txtFile); + } } // Copy an image to a new image @@ -803,7 +913,7 @@ Image ImageCopy(Image image) if (height < 1) height = 1; } - newImage.data = malloc(size); + newImage.data = RL_MALLOC(size); if (newImage.data != NULL) { @@ -819,10 +929,25 @@ Image ImageCopy(Image image) return newImage; } +// Create an image from another image piece +Image ImageFromImage(Image image, Rectangle rec) +{ + Image result = ImageCopy(image); + +#if defined(SUPPORT_IMAGE_MANIPULATION) + ImageCrop(&result, rec); +#endif + + return result; +} + // Convert image to POT (power-of-two) // NOTE: It could be useful on OpenGL ES 2.0 (RPI, HTML5) void ImageToPOT(Image *image, Color fillColor) { + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + Color *pixels = GetImageData(*image); // Get pixels data // Calculate next power-of-two values @@ -836,7 +961,7 @@ void ImageToPOT(Image *image, Color fillColor) Color *pixelsPOT = NULL; // Generate POT array from NPOT data - pixelsPOT = (Color *)malloc(potWidth*potHeight*sizeof(Color)); + pixelsPOT = (Color *)RL_MALLOC(potWidth*potHeight*sizeof(Color)); for (int j = 0; j < potHeight; j++) { @@ -849,15 +974,15 @@ void ImageToPOT(Image *image, Color fillColor) TraceLog(LOG_WARNING, "Image converted to POT: (%ix%i) -> (%ix%i)", image->width, image->height, potWidth, potHeight); - free(pixels); // Free pixels data - free(image->data); // Free old image data + RL_FREE(pixels); // Free pixels data + RL_FREE(image->data); // Free old image data int format = image->format; // Store image data format to reconvert later // NOTE: Image size changes, new width and height *image = LoadImageEx(pixelsPOT, potWidth, potHeight); - free(pixelsPOT); // Free POT pixels data + RL_FREE(pixelsPOT); // Free POT pixels data ImageFormat(image, format); // Reconvert image to previous format } @@ -866,13 +991,16 @@ void ImageToPOT(Image *image, Color fillColor) // Convert image data to desired format void ImageFormat(Image *image, int newFormat) { + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + if ((newFormat != 0) && (image->format != newFormat)) { if ((image->format < COMPRESSED_DXT1_RGB) && (newFormat < COMPRESSED_DXT1_RGB)) { Vector4 *pixels = GetImageDataNormalized(*image); // Supports 8 to 32 bit per channel - free(image->data); // WARNING! We loose mipmaps data --> Regenerated at the end... + RL_FREE(image->data); // WARNING! We loose mipmaps data --> Regenerated at the end... image->data = NULL; image->format = newFormat; @@ -882,7 +1010,7 @@ void ImageFormat(Image *image, int newFormat) { case UNCOMPRESSED_GRAYSCALE: { - image->data = (unsigned char *)malloc(image->width*image->height*sizeof(unsigned char)); + image->data = (unsigned char *)RL_MALLOC(image->width*image->height*sizeof(unsigned char)); for (int i = 0; i < image->width*image->height; i++) { @@ -892,7 +1020,7 @@ void ImageFormat(Image *image, int newFormat) } break; case UNCOMPRESSED_GRAY_ALPHA: { - image->data = (unsigned char *)malloc(image->width*image->height*2*sizeof(unsigned char)); + image->data = (unsigned char *)RL_MALLOC(image->width*image->height*2*sizeof(unsigned char)); for (int i = 0; i < image->width*image->height*2; i += 2, k++) { @@ -903,7 +1031,7 @@ void ImageFormat(Image *image, int newFormat) } break; case UNCOMPRESSED_R5G6B5: { - image->data = (unsigned short *)malloc(image->width*image->height*sizeof(unsigned short)); + image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); unsigned char r = 0; unsigned char g = 0; @@ -921,7 +1049,7 @@ void ImageFormat(Image *image, int newFormat) } break; case UNCOMPRESSED_R8G8B8: { - image->data = (unsigned char *)malloc(image->width*image->height*3*sizeof(unsigned char)); + image->data = (unsigned char *)RL_MALLOC(image->width*image->height*3*sizeof(unsigned char)); for (int i = 0, k = 0; i < image->width*image->height*3; i += 3, k++) { @@ -934,7 +1062,7 @@ void ImageFormat(Image *image, int newFormat) { #define ALPHA_THRESHOLD 50 - image->data = (unsigned short *)malloc(image->width*image->height*sizeof(unsigned short)); + image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); unsigned char r = 0; unsigned char g = 0; @@ -946,7 +1074,7 @@ 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)ALPHA_THRESHOLD/255.0f)) ? 1 : 0; + a = (pixels[i].w > ((float)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; } @@ -954,7 +1082,7 @@ void ImageFormat(Image *image, int newFormat) } break; case UNCOMPRESSED_R4G4B4A4: { - image->data = (unsigned short *)malloc(image->width*image->height*sizeof(unsigned short)); + image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); unsigned char r = 0; unsigned char g = 0; @@ -974,7 +1102,7 @@ void ImageFormat(Image *image, int newFormat) } break; case UNCOMPRESSED_R8G8B8A8: { - image->data = (unsigned char *)malloc(image->width*image->height*4*sizeof(unsigned char)); + image->data = (unsigned char *)RL_MALLOC(image->width*image->height*4*sizeof(unsigned char)); for (int i = 0, k = 0; i < image->width*image->height*4; i += 4, k++) { @@ -988,7 +1116,7 @@ void ImageFormat(Image *image, int newFormat) { // WARNING: Image is converted to GRAYSCALE eqeuivalent 32bit - image->data = (float *)malloc(image->width*image->height*sizeof(float)); + image->data = (float *)RL_MALLOC(image->width*image->height*sizeof(float)); for (int i = 0; i < image->width*image->height; i++) { @@ -997,7 +1125,7 @@ void ImageFormat(Image *image, int newFormat) } break; case UNCOMPRESSED_R32G32B32: { - image->data = (float *)malloc(image->width*image->height*3*sizeof(float)); + image->data = (float *)RL_MALLOC(image->width*image->height*3*sizeof(float)); for (int i = 0, k = 0; i < image->width*image->height*3; i += 3, k++) { @@ -1008,7 +1136,7 @@ void ImageFormat(Image *image, int newFormat) } break; case UNCOMPRESSED_R32G32B32A32: { - image->data = (float *)malloc(image->width*image->height*4*sizeof(float)); + image->data = (float *)RL_MALLOC(image->width*image->height*4*sizeof(float)); for (int i = 0, k = 0; i < image->width*image->height*4; i += 4, k++) { @@ -1021,7 +1149,7 @@ void ImageFormat(Image *image, int newFormat) default: break; } - free(pixels); + RL_FREE(pixels); pixels = NULL; // In case original image had mipmaps, generate mipmaps for formated image @@ -1029,7 +1157,9 @@ void ImageFormat(Image *image, int newFormat) if (image->mipmaps > 1) { image->mipmaps = 1; + #if defined(SUPPORT_IMAGE_MANIPULATION) if (image->data != NULL) ImageMipmaps(image); + #endif } } else TraceLog(LOG_WARNING, "Image data format is compressed, can not be converted"); @@ -1058,13 +1188,18 @@ void ImageAlphaMask(Image *image, Image alphaMask) // In case image is only grayscale, we just add alpha channel if (image->format == UNCOMPRESSED_GRAYSCALE) { - ImageFormat(image, UNCOMPRESSED_GRAY_ALPHA); + unsigned char *data = (unsigned char *)RL_MALLOC(image->width*image->height*2); // Apply alpha mask to alpha channel - for (int i = 0, k = 1; (i < mask.width*mask.height) || (i < image->width*image->height); i++, k += 2) + for (int i = 0, k = 0; (i < mask.width*mask.height) || (i < image->width*image->height); i++, k += 2) { - ((unsigned char *)image->data)[k] = ((unsigned char *)mask.data)[i]; + data[k] = ((unsigned char *)image->data)[i]; + data[k + 1] = ((unsigned char *)mask.data)[i]; } + + RL_FREE(image->data); + image->data = data; + image->format = UNCOMPRESSED_GRAY_ALPHA; } else { @@ -1086,6 +1221,9 @@ void ImageAlphaMask(Image *image, Image alphaMask) // NOTE: Threshold defines the alpha limit, 0.0f to 1.0f 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; + Color *pixels = GetImageData(*image); for (int i = 0; i < image->width*image->height; i++) if (pixels[i].a <= (unsigned char)(threshold*255.0f)) pixels[i] = color; @@ -1098,54 +1236,12 @@ void ImageAlphaClear(Image *image, Color color, float threshold) ImageFormat(image, prevFormat); } -// Crop image depending on alpha value -void ImageAlphaCrop(Image *image, float threshold) -{ - Rectangle crop = { 0 }; - - Color *pixels = GetImageData(*image); - - int minx = 0; - int miny = 0; - - for (int i = 0; i < image->width*image->height; i++) - { - if (pixels[i].a > (unsigned char)(threshold*255.0f)) - { - minx = i%image->width; - miny = -(-((i/image->width) + 1) + 1); - - if (crop.y == 0.0f) crop.y = (float)miny; - - if (crop.x == 0.0f) crop.x = (float)minx; - else if (minx < crop.x) crop.x = (float)minx; - - if (crop.width == 0.0f) crop.width = (float)minx; - else if (crop.width < minx) crop.width = (float)minx; - - if (crop.height == 0.0f) crop.height = (float)miny; - else if (crop.height < (float)miny) crop.height = (float)miny; - } - } - - crop.width -= (crop.x - 1); - crop.height -= (crop.y - 1); - - TraceLog(LOG_INFO, "Crop rectangle: (%i, %i, %i, %i)", crop.x, crop.y, crop.width, crop.height); - - free(pixels); - - // NOTE: Added this weird check to avoid additional 1px crop to - // image data that has already been cropped... - if ((crop.x != 1) && - (crop.y != 1) && - (crop.width != image->width - 1) && - (crop.height != image->height - 1)) ImageCrop(image, crop); -} - // Premultiply alpha channel void ImageAlphaPremultiply(Image *image) { + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + float alpha = 0.0f; Color *pixels = GetImageData(*image); @@ -1165,31 +1261,107 @@ void ImageAlphaPremultiply(Image *image) ImageFormat(image, prevFormat); } - - #if defined(SUPPORT_IMAGE_MANIPULATION) +// Load cubemap from image, multiple image cubemap layouts supported +TextureCubemap LoadTextureCubemap(Image image, int layoutType) +{ + TextureCubemap cubemap = { 0 }; + + if (layoutType == CUBEMAP_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; } + } + 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; } + } + + cubemap.height = cubemap.width; + } + + if (layoutType != CUBEMAP_AUTO_DETECT) + { + int size = cubemap.width; + + Image faces = { 0 }; // Vertical column image + Rectangle faceRecs[6] = { 0 }; // Face source rectangles + for (int i = 0; i < 6; i++) faceRecs[i] = (Rectangle){ 0, 0, size, size }; + + if (layoutType == CUBEMAP_LINE_VERTICAL) + { + faces = image; + for (int i = 0; i < 6; i++) faceRecs[i].y = size*i; + } + else if (layoutType == CUBEMAP_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 = size*i; + else if (layoutType == CUBEMAP_CROSS_THREE_BY_FOUR) + { + faceRecs[0].x = size; faceRecs[0].y = size; + faceRecs[1].x = size; faceRecs[1].y = 3*size; + faceRecs[2].x = size; faceRecs[2].y = 0; + faceRecs[3].x = size; faceRecs[3].y = 2*size; + faceRecs[4].x = 0; faceRecs[4].y = size; + faceRecs[5].x = 2*size; faceRecs[5].y = size; + } + else if (layoutType == CUBEMAP_CROSS_FOUR_BY_THREE) + { + faceRecs[0].x = 2*size; faceRecs[0].y = size; + faceRecs[1].x = 0; faceRecs[1].y = size; + faceRecs[2].x = size; faceRecs[2].y = 0; + faceRecs[3].x = size; faceRecs[3].y = 2*size; + faceRecs[4].x = size; faceRecs[4].y = size; + faceRecs[5].x = 3*size; faceRecs[5].y = size; + } + + // Convert image data to 6 faces in a vertical column, that's the optimum layout for loading + faces = GenImageColor(size, size*6, MAGENTA); + ImageFormat(&faces, image.format); + + // TODO: Image formating does not work with compressed textures! + } + + for (int i = 0; i < 6; i++) ImageDraw(&faces, image, faceRecs[i], (Rectangle){ 0, size*i, size, size }, WHITE); + + cubemap.id = rlLoadTextureCubemap(faces.data, size, faces.format); + if (cubemap.id == 0) TraceLog(LOG_WARNING, "Cubemap image could not be loaded."); + + UnloadImage(faces); + } + else TraceLog(LOG_WARNING, "Cubemap image layout can not be detected."); + + return cubemap; +} + // Crop an image to area defined by a rectangle // NOTE: Security checks are performed in case rectangle goes out of bounds void ImageCrop(Image *image, Rectangle crop) { - // Security checks to make sure cropping rectangle is inside margins - if ((crop.x + crop.width) > image->width) - { - crop.width = image->width - crop.x; - TraceLog(LOG_WARNING, "Crop rectangle width out of bounds, rescaled crop width: %i", crop.width); - } + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - if ((crop.y + crop.height) > image->height) - { - crop.height = image->height - crop.y; - TraceLog(LOG_WARNING, "Crop rectangle height out of bounds, rescaled crop height: %i", crop.height); - } + // Security checks to validate crop rectangle + if (crop.x < 0) { crop.width += crop.x; crop.x = 0; } + 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)) { // Start the cropping process Color *pixels = GetImageData(*image); // Get data as Color pixels array - Color *cropPixels = (Color *)malloc((int)crop.width*(int)crop.height*sizeof(Color)); + Color *cropPixels = (Color *)RL_MALLOC((int)crop.width*(int)crop.height*sizeof(Color)); for (int j = (int)crop.y; j < (int)(crop.y + crop.height); j++) { @@ -1199,7 +1371,7 @@ void ImageCrop(Image *image, Rectangle crop) } } - free(pixels); + RL_FREE(pixels); int format = image->format; @@ -1207,15 +1379,47 @@ void ImageCrop(Image *image, Rectangle crop) *image = LoadImageEx(cropPixels, (int)crop.width, (int)crop.height); - free(cropPixels); + RL_FREE(cropPixels); // Reformat 32bit RGBA image to original format ImageFormat(image, format); } - else + else TraceLog(LOG_WARNING, "Image can not be cropped, crop rectangle out of bounds"); +} + +// Crop image depending on alpha value +void ImageAlphaCrop(Image *image, float threshold) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + Color *pixels = GetImageData(*image); + + int xMin = 65536; // Define a big enough number + int xMax = 0; + int yMin = 65536; + int yMax = 0; + + for (int y = 0; y < image->height; y++) { - TraceLog(LOG_WARNING, "Image can not be cropped, crop rectangle out of bounds"); + for (int x = 0; x < image->width; x++) + { + if (pixels[y*image->width + x].a > (unsigned char)(threshold*255.0f)) + { + if (x < xMin) xMin = x; + if (x > xMax) xMax = x; + if (y < yMin) yMin = y; + if (y > yMax) yMax = y; + } + } } + + Rectangle crop = { xMin, yMin, (xMax + 1) - xMin, (yMax + 1) - yMin }; + + RL_FREE(pixels); + + // Check for not empty image brefore cropping + if (!((xMax < xMin) || (yMax < yMin))) ImageCrop(image, crop); } // Resize and image to new size @@ -1224,9 +1428,12 @@ void ImageCrop(Image *image, Rectangle crop) // STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL (high-quality Catmull-Rom) 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; + // Get data as Color pixels array to work with it Color *pixels = GetImageData(*image); - Color *output = (Color *)malloc(newWidth*newHeight*sizeof(Color)); + 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); @@ -1238,15 +1445,18 @@ void ImageResize(Image *image, int newWidth, int newHeight) *image = LoadImageEx(output, newWidth, newHeight); ImageFormat(image, format); // Reformat 32bit RGBA image to original format - free(output); - free(pixels); + RL_FREE(output); + RL_FREE(pixels); } // Resize and image to new size using Nearest-Neighbor scaling algorithm 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 *output = (Color *)malloc(newWidth*newHeight*sizeof(Color)); + Color *output = (Color *)RL_MALLOC(newWidth*newHeight*sizeof(Color)); // EDIT: added +1 to account for an early rounding problem int xRatio = (int)((image->width << 16)/newWidth) + 1; @@ -1271,30 +1481,85 @@ void ImageResizeNN(Image *image,int newWidth,int newHeight) *image = LoadImageEx(output, newWidth, newHeight); ImageFormat(image, format); // Reformat 32bit RGBA image to original format - free(output); - free(pixels); + RL_FREE(output); + RL_FREE(pixels); } // Resize canvas and fill with color // NOTE: Resize offset is relative to the top-left corner of the original image -void ImageResizeCanvas(Image *image, int newWidth,int newHeight, int offsetX, int offsetY, Color color) +void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color color) { - Image imTemp = GenImageColor(newWidth, newHeight, color); - Rectangle srcRec = { 0.0f, 0.0f, (float)image->width, (float)image->height }; - Rectangle dstRec = { (float)offsetX, (float)offsetY, (float)srcRec.width, (float)srcRec.height }; + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - // TODO: Review different scaling situations + if ((newWidth != image->width) || (newHeight != image->height)) + { + // Support offsets out of canvas new size -> original image is cropped + if (offsetX < 0) + { + ImageCrop(image, (Rectangle) { -offsetX, 0, image->width + offsetX, image->height }); + offsetX = 0; + } + else if (offsetX > (newWidth - image->width)) + { + ImageCrop(image, (Rectangle) { 0, 0, image->width - (offsetX - (newWidth - image->width)), image->height }); + offsetX = newWidth - image->width; + } - if ((newWidth > image->width) && (newHeight > image->height)) - { - ImageDraw(&imTemp, *image, srcRec, dstRec); - ImageFormat(&imTemp, image->format); - UnloadImage(*image); - *image = imTemp; - } - else - { - // TODO: ImageCrop(), define proper cropping rectangle + if (offsetY < 0) + { + ImageCrop(image, (Rectangle) { 0, -offsetY, image->width, image->height + offsetY }); + offsetY = 0; + } + else if (offsetY > (newHeight - image->height)) + { + ImageCrop(image, (Rectangle) { 0, 0, image->width, image->height - (offsetY - (newHeight - image->height)) }); + offsetY = newHeight - image->height; + } + + if ((newWidth > image->width) && (newHeight > image->height)) + { + Image imTemp = GenImageColor(newWidth, newHeight, color); + + Rectangle srcRec = { 0.0f, 0.0f, (float)image->width, (float)image->height }; + Rectangle dstRec = { (float)offsetX, (float)offsetY, srcRec.width, srcRec.height }; + + ImageDraw(&imTemp, *image, srcRec, dstRec, WHITE); + ImageFormat(&imTemp, image->format); + UnloadImage(*image); + *image = imTemp; + } + else if ((newWidth < image->width) && (newHeight < image->height)) + { + Rectangle crop = { (float)offsetX, (float)offsetY, (float)newWidth, (float)newHeight }; + ImageCrop(image, crop); + } + else // One side is bigger and the other is smaller + { + Image imTemp = GenImageColor(newWidth, newHeight, color); + + Rectangle srcRec = { 0.0f, 0.0f, (float)image->width, (float)image->height }; + Rectangle dstRec = { (float)offsetX, (float)offsetY, (float)image->width, (float)image->height }; + + if (newWidth < image->width) + { + srcRec.x = offsetX; + srcRec.width = newWidth; + dstRec.x = 0.0f; + } + + if (newHeight < image->height) + { + srcRec.y = offsetY; + srcRec.height = newHeight; + dstRec.y = 0.0f; + } + + ImageDraw(&imTemp, *image, srcRec, dstRec, WHITE); + ImageFormat(&imTemp, image->format); + UnloadImage(*image); + *image = imTemp; + } } } @@ -1304,6 +1569,9 @@ void ImageResizeCanvas(Image *image, int newWidth,int newHeight, int offsetX, in // NOTE 3: Mipmaps format is the same as base image void ImageMipmaps(Image *image) { + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + int mipCount = 1; // Required mipmap levels count (including base level) int mipWidth = image->width; // Base image width int mipHeight = image->height; // Base image height @@ -1378,13 +1646,16 @@ void ImageMipmaps(Image *image) // dithered data is stored in the LSB part of the unsigned short 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) { TraceLog(LOG_WARNING, "Compressed data formats can not be dithered"); return; } - if ((rBpp+gBpp+bBpp+aBpp) > 16) + if ((rBpp + gBpp + bBpp + aBpp) > 16) { TraceLog(LOG_WARNING, "Unsupported dithering bpps (%ibpp), only 16bpp or lower modes supported", (rBpp+gBpp+bBpp+aBpp)); } @@ -1392,7 +1663,7 @@ void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp) { Color *pixels = GetImageData(*image); - free(image->data); // free old image data + RL_FREE(image->data); // free old image data if ((image->format != UNCOMPRESSED_R8G8B8) && (image->format != UNCOMPRESSED_R8G8B8A8)) { @@ -1410,7 +1681,7 @@ void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp) } // NOTE: We will store the dithered data as unsigned short (16bpp) - image->data = (unsigned short *)malloc(image->width*image->height*sizeof(unsigned short)); + image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); Color oldPixel = WHITE; Color newPixel = WHITE; @@ -1478,7 +1749,7 @@ void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp) } } - free(pixels); + RL_FREE(pixels); } } @@ -1489,7 +1760,7 @@ Color *ImageExtractPalette(Image image, int maxPaletteSize, int *extractCount) #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 *)malloc(maxPaletteSize*sizeof(Color)); + 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 @@ -1520,13 +1791,13 @@ Color *ImageExtractPalette(Image image, int maxPaletteSize, int *extractCount) if (palCount >= maxPaletteSize) { i = image.width*image.height; // Finish palette get - printf("WARNING: Image palette is greater than %i colors!\n", maxPaletteSize); + TraceLog(LOG_WARNING, "Image palette is greater than %i colors!", maxPaletteSize); } } } } - free(pixels); + RL_FREE(pixels); *extractCount = palCount; @@ -1534,10 +1805,12 @@ Color *ImageExtractPalette(Image image, int maxPaletteSize, int *extractCount) } // Draw an image (source) within an image (destination) -// TODO: Feel this function could be simplified... -void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec) +// NOTE: Color tint is applied to source image +void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint) { - bool cropRequired = false; + // Security check to avoid program crash + if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0) || + (src.data == NULL) || (src.width == 0) || (src.height == 0)) return; // Security checks to avoid size and rectangle issues (out of bounds) // Check that srcRec is inside src image @@ -1557,47 +1830,55 @@ void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec) } Image srcCopy = ImageCopy(src); // Make a copy of source image to work with it - ImageCrop(&srcCopy, srcRec); // Crop source image to desired source rectangle - // Check that dstRec is inside dst image - // TODO: Allow negative position within destination with cropping - if (dstRec.x < 0) dstRec.x = 0; - if (dstRec.y < 0) dstRec.y = 0; + // Crop source image to desired source rectangle (if required) + if ((src.width != (int)srcRec.width) && (src.height != (int)srcRec.height)) ImageCrop(&srcCopy, srcRec); // Scale source image in case destination rec size is different than source rec size - if ((dstRec.width != srcRec.width) || (dstRec.height != srcRec.height)) ImageResize(&srcCopy, (int)dstRec.width, (int)dstRec.height); + if (((int)dstRec.width != (int)srcRec.width) || ((int)dstRec.height != (int)srcRec.height)) + { + ImageResize(&srcCopy, (int)dstRec.width, (int)dstRec.height); + } + + // Check that dstRec is inside dst image + // Allow negative position within destination with cropping + if (dstRec.x < 0) + { + ImageCrop(&srcCopy, (Rectangle) { -dstRec.x, 0, dstRec.width + dstRec.x, dstRec.height }); + dstRec.width = dstRec.width + dstRec.x; + dstRec.x = 0; + } if ((dstRec.x + dstRec.width) > dst->width) { + ImageCrop(&srcCopy, (Rectangle) { 0, 0, dst->width - dstRec.x, dstRec.height }); dstRec.width = dst->width - dstRec.x; - TraceLog(LOG_WARNING, "Destination rectangle width out of bounds, rescaled width: %i", dstRec.width); - cropRequired = true; + } + + if (dstRec.y < 0) + { + ImageCrop(&srcCopy, (Rectangle) { 0, -dstRec.y, dstRec.width, dstRec.height + dstRec.y }); + dstRec.height = dstRec.height + dstRec.y; + dstRec.y = 0; } if ((dstRec.y + dstRec.height) > dst->height) { + ImageCrop(&srcCopy, (Rectangle) { 0, 0, dstRec.width, dst->height - dstRec.y }); dstRec.height = dst->height - dstRec.y; - TraceLog(LOG_WARNING, "Destination rectangle height out of bounds, rescaled height: %i", dstRec.height); - cropRequired = true; - } - - if (cropRequired) - { - // Crop destination rectangle if out of bounds - Rectangle crop = { 0, 0, dstRec.width, dstRec.height }; - ImageCrop(&srcCopy, crop); } // Get image data as Color pixels array to work with it Color *dstPixels = GetImageData(*dst); Color *srcPixels = GetImageData(srcCopy); - UnloadImage(srcCopy); // Source copy not required any more... + UnloadImage(srcCopy); // Source copy not required any more - Vector4 fsrc, fdst, fout; // float based versions of pixel data + Vector4 fsrc, fdst, fout; // Normalized pixel data (ready for operation) + Vector4 ftint = ColorNormalize(tint); // Normalized color tint // Blit pixels, copy source image into destination - // TODO: Probably out-of-bounds blitting could be considered here instead of so much cropping... + // TODO: Maybe out-of-bounds blitting could be considered here instead of so much cropping for (int j = (int)dstRec.y; j < (int)(dstRec.y + dstRec.height); j++) { for (int i = (int)dstRec.x; i < (int)(dstRec.x + dstRec.width); i++) @@ -1607,6 +1888,9 @@ void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec) fdst = ColorNormalize(dstPixels[j*(int)dst->width + i]); fsrc = ColorNormalize(srcPixels[(j - (int)dstRec.y)*(int)dstRec.width + (i - (int)dstRec.x)]); + // Apply color tint to source image + fsrc.x *= ftint.x; fsrc.y *= ftint.y; fsrc.z *= ftint.z; fsrc.w *= ftint.w; + fout.w = fsrc.w + fdst.w*(1.0f - fsrc.w); if (fout.w <= 0.0f) @@ -1631,13 +1915,13 @@ void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec) } } - UnloadImage(*dst); // NOTE: Only dst->data is unloaded + UnloadImage(*dst); *dst = LoadImageEx(dstPixels, (int)dst->width, (int)dst->height); ImageFormat(dst, dst->format); - free(srcPixels); - free(dstPixels); + RL_FREE(srcPixels); + RL_FREE(dstPixels); } // Create an image from text (default font) @@ -1656,66 +1940,44 @@ Image ImageText(const char *text, int fontSize, Color color) Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint) { int length = strlen(text); - int posX = 0; + int index; // Index position in sprite font - unsigned char character; // Current character + int letter = 0; // Current character + int positionX = 0; // Image drawing position - // TODO: ISSUE: Measured text size does not seem to be correct... issue on ImageDraw() + // NOTE: Text image is generated at font base size, later scaled to desired font size Vector2 imSize = MeasureTextEx(font, text, (float)font.baseSize, spacing); - TraceLog(LOG_DEBUG, "Text Image size: %f, %f", imSize.x, imSize.y); - - // NOTE: glGetTexImage() not available in OpenGL ES - // TODO: This is horrible, retrieving font texture from GPU!!! - // Define ImageFont struct? or include Image spritefont in Font struct? - Image imFont = GetTextureData(font.texture); - - ImageFormat(&imFont, UNCOMPRESSED_R8G8B8A8); // Make sure image format could be properly colored! - - ImageColorTint(&imFont, tint); // Apply color tint to font - // Create image to store text Image imText = GenImageColor((int)imSize.x, (int)imSize.y, BLANK); for (int i = 0; i < length; i++) { - if ((unsigned char)text[i] == '\n') + int next = 0; + letter = GetNextCodepoint(&text[i], &next); + index = GetGlyphIndex(font, letter); + + if (letter == 0x3f) next = 1; + i += (next - 1); + + if (letter == '\n') { // TODO: Support line break } else { - if ((unsigned char)text[i] == 0xc2) // UTF-8 encoding identification HACK! + if (letter != ' ') { - // Support UTF-8 encoded values from [0xc2 0x80] -> [0xc2 0xbf](¿) - character = (unsigned char)text[i + 1]; - index = GetGlyphIndex(font, (int)character); - i++; - } - else if ((unsigned char)text[i] == 0xc3) // UTF-8 encoding identification HACK! - { - // Support UTF-8 encoded values from [0xc3 0x80](À) -> [0xc3 0xbf](ÿ) - character = (unsigned char)text[i + 1]; - index = GetGlyphIndex(font, (int)character + 64); - i++; - } - else index = GetGlyphIndex(font, (unsigned char)text[i]); - - CharInfo letter = font.chars[index]; - - if ((unsigned char)text[i] != ' ') - { - ImageDraw(&imText, imFont, letter.rec, (Rectangle){ (float)(posX + letter.offsetX), - (float)letter.offsetY, (float)letter.rec.width, (float)letter.rec.height }); + ImageDraw(&imText, font.chars[index].image, (Rectangle){ 0, 0, font.chars[index].image.width, font.chars[index].image.height }, + (Rectangle){ (float)(positionX + font.chars[index].offsetX),(float)font.chars[index].offsetY, + font.chars[index].image.width, font.chars[index].image.height }, tint); } - if (letter.advanceX == 0) posX += (int)(letter.rec.width + spacing); - else posX += letter.advanceX + (int)spacing; + if (font.chars[index].advanceX == 0) positionX += (int)(font.recs[index].width + spacing); + else positionX += font.chars[index].advanceX + (int)spacing; } } - UnloadImage(imFont); - // Scale image depending on text size if (fontSize > imSize.y) { @@ -1733,8 +1995,11 @@ Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Co // Draw rectangle within an image void ImageDrawRectangle(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); + ImageDraw(dst, imRec, (Rectangle){ 0, 0, rec.width, rec.height }, rec, WHITE); UnloadImage(imRec); } @@ -1744,7 +2009,7 @@ void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color) ImageDrawRectangle(dst, (Rectangle){ rec.x, rec.y, rec.width, thick }, color); ImageDrawRectangle(dst, (Rectangle){ rec.x, rec.y + thick, thick, rec.height - thick*2 }, color); ImageDrawRectangle(dst, (Rectangle){ rec.x + rec.width - thick, rec.y + thick, thick, rec.height - thick*2 }, color); - ImageDrawRectangle(dst, (Rectangle){ rec.x, rec.height - thick, rec.width, thick }, color); + ImageDrawRectangle(dst, (Rectangle){ rec.x, rec.y + rec.height - thick, rec.width, thick }, color); } // Draw text (default font) within an image (destination) @@ -1762,7 +2027,7 @@ void ImageDrawTextEx(Image *dst, Vector2 position, Font font, const char *text, Rectangle srcRec = { 0.0f, 0.0f, (float)imText.width, (float)imText.height }; Rectangle dstRec = { position.x, position.y, (float)imText.width, (float)imText.height }; - ImageDraw(dst, imText, srcRec, dstRec); + ImageDraw(dst, imText, srcRec, dstRec, WHITE); UnloadImage(imText); } @@ -1770,8 +2035,11 @@ void ImageDrawTextEx(Image *dst, Vector2 position, Font font, const char *text, // Flip image vertically void ImageFlipVertical(Image *image) { + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + Color *srcPixels = GetImageData(*image); - Color *dstPixels = (Color *)malloc(image->width*image->height*sizeof(Color)); + Color *dstPixels = (Color *)RL_MALLOC(image->width*image->height*sizeof(Color)); for (int y = 0; y < image->height; y++) { @@ -1785,8 +2053,8 @@ void ImageFlipVertical(Image *image) ImageFormat(&processed, image->format); UnloadImage(*image); - free(srcPixels); - free(dstPixels); + RL_FREE(srcPixels); + RL_FREE(dstPixels); image->data = processed.data; } @@ -1794,8 +2062,11 @@ void ImageFlipVertical(Image *image) // Flip image horizontally void ImageFlipHorizontal(Image *image) { + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + Color *srcPixels = GetImageData(*image); - Color *dstPixels = (Color *)malloc(image->width*image->height*sizeof(Color)); + Color *dstPixels = (Color *)RL_MALLOC(image->width*image->height*sizeof(Color)); for (int y = 0; y < image->height; y++) { @@ -1809,8 +2080,8 @@ void ImageFlipHorizontal(Image *image) ImageFormat(&processed, image->format); UnloadImage(*image); - free(srcPixels); - free(dstPixels); + RL_FREE(srcPixels); + RL_FREE(dstPixels); image->data = processed.data; } @@ -1818,8 +2089,11 @@ void ImageFlipHorizontal(Image *image) // Rotate image clockwise 90deg void ImageRotateCW(Image *image) { + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + Color *srcPixels = GetImageData(*image); - Color *rotPixels = (Color *)malloc(image->width*image->height*sizeof(Color)); + Color *rotPixels = (Color *)RL_MALLOC(image->width*image->height*sizeof(Color)); for (int y = 0; y < image->height; y++) { @@ -1833,8 +2107,8 @@ void ImageRotateCW(Image *image) ImageFormat(&processed, image->format); UnloadImage(*image); - free(srcPixels); - free(rotPixels); + RL_FREE(srcPixels); + RL_FREE(rotPixels); image->data = processed.data; image->width = processed.width; @@ -1844,8 +2118,11 @@ void ImageRotateCW(Image *image) // Rotate image counter-clockwise 90deg void ImageRotateCCW(Image *image) { + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + Color *srcPixels = GetImageData(*image); - Color *rotPixels = (Color *)malloc(image->width*image->height*sizeof(Color)); + Color *rotPixels = (Color *)RL_MALLOC(image->width*image->height*sizeof(Color)); for (int y = 0; y < image->height; y++) { @@ -1859,8 +2136,8 @@ void ImageRotateCCW(Image *image) ImageFormat(&processed, image->format); UnloadImage(*image); - free(srcPixels); - free(rotPixels); + RL_FREE(srcPixels); + RL_FREE(rotPixels); image->data = processed.data; image->width = processed.width; @@ -1870,6 +2147,9 @@ void ImageRotateCCW(Image *image) // Modify image color: tint 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); float cR = (float)color.r/255; @@ -1897,7 +2177,7 @@ void ImageColorTint(Image *image, Color color) Image processed = LoadImageEx(pixels, image->width, image->height); ImageFormat(&processed, image->format); UnloadImage(*image); - free(pixels); + RL_FREE(pixels); image->data = processed.data; } @@ -1905,6 +2185,9 @@ void ImageColorTint(Image *image, Color color) // Modify image color: invert 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); for (int y = 0; y < image->height; y++) @@ -1920,7 +2203,7 @@ void ImageColorInvert(Image *image) Image processed = LoadImageEx(pixels, image->width, image->height); ImageFormat(&processed, image->format); UnloadImage(*image); - free(pixels); + RL_FREE(pixels); image->data = processed.data; } @@ -1935,6 +2218,9 @@ void ImageColorGrayscale(Image *image) // NOTE: Contrast values between -100 and 100 void ImageColorContrast(Image *image, float contrast) { + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + if (contrast < -100) contrast = -100; if (contrast > 100) contrast = 100; @@ -1980,7 +2266,7 @@ void ImageColorContrast(Image *image, float contrast) Image processed = LoadImageEx(pixels, image->width, image->height); ImageFormat(&processed, image->format); UnloadImage(*image); - free(pixels); + RL_FREE(pixels); image->data = processed.data; } @@ -1989,6 +2275,9 @@ void ImageColorContrast(Image *image, float contrast) // NOTE: Brightness values between -255 and 255 void ImageColorBrightness(Image *image, int brightness) { + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + if (brightness < -255) brightness = -255; if (brightness > 255) brightness = 255; @@ -2020,7 +2309,7 @@ void ImageColorBrightness(Image *image, int brightness) Image processed = LoadImageEx(pixels, image->width, image->height); ImageFormat(&processed, image->format); UnloadImage(*image); - free(pixels); + RL_FREE(pixels); image->data = processed.data; } @@ -2028,6 +2317,9 @@ void ImageColorBrightness(Image *image, int brightness) // Modify image color: replace color 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); for (int y = 0; y < image->height; y++) @@ -2050,31 +2342,31 @@ void ImageColorReplace(Image *image, Color color, Color replace) Image processed = LoadImageEx(pixels, image->width, image->height); ImageFormat(&processed, image->format); UnloadImage(*image); - free(pixels); + RL_FREE(pixels); image->data = processed.data; } #endif // SUPPORT_IMAGE_MANIPULATION -#if defined(SUPPORT_IMAGE_GENERATION) // Generate image: plain color Image GenImageColor(int width, int height, Color color) { - Color *pixels = (Color *)calloc(width*height, sizeof(Color)); + Color *pixels = (Color *)RL_CALLOC(width*height, sizeof(Color)); for (int i = 0; i < width*height; i++) pixels[i] = color; Image image = LoadImageEx(pixels, width, height); - free(pixels); + RL_FREE(pixels); return image; } +#if defined(SUPPORT_IMAGE_GENERATION) // Generate image: vertical gradient Image GenImageGradientV(int width, int height, Color top, Color bottom) { - Color *pixels = (Color *)malloc(width*height*sizeof(Color)); + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); for (int j = 0; j < height; j++) { @@ -2089,7 +2381,7 @@ Image GenImageGradientV(int width, int height, Color top, Color bottom) } Image image = LoadImageEx(pixels, width, height); - free(pixels); + RL_FREE(pixels); return image; } @@ -2097,7 +2389,7 @@ Image GenImageGradientV(int width, int height, Color top, Color bottom) // Generate image: horizontal gradient Image GenImageGradientH(int width, int height, Color left, Color right) { - Color *pixels = (Color *)malloc(width*height*sizeof(Color)); + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); for (int i = 0; i < width; i++) { @@ -2112,7 +2404,7 @@ Image GenImageGradientH(int width, int height, Color left, Color right) } Image image = LoadImageEx(pixels, width, height); - free(pixels); + RL_FREE(pixels); return image; } @@ -2120,8 +2412,8 @@ Image GenImageGradientH(int width, int height, Color left, Color right) // Generate image: radial gradient Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer) { - Color *pixels = (Color *)malloc(width*height*sizeof(Color)); - float radius = (width < height) ? (float)width/2.0f : (float)height/2.0f; + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + float radius = (width < height)? (float)width/2.0f : (float)height/2.0f; float centerX = (float)width/2.0f; float centerY = (float)height/2.0f; @@ -2144,7 +2436,7 @@ Image GenImageGradientRadial(int width, int height, float density, Color inner, } Image image = LoadImageEx(pixels, width, height); - free(pixels); + RL_FREE(pixels); return image; } @@ -2152,7 +2444,7 @@ Image GenImageGradientRadial(int width, int height, float density, Color inner, // Generate image: checked Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2) { - Color *pixels = (Color *)malloc(width*height*sizeof(Color)); + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); for (int y = 0; y < height; y++) { @@ -2164,7 +2456,7 @@ Image GenImageChecked(int width, int height, int checksX, int checksY, Color col } Image image = LoadImageEx(pixels, width, height); - free(pixels); + RL_FREE(pixels); return image; } @@ -2172,7 +2464,7 @@ Image GenImageChecked(int width, int height, int checksX, int checksY, Color col // Generate image: white noise Image GenImageWhiteNoise(int width, int height, float factor) { - Color *pixels = (Color *)malloc(width*height*sizeof(Color)); + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); for (int i = 0; i < width*height; i++) { @@ -2181,7 +2473,7 @@ Image GenImageWhiteNoise(int width, int height, float factor) } Image image = LoadImageEx(pixels, width, height); - free(pixels); + RL_FREE(pixels); return image; } @@ -2189,7 +2481,7 @@ Image GenImageWhiteNoise(int width, int height, float factor) // Generate image: perlin noise Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale) { - Color *pixels = (Color *)malloc(width*height*sizeof(Color)); + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); for (int y = 0; y < height; y++) { @@ -2204,7 +2496,7 @@ Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float // octaves = 6 -- number of "octaves" of noise3() to sum // NOTE: We need to translate the data from [-1..1] to [0..1] - float p = (stb_perlin_fbm_noise3(nx, ny, 1.0f, 2.0f, 0.5f, 6, 0, 0, 0) + 1.0f)/2.0f; + float p = (stb_perlin_fbm_noise3(nx, ny, 1.0f, 2.0f, 0.5f, 6) + 1.0f)/2.0f; int intensity = (int)(p*255.0f); pixels[y*width + x] = (Color){intensity, intensity, intensity, 255}; @@ -2212,7 +2504,7 @@ Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float } Image image = LoadImageEx(pixels, width, height); - free(pixels); + RL_FREE(pixels); return image; } @@ -2220,13 +2512,13 @@ Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float // Generate image: cellular algorithm. Bigger tileSize means bigger cells Image GenImageCellular(int width, int height, int tileSize) { - Color *pixels = (Color *)malloc(width*height*sizeof(Color)); + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); int seedsPerRow = width/tileSize; int seedsPerCol = height/tileSize; int seedsCount = seedsPerRow * seedsPerCol; - Vector2 *seeds = (Vector2 *)malloc(seedsCount*sizeof(Vector2)); + Vector2 *seeds = (Vector2 *)RL_MALLOC(seedsCount*sizeof(Vector2)); for (int i = 0; i < seedsCount; i++) { @@ -2269,10 +2561,10 @@ Image GenImageCellular(int width, int height, int tileSize) } } - free(seeds); + RL_FREE(seeds); Image image = LoadImageEx(pixels, width, height); - free(pixels); + RL_FREE(pixels); return image; } @@ -2367,10 +2659,15 @@ void SetTextureWrap(Texture2D texture, int wrapMode) rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_WRAP_CLAMP); rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_WRAP_CLAMP); } break; - case WRAP_MIRROR: + case WRAP_MIRROR_REPEAT: { - rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_WRAP_CLAMP_MIRROR); - rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_WRAP_CLAMP_MIRROR); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_WRAP_MIRROR_REPEAT); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_WRAP_MIRROR_REPEAT); + } break; + case 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); } break; default: break; } @@ -2407,6 +2704,17 @@ void DrawTextureRec(Texture2D texture, Rectangle sourceRec, Vector2 position, Co DrawTexturePro(texture, sourceRec, destRec, origin, 0.0f, tint); } +// Draw texture quad with tiling and offset parameters +// NOTE: Tiling and offset should be provided considering normalized texture values [0..1] +// i.e tiling = { 1.0f, 1.0f } refers to all texture, offset = { 0.5f, 0.5f } moves texture origin to center +void DrawTextureQuad(Texture2D texture, Vector2 tiling, Vector2 offset, Rectangle quad, Color tint) +{ + Rectangle source = { offset.x*texture.width, offset.y*texture.height, tiling.x*texture.width, tiling.y*texture.height }; + Vector2 origin = { 0.0f, 0.0f }; + + DrawTexturePro(texture, source, quad, origin, 0.0f, tint); +} + // 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) @@ -2418,16 +2726,16 @@ void DrawTexturePro(Texture2D texture, Rectangle sourceRec, Rectangle destRec, V float height = (float)texture.height; bool flipX = false; - + if (sourceRec.width < 0) { flipX = true; sourceRec.width *= -1; } if (sourceRec.height < 0) sourceRec.y -= sourceRec.height; rlEnableTexture(texture.id); rlPushMatrix(); - rlTranslatef(destRec.x, destRec.y, 0); - rlRotatef(rotation, 0, 0, 1); - rlTranslatef(-origin.x, -origin.y, 0); + rlTranslatef(destRec.x, destRec.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); @@ -2459,6 +2767,7 @@ void DrawTexturePro(Texture2D texture, Rectangle sourceRec, Rectangle destRec, V } } +// 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) { if (texture.id > 0) @@ -2519,9 +2828,9 @@ void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle destR rlEnableTexture(texture.id); rlPushMatrix(); - rlTranslatef(destRec.x, destRec.y, 0); - rlRotatef(rotation, 0, 0, 1); - rlTranslatef(-origin.x, -origin.y, 0); + rlTranslatef(destRec.x, destRec.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); @@ -2658,6 +2967,45 @@ void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle destR //---------------------------------------------------------------------------------- // Module specific Functions Definition //---------------------------------------------------------------------------------- +#if defined(SUPPORT_FILEFORMAT_GIF) +// Load animated GIF data +// - Image.data buffer includes all frames: [image#0][image#1][image#2][...] +// - Number of frames is returned through 'frames' parameter +// - Frames delay is returned through 'delays' parameter (int array) +// - All frames are returned in RGBA format +static Image LoadAnimatedGIF(const char *fileName, int *frames, int **delays) +{ + Image image = { 0 }; + + FILE *gifFile = fopen(fileName, "rb"); + + if (gifFile == NULL) + { + TraceLog(LOG_WARNING, "[%s] Animated GIF file could not be opened", fileName); + } + else + { + fseek(gifFile, 0L, SEEK_END); + int size = ftell(gifFile); + fseek(gifFile, 0L, SEEK_SET); + + unsigned char *buffer = (unsigned char *)RL_CALLOC(size, sizeof(char)); + fread(buffer, sizeof(char), size, gifFile); + + fclose(gifFile); // Close file pointer + + int comp = 0; + image.data = stbi_load_gif_from_memory(buffer, size, delays, &image.width, &image.height, frames, &comp, 4); + + image.mipmaps = 1; + image.format = UNCOMPRESSED_R8G8B8A8; + + free(buffer); + } + + return image; +} +#endif #if defined(SUPPORT_FILEFORMAT_DDS) // Loading DDS image data (compressed or uncompressed) @@ -2748,7 +3096,7 @@ static Image LoadDDS(const char *fileName) { if (ddsHeader.ddspf.flags == 0x40) // no alpha channel { - image.data = (unsigned short *)malloc(image.width*image.height*sizeof(unsigned short)); + image.data = (unsigned short *)RL_MALLOC(image.width*image.height*sizeof(unsigned short)); fread(image.data, image.width*image.height*sizeof(unsigned short), 1, ddsFile); image.format = UNCOMPRESSED_R5G6B5; @@ -2757,7 +3105,7 @@ static Image LoadDDS(const char *fileName) { if (ddsHeader.ddspf.aBitMask == 0x8000) // 1bit alpha { - image.data = (unsigned short *)malloc(image.width*image.height*sizeof(unsigned short)); + image.data = (unsigned short *)RL_MALLOC(image.width*image.height*sizeof(unsigned short)); fread(image.data, image.width*image.height*sizeof(unsigned short), 1, ddsFile); unsigned char alpha = 0; @@ -2774,7 +3122,7 @@ static Image LoadDDS(const char *fileName) } else if (ddsHeader.ddspf.aBitMask == 0xf000) // 4bit alpha { - image.data = (unsigned short *)malloc(image.width*image.height*sizeof(unsigned short)); + image.data = (unsigned short *)RL_MALLOC(image.width*image.height*sizeof(unsigned short)); fread(image.data, image.width*image.height*sizeof(unsigned short), 1, ddsFile); unsigned char alpha = 0; @@ -2794,14 +3142,14 @@ static Image LoadDDS(const char *fileName) if (ddsHeader.ddspf.flags == 0x40 && ddsHeader.ddspf.rgbBitCount == 24) // DDS_RGB, no compressed { // NOTE: not sure if this case exists... - image.data = (unsigned char *)malloc(image.width*image.height*3*sizeof(unsigned char)); + image.data = (unsigned char *)RL_MALLOC(image.width*image.height*3*sizeof(unsigned char)); fread(image.data, image.width*image.height*3, 1, ddsFile); image.format = UNCOMPRESSED_R8G8B8; } else if (ddsHeader.ddspf.flags == 0x41 && ddsHeader.ddspf.rgbBitCount == 32) // DDS_RGBA, no compressed { - image.data = (unsigned char *)malloc(image.width*image.height*4*sizeof(unsigned char)); + image.data = (unsigned char *)RL_MALLOC(image.width*image.height*4*sizeof(unsigned char)); fread(image.data, image.width*image.height*4, 1, ddsFile); unsigned char blue = 0; @@ -2828,7 +3176,7 @@ static Image LoadDDS(const char *fileName) TraceLog(LOG_DEBUG, "Pitch or linear size: %i", ddsHeader.pitchOrLinearSize); - image.data = (unsigned char *)malloc(size*sizeof(unsigned char)); + image.data = (unsigned char *)RL_MALLOC(size*sizeof(unsigned char)); fread(image.data, size, 1, ddsFile); @@ -2925,7 +3273,7 @@ static Image LoadPKM(const char *fileName) int size = image.width*image.height*bpp/8; // Total data size in bytes - image.data = (unsigned char *)malloc(size*sizeof(unsigned char)); + image.data = (unsigned char *)RL_MALLOC(size*sizeof(unsigned char)); fread(image.data, size, 1, pkmFile); @@ -3019,7 +3367,7 @@ static Image LoadKTX(const char *fileName) int dataSize; fread(&dataSize, sizeof(unsigned int), 1, ktxFile); - image.data = (unsigned char *)malloc(dataSize*sizeof(unsigned char)); + image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char)); fread(image.data, dataSize, 1, ktxFile); @@ -3072,25 +3420,27 @@ static int SaveKTX(Image image, const char *fileName) { KTXHeader ktxHeader; - // KTX identifier (v2.2) + // KTX identifier (v1.1) //unsigned char id[12] = { '«', 'K', 'T', 'X', ' ', '1', '1', '»', '\r', '\n', '\x1A', '\n' }; //unsigned char id[12] = { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; + const char ktxIdentifier[12] = { 0xAB, 'K', 'T', 'X', ' ', '1', '1', 0xBB, '\r', '\n', 0x1A, '\n' }; + // Get the image header - strcpy(ktxHeader.id, "«KTX 11»\r\n\x1A\n"); // KTX 1.1 signature + strncpy(ktxHeader.id, ktxIdentifier, 12); // KTX 1.1 signature ktxHeader.endianness = 0; - ktxHeader.glType = 0; // Obtained from image.format + ktxHeader.glType = 0; // Obtained from image.format ktxHeader.glTypeSize = 1; - ktxHeader.glFormat = 0; // Obtained from image.format - ktxHeader.glInternalFormat = 0; // Obtained from image.format + ktxHeader.glFormat = 0; // Obtained from image.format + ktxHeader.glInternalFormat = 0; // Obtained from image.format ktxHeader.glBaseInternalFormat = 0; ktxHeader.width = image.width; ktxHeader.height = image.height; ktxHeader.depth = 0; ktxHeader.elements = 0; ktxHeader.faces = 1; - ktxHeader.mipmapLevels = image.mipmaps; // If it was 0, it means mipmaps should be generated on loading (not for compressed formats) - ktxHeader.keyValueDataSize = 0; // No extra data after the header + ktxHeader.mipmapLevels = image.mipmaps; // If it was 0, it means mipmaps should be generated on loading (not for compressed formats) + ktxHeader.keyValueDataSize = 0; // No extra data after the header rlGetGlTextureFormats(image.format, &ktxHeader.glInternalFormat, &ktxHeader.glFormat, &ktxHeader.glType); // rlgl module function ktxHeader.glBaseInternalFormat = ktxHeader.glFormat; // KTX 1.1 only @@ -3266,7 +3616,7 @@ static Image LoadPVR(const char *fileName) } int dataSize = image.width*image.height*bpp/8; // Total data size in bytes - image.data = (unsigned char *)malloc(dataSize*sizeof(unsigned char)); + image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char)); // Read data from file fread(image.data, dataSize, 1, pvrFile); @@ -3343,7 +3693,7 @@ static Image LoadASTC(const char *fileName) { int dataSize = image.width*image.height*bpp/8; // Data size in bytes - image.data = (unsigned char *)malloc(dataSize*sizeof(unsigned char)); + image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char)); fread(image.data, dataSize, 1, astcFile); if (bpp == 8) image.format = COMPRESSED_ASTC_4x4_RGBA; diff --git a/raylib/textures.go b/raylib/textures.go index e2f2dfe..4c76f52 100644 --- a/raylib/textures.go +++ b/raylib/textures.go @@ -299,12 +299,13 @@ func ImageTextEx(font Font, text string, fontSize, spacing float32, tint Color) } // ImageDraw - Draw a source image within a destination image -func ImageDraw(dst, src *Image, srcRec, dstRec Rectangle) { +func ImageDraw(dst, src *Image, srcRec, dstRec Rectangle, tint Color) { cdst := dst.cptr() csrc := src.cptr() csrcRec := srcRec.cptr() cdstRec := dstRec.cptr() - C.ImageDraw(cdst, *csrc, *csrcRec, *cdstRec) + ctint := tint.cptr() + C.ImageDraw(cdst, *csrc, *csrcRec, *cdstRec, *ctint) } // ImageDrawRectangle - Draw rectangle within an image diff --git a/raylib/utils.c b/raylib/utils.c index b295104..8aad9f4 100644 --- a/raylib/utils.c +++ b/raylib/utils.c @@ -11,7 +11,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2018 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2019 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. @@ -30,43 +30,49 @@ * **********************************************************************************************/ -#include "config.h" +#include "raylib.h" // WARNING: Required for: LogType enum + +// Check if config flags have been externally provided on compilation line +#if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags +#endif -#include "raylib.h" // WARNING: Required for: LogType enum #include "utils.h" #if defined(PLATFORM_ANDROID) - #include - #include - #include + #include // Required for: Android error types + #include // Required for: Android log system: __android_log_vprint() + #include // Required for: Android assets manager: AAsset, AAssetManager_open(), ... #endif -#include // Required for: malloc(), free() -#include // Required for: fopen(), fclose(), fputc(), fwrite(), printf(), fprintf(), funopen() -#include // Required for: va_list, va_start(), vfprintf(), va_end() -#include // Required for: strlen(), strrchr(), strcmp() +#include // Required for: exit() +#include // Required for: printf(), sprintf() +#include // Required for: va_list, va_start(), vfprintf(), va_end() +#include // Required for: strcpy(), strcat() -/* This should be in , but Travis doesn't find it... */ -FILE *funopen(const void *cookie, int (*readfn)(void *, char *, int), - int (*writefn)(void *, const char *, int), - fpos_t (*seekfn)(void *, fpos_t, int), int (*closefn)(void *)); +#define MAX_TRACELOG_BUFFER_SIZE 128 // Max length of one trace-log message //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- -// Log types messages supported flags (bit based) -static unsigned char logTypeFlags = LOG_INFO | LOG_WARNING | LOG_ERROR; +// Log types messages +static int logTypeLevel = LOG_INFO; +static int logTypeExit = LOG_ERROR; static TraceLogCallback logCallback = NULL; #if defined(PLATFORM_ANDROID) -AAssetManager *assetManager; +AAssetManager *assetManager = NULL; #endif //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- #if defined(PLATFORM_ANDROID) +// This should be in , but Travis does not find it... +FILE *funopen(const void *cookie, int (*readfn)(void *, char *, int), int (*writefn)(void *, const char *, int), + fpos_t (*seekfn)(void *, fpos_t, int), int (*closefn)(void *)); + static int android_read(void *cookie, char *buf, int size); static int android_write(void *cookie, const char *buf, int size); static fpos_t android_seek(void *cookie, fpos_t offset, int whence); @@ -77,82 +83,78 @@ static int android_close(void *cookie); // Module Functions Definition - Utilities //---------------------------------------------------------------------------------- -// Enable trace log message types (bit flags based) -void SetTraceLog(unsigned char types) +// Set the current threshold (minimum) log level +void SetTraceLogLevel(int logType) { - logTypeFlags = types; + logTypeLevel = logType; } -// Set a trace log callback to enable custom logging bypassing raylib's one +// 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; } // Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG) -void TraceLog(int msgType, const char *text, ...) +void TraceLog(int logType, const char *text, ...) { #if defined(SUPPORT_TRACELOG) - static char buffer[128]; + // Message has level below current threshold, don't emit + if (logType < logTypeLevel) return; + va_list args; va_start(args, text); if (logCallback) { - logCallback(msgType, text, args); + logCallback(logType, text, args); va_end(args); return; } - switch(msgType) +#if defined(PLATFORM_ANDROID) + switch(logType) { - case LOG_INFO: strcpy(buffer, "INFO: "); break; - case LOG_ERROR: strcpy(buffer, "ERROR: "); break; - case LOG_WARNING: strcpy(buffer, "WARNING: "); break; + case LOG_TRACE: __android_log_vprint(ANDROID_LOG_VERBOSE, "raylib", text, args); break; + case LOG_DEBUG: __android_log_vprint(ANDROID_LOG_DEBUG, "raylib", text, args); break; + case LOG_INFO: __android_log_vprint(ANDROID_LOG_INFO, "raylib", text, args); break; + case LOG_WARNING: __android_log_vprint(ANDROID_LOG_WARN, "raylib", text, args); break; + case LOG_ERROR: __android_log_vprint(ANDROID_LOG_ERROR, "raylib", text, args); break; + case LOG_FATAL: __android_log_vprint(ANDROID_LOG_FATAL, "raylib", text, args); break; + default: break; + } +#else + char buffer[MAX_TRACELOG_BUFFER_SIZE] = { 0 }; + + switch (logType) + { + case LOG_TRACE: strcpy(buffer, "TRACE: "); break; case LOG_DEBUG: strcpy(buffer, "DEBUG: "); break; + case LOG_INFO: strcpy(buffer, "INFO: "); break; + case LOG_WARNING: strcpy(buffer, "WARNING: "); break; + case LOG_ERROR: strcpy(buffer, "ERROR: "); break; + case LOG_FATAL: strcpy(buffer, "FATAL: "); break; default: break; } strcat(buffer, text); strcat(buffer, "\n"); - -#if defined(PLATFORM_ANDROID) - switch(msgType) - { - case LOG_INFO: if (logTypeFlags & LOG_INFO) __android_log_vprint(ANDROID_LOG_INFO, "raylib", buffer, args); break; - case LOG_WARNING: if (logTypeFlags & LOG_WARNING) __android_log_vprint(ANDROID_LOG_WARN, "raylib", buffer, args); break; - case LOG_ERROR: if (logTypeFlags & LOG_ERROR) __android_log_vprint(ANDROID_LOG_ERROR, "raylib", buffer, args); break; - case LOG_DEBUG: if (logTypeFlags & LOG_DEBUG) __android_log_vprint(ANDROID_LOG_DEBUG, "raylib", buffer, args); break; - default: break; - } -#else - switch(msgType) - { - case LOG_INFO: if (logTypeFlags & LOG_INFO) vprintf(buffer, args); break; - case LOG_WARNING: if (logTypeFlags & LOG_WARNING) vprintf(buffer, args); break; - case LOG_ERROR: if (logTypeFlags & LOG_ERROR) vprintf(buffer, args); break; - case LOG_DEBUG: if (logTypeFlags & LOG_DEBUG) vprintf(buffer, args); break; - default: break; - } + vprintf(buffer, args); #endif va_end(args); - if (msgType == LOG_ERROR) exit(1); // If LOG_ERROR message, exit program + if (logType >= logTypeExit) exit(1); // If exit message, exit program #endif // SUPPORT_TRACELOG } -// Keep track of memory allocated -// NOTE: mallocType defines the type of data allocated -/* -void RecordMalloc(int mallocType, int mallocSize, const char *msg) -{ - // TODO: Investigate how to record memory allocation data... - // Maybe creating my own malloc function... -} -*/ - #if defined(PLATFORM_ANDROID) // Initialize asset manager from android app void InitAssetManager(AAssetManager *manager) @@ -171,7 +173,7 @@ FILE *android_fopen(const char *fileName, const char *mode) return funopen(asset, android_read, android_write, android_seek, android_close); } -#endif +#endif // PLATFORM_ANDROID //---------------------------------------------------------------------------------- // Module specific Functions Definition @@ -199,4 +201,79 @@ static int android_close(void *cookie) AAsset_close((AAsset *)cookie); return 0; } -#endif +#endif // PLATFORM_ANDROID + +#if defined(PLATFORM_UWP) + +#define MAX_MESSAGES 512 // If there are over 128 messages, I will cry... either way, this may be too much EDIT: Welp, 512 + +static int UWPOutMessageId = -1; // Stores the last index for the message +static UWPMessage* UWPOutMessages[MAX_MESSAGES]; // Messages out to UWP + +static int UWPInMessageId = -1; // Stores the last index for the message +static UWPMessage* UWPInMessages[MAX_MESSAGES]; // Messages in from UWP + +UWPMessage* CreateUWPMessage(void) +{ + UWPMessage *msg = (UWPMessage *)RL_MALLOC(sizeof(UWPMessage)); + msg->type = UWP_MSG_NONE; + Vector2 v0 = { 0, 0 }; + msg->paramVector0 = v0; + msg->paramInt0 = 0; + msg->paramInt1 = 0; + msg->paramChar0 = 0; + msg->paramFloat0 = 0; + msg->paramDouble0 = 0; + msg->paramBool0 = false; + return msg; +} + +void DeleteUWPMessage(UWPMessage *msg) +{ + RL_FREE(msg); +} + +bool UWPHasMessages(void) +{ + return (UWPOutMessageId > -1); +} + +UWPMessage *UWPGetMessage(void) +{ + if (UWPHasMessages()) return UWPOutMessages[UWPOutMessageId--]; + + return NULL; +} + +void UWPSendMessage(UWPMessage *msg) +{ + if (UWPInMessageId + 1 < MAX_MESSAGES) + { + UWPInMessageId++; + UWPInMessages[UWPInMessageId] = msg; + } + else TraceLog(LOG_WARNING, "[UWP Messaging] Not enough array space to register new UWP inbound Message."); +} + +void SendMessageToUWP(UWPMessage *msg) +{ + if (UWPOutMessageId + 1 < MAX_MESSAGES) + { + UWPOutMessageId++; + UWPOutMessages[UWPOutMessageId] = msg; + } + else TraceLog(LOG_WARNING, "[UWP Messaging] Not enough array space to register new UWP outward Message."); +} + +bool HasMessageFromUWP(void) +{ + return UWPInMessageId > -1; +} + +UWPMessage* GetMessageFromUWP(void) +{ + if (HasMessageFromUWP()) return UWPInMessages[UWPInMessageId--]; + + return NULL; +} +#endif // PLATFORM_UWP diff --git a/raylib/utils.go b/raylib/utils.go index 4a32702..56cfad9 100644 --- a/raylib/utils.go +++ b/raylib/utils.go @@ -12,12 +12,12 @@ import ( "os" ) -// SetTraceLog - Enable trace log message types (bit flags based) +// SetTraceLog - Enable trace log message types func SetTraceLog(typeFlags int) { logTypeFlags = typeFlags - ctypeFlags := (C.uchar)(typeFlags) - C.SetTraceLog(ctypeFlags) + ctypeFlags := (C.int)(typeFlags) + C.SetTraceLogLevel(ctypeFlags) } // TraceLog - Show trace log messages (INFO, WARNING, ERROR, DEBUG) diff --git a/raylib/utils.h b/raylib/utils.h index 08b3396..98c2f59 100644 --- a/raylib/utils.h +++ b/raylib/utils.h @@ -5,7 +5,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2018 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2019 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. @@ -36,7 +36,7 @@ // Some basic Defines //---------------------------------------------------------------------------------- #if defined(PLATFORM_ANDROID) - #define fopen(name, mode) android_fopen(name, mode) + #define fopen(name, mode) android_fopen(name, mode) #endif //---------------------------------------------------------------------------------- @@ -59,6 +59,65 @@ void InitAssetManager(AAssetManager *manager); // Initialize asset manager from FILE *android_fopen(const char *fileName, const char *mode); // Replacement for fopen() #endif +#if defined(PLATFORM_UWP) +// UWP Messages System +typedef enum { + UWP_MSG_NONE = 0, + + // Send + UWP_MSG_SHOW_MOUSE, + UWP_MSG_HIDE_MOUSE, + UWP_MSG_LOCK_MOUSE, + UWP_MSG_UNLOCK_MOUSE, + UWP_MSG_SET_MOUSE_LOCATION, // paramVector0 (pos) + + // Receive (Into C) + UWP_MSG_REGISTER_KEY, // paramInt0 (key), paramChar0 (status) + UWP_MSG_REGISTER_CLICK, // paramInt0 (button), paramChar0 (status) + UWP_MSG_SCROLL_WHEEL_UPDATE, // paramInt0 (delta) + UWP_MSG_UPDATE_MOUSE_LOCATION, // paramVector0 (pos) + UWP_MSG_SET_GAMEPAD_ACTIVE, // paramInt0 (gamepad), paramBool0 (active or not) + UWP_MSG_SET_GAMEPAD_BUTTON, // paramInt0 (gamepad), paramInt1 (button), paramChar0 (status) + UWP_MSG_SET_GAMEPAD_AXIS, // paramInt0 (gamepad), int1 (axis), paramFloat0 (value) + UWP_MSG_SET_DISPLAY_DIMS, // paramVector0 (display dimensions) + UWP_MSG_HANDLE_RESIZE, // paramVector0 (new dimensions) - Onresized event + UWP_MSG_SET_GAME_TIME, // paramInt0 +} UWPMessageType; + +typedef struct UWPMessage { + UWPMessageType type; // Message type + + Vector2 paramVector0; // Vector parameters + int paramInt0; // Int parameter + int paramInt1; // Int parameter + char paramChar0; // Char parameters + float paramFloat0; // Float parameters + double paramDouble0; // Double parameters + bool paramBool0; // Bool parameters + + // More parameters can be added and fed to functions +} UWPMessage; + +// Allocate UWP Message +RLAPI UWPMessage* CreateUWPMessage(void); + +// Free UWP Message +RLAPI void DeleteUWPMessage(UWPMessage* msg); + +// Get messages into C++ +RLAPI bool UWPHasMessages(void); +RLAPI UWPMessage* UWPGetMessage(void); +RLAPI void UWPSendMessage(UWPMessage* msg); + +// For C to call +#ifndef __cplusplus // Hide from C++ code +void SendMessageToUWP(UWPMessage* msg); +bool HasMessageFromUWP(void); +UWPMessage* GetMessageFromUWP(void); +#endif + +#endif //defined(PLATFORM_UWP) + #ifdef __cplusplus } #endif