summaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile8
-rw-r--r--src/core.c23
-rw-r--r--src/external/cgltf.h2023
-rw-r--r--src/external/mini_al.h1113
-rw-r--r--src/models.c89
-rw-r--r--src/raylib.h44
-rw-r--r--src/raymath.h17
-rw-r--r--src/rlgl.h78
-rw-r--r--src/shapes.c8
-rw-r--r--src/text.c39
-rw-r--r--src/textures.c4
11 files changed, 2828 insertions, 618 deletions
diff --git a/src/Makefile b/src/Makefile
index 26233c95..ac4b15b4 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -201,7 +201,7 @@ RAYLIB_RELEASE_PATH ?= $(RAYLIB_PATH)/release/libs
# Define output directory for compiled library
ifeq ($(PLATFORM),PLATFORM_DESKTOP)
ifeq ($(PLATFORM_OS),WINDOWS)
- RAYLIB_RELEASE_PATH = $(RAYLIB_PATH)/release/libs/win32/mingw32
+ RAYLIB_RELEASE_PATH = $(RAYLIB_PATH)\release\libs\win32\mingw32
endif
ifeq ($(PLATFORM_OS),LINUX)
RAYLIB_RELEASE_PATH = $(RAYLIB_PATH)/release/libs/linux
@@ -460,7 +460,13 @@ ifeq ($(PLATFORM_OS),LINUX)
endif
# Compile raylib library
+# NOTE: Release directory is created if not exist
raylib: $(OBJS)
+ifeq ($(PLATFORM_OS),WINDOWS)
+ if not exist $(RAYLIB_RELEASE_PATH) mkdir $(RAYLIB_RELEASE_PATH)
+else
+ mkdir -p $(RAYLIB_RELEASE_PATH)
+endif
ifeq ($(PLATFORM),PLATFORM_WEB)
# Compile raylib for web.
emcc -O1 $(OBJS) -o $(RAYLIB_RELEASE_PATH)/libraylib.bc
diff --git a/src/core.c b/src/core.c
index 7363a0c1..3cd75f64 100644
--- a/src/core.c
+++ b/src/core.c
@@ -1346,11 +1346,19 @@ const char *GetExtension(const char *fileName)
return (dot + 1);
}
+// String pointer reverse break: returns right-most occurrence of charset in s
+static const char *strprbrk(const char *s, const char *charset)
+{
+ const char *latestMatch = NULL;
+ for (; s = strpbrk(s, charset), s != NULL; latestMatch = s++) { }
+ return latestMatch;
+}
+
// Get pointer to filename for a path string
const char *GetFileName(const char *filePath)
{
- const char *fileName = strrchr(filePath, '\\');
-
+ const char *fileName = strprbrk(filePath, "\\/");
+
if (!fileName || fileName == filePath) return filePath;
return fileName + 1;
@@ -1360,14 +1368,17 @@ const char *GetFileName(const char *filePath)
// Get directory for a given fileName (with path)
const char *GetDirectoryPath(const char *fileName)
{
- char *lastSlash = NULL;
+ const char *lastSlash = NULL;
static char filePath[256]; // MAX_DIRECTORY_PATH_SIZE = 256
memset(filePath, 0, 256);
-
- lastSlash = strrchr(fileName, '\\');
+
+ lastSlash = strprbrk(fileName, "\\/");
+ if (!lastSlash)
+ return NULL;
+
strncpy(filePath, fileName, strlen(fileName) - (strlen(lastSlash) - 1));
filePath[strlen(fileName) - strlen(lastSlash)] = '\0';
-
+
return filePath;
}
diff --git a/src/external/cgltf.h b/src/external/cgltf.h
new file mode 100644
index 00000000..ed04d545
--- /dev/null
+++ b/src/external/cgltf.h
@@ -0,0 +1,2023 @@
+#ifndef CGLTF_H_INCLUDED__
+#define CGLTF_H_INCLUDED__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+typedef unsigned long cgltf_size;
+typedef float cgltf_float;
+typedef int cgltf_bool;
+
+typedef enum cgltf_file_type
+{
+ cgltf_file_type_invalid,
+ cgltf_file_type_gltf,
+ cgltf_file_type_glb,
+} cgltf_file_type;
+
+typedef struct cgltf_options
+{
+ cgltf_file_type type;
+ cgltf_size json_token_count; /* 0 == auto */
+ void* (*memory_alloc)(void* user, cgltf_size size);
+ void (*memory_free) (void* user, void* ptr);
+ void* memory_user_data;
+} cgltf_options;
+
+typedef enum cgltf_result
+{
+ cgltf_result_success,
+ cgltf_result_data_too_short,
+ cgltf_result_unknown_format,
+ cgltf_result_invalid_json,
+ cgltf_result_invalid_options,
+} cgltf_result;
+
+typedef enum cgltf_buffer_view_type
+{
+ cgltf_buffer_view_type_invalid,
+ cgltf_buffer_view_type_indices,
+ cgltf_buffer_view_type_vertices,
+} cgltf_buffer_view_type;
+
+typedef enum cgltf_attribute_type
+{
+ cgltf_attribute_type_invalid,
+ 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;
+
+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;
+
+typedef enum cgltf_type
+{
+ cgltf_type_invalid,
+ cgltf_type_scalar,
+ cgltf_type_vec2,
+ cgltf_type_vec3,
+ cgltf_type_vec4,
+ cgltf_type_mat2,
+ cgltf_type_mat3,
+ cgltf_type_mat4,
+} 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;
+
+typedef struct cgltf_buffer
+{
+ cgltf_size size;
+ char* uri;
+} cgltf_buffer;
+
+typedef struct cgltf_buffer_view
+{
+ cgltf_buffer* buffer;
+ cgltf_size offset;
+ cgltf_size size;
+ cgltf_size stride; /* 0 == automatically determined by accessor */
+ cgltf_buffer_view_type type;
+} cgltf_buffer_view;
+
+typedef struct cgltf_accessor
+{
+ cgltf_component_type component_type;
+ cgltf_type type;
+ cgltf_size offset;
+ cgltf_size count;
+ cgltf_size stride;
+ cgltf_buffer_view* buffer_view;
+} cgltf_accessor;
+
+typedef struct cgltf_attribute
+{
+ cgltf_attribute_type name;
+ 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* uri;
+ cgltf_buffer_view* buffer_view;
+ char* mime_type;
+} cgltf_image;
+
+typedef struct cgltf_sampler
+{
+ cgltf_float mag_filter;
+ cgltf_float min_filter;
+ cgltf_float wrap_s;
+ cgltf_float wrap_t;
+} cgltf_sampler;
+
+typedef struct cgltf_texture
+{
+ cgltf_image* image;
+ cgltf_sampler* sampler;
+} cgltf_texture;
+
+typedef struct cgltf_texture_view
+{
+ cgltf_texture* texture;
+ cgltf_size texcoord;
+ cgltf_float scale;
+} cgltf_texture_view;
+
+typedef struct cgltf_pbr
+{
+ cgltf_texture_view base_color_texture;
+ cgltf_texture_view metallic_roughness_texture;
+
+ cgltf_rgba base_color;
+ cgltf_float metallic_factor;
+ cgltf_float roughness_factor;
+} cgltf_pbr;
+
+typedef struct cgltf_material
+{
+ char* name;
+ cgltf_pbr pbr;
+ cgltf_rgba emissive_color;
+ cgltf_texture_view normal_texture;
+ cgltf_texture_view emissive_texture;
+ cgltf_texture_view occlusion_texture;
+ cgltf_bool double_sided;
+} cgltf_material;
+
+typedef struct cgltf_primitive {
+ cgltf_primitive_type type;
+ cgltf_accessor* indices;
+ cgltf_material* material;
+ cgltf_attribute* attributes;
+ cgltf_size attributes_count;
+} cgltf_primitive;
+
+typedef struct cgltf_mesh {
+ char* name;
+ cgltf_primitive* primitives;
+ cgltf_size primitives_count;
+} cgltf_mesh;
+
+typedef struct cgltf_data
+{
+ unsigned version;
+ cgltf_file_type file_type;
+
+ cgltf_mesh* meshes;
+ cgltf_size meshes_count;
+
+ cgltf_material* materials;
+ cgltf_size materials_count;
+
+ cgltf_accessor* accessors;
+ cgltf_size accessors_count;
+
+ cgltf_buffer_view* buffer_views;
+ cgltf_size buffer_views_count;
+
+ cgltf_buffer* buffers;
+ cgltf_size buffers_count;
+
+ cgltf_image* images;
+ cgltf_size images_count;
+
+ cgltf_texture* textures;
+ cgltf_size textures_count;
+
+ cgltf_sampler* samplers;
+ cgltf_size samplers_count;
+
+ const void* bin;
+ cgltf_size bin_size;
+
+ void (*memory_free) (void* user, void* ptr);
+ void* memory_user_data;
+} cgltf_data;
+
+cgltf_result cgltf_parse(
+ const cgltf_options* options,
+ const void* data,
+ cgltf_size size,
+ cgltf_data* out_data);
+
+void cgltf_free(cgltf_data* data);
+
+#endif /* #ifndef CGLTF_H_INCLUDED__ */
+
+/*
+ *
+ * Stop now, if you are only interested in the API.
+ * Below, you find the implementation.
+ *
+ */
+
+#ifdef __INTELLISENSE__
+/* This makes MSVC intellisense work. */
+#define CGLTF_IMPLEMENTATION
+#endif
+
+#ifdef CGLTF_IMPLEMENTATION
+
+#include <stdint.h> /* For uint8_t, uint32_t */
+#include <string.h> /* For strncpy */
+#include <stdlib.h> /* For malloc, free */
+
+
+/*
+ * -- jsmn.h start --
+ * Source: https://github.com/zserge/jsmn
+ * License: MIT
+ */
+typedef enum {
+ JSMN_UNDEFINED = 0,
+ JSMN_OBJECT = 1,
+ JSMN_ARRAY = 2,
+ JSMN_STRING = 3,
+ JSMN_PRIMITIVE = 4
+} jsmntype_t;
+enum jsmnerr {
+ /* Not enough tokens were provided */
+ JSMN_ERROR_NOMEM = -1,
+ /* Invalid character inside JSON string */
+ JSMN_ERROR_INVAL = -2,
+ /* The string is not a full JSON packet, more bytes expected */
+ JSMN_ERROR_PART = -3
+};
+typedef struct {
+ jsmntype_t type;
+ int start;
+ int end;
+ int size;
+#ifdef JSMN_PARENT_LINKS
+ int parent;
+#endif
+} jsmntok_t;
+typedef struct {
+ unsigned int pos; /* offset in the JSON string */
+ 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);
+/*
+ * -- 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 void* cgltf_mem_alloc(void* user, cgltf_size size)
+{
+ return malloc(size);
+}
+
+static void cgltf_mem_free(void* user, void* ptr)
+{
+ 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)
+{
+ if (size < GltfHeaderSize)
+ {
+ return cgltf_result_data_too_short;
+ }
+
+ if (options == NULL)
+ {
+ return cgltf_result_invalid_options;
+ }
+
+ cgltf_options fixed_options = *options;
+ if (fixed_options.memory_alloc == NULL)
+ {
+ fixed_options.memory_alloc = &cgltf_mem_alloc;
+ }
+ if (fixed_options.memory_free == NULL)
+ {
+ fixed_options.memory_free = &cgltf_mem_free;
+ }
+
+ uint32_t tmp;
+ // Magic
+ memcpy(&tmp, data, 4);
+ if (tmp != GltfMagic)
+ {
+ if (fixed_options.type == cgltf_file_type_invalid)
+ {
+ fixed_options.type = cgltf_file_type_gltf;
+ }
+ else
+ {
+ 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);
+ }
+
+ const uint8_t* ptr = (const uint8_t*)data;
+ // Version
+ memcpy(&tmp, ptr + 4, 4);
+ out_data->version = tmp;
+
+ // Total length
+ memcpy(&tmp, ptr + 8, 4);
+ if (tmp > size)
+ {
+ return cgltf_result_data_too_short;
+ }
+
+ const uint8_t* json_chunk = ptr + GltfHeaderSize;
+
+ // JSON chunk: length
+ uint32_t json_length;
+ memcpy(&json_length, json_chunk, 4);
+ if (GltfHeaderSize + GltfChunkHeaderSize + json_length > size)
+ {
+ return cgltf_result_data_too_short;
+ }
+
+ // JSON chunk: magic
+ memcpy(&tmp, json_chunk + 4, 4);
+ if (tmp != GltfMagicJsonChunk)
+ {
+ 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;
+ }
+
+ out_data->file_type = cgltf_file_type_invalid;
+ if (GltfHeaderSize + GltfChunkHeaderSize + json_length + GltfChunkHeaderSize <= size)
+ {
+ // We can read another chunk
+ const uint8_t* bin_chunk = json_chunk + json_length;
+
+ // Bin chunk: length
+ uint32_t bin_length;
+ memcpy(&bin_length, bin_chunk, 4);
+ if (GltfHeaderSize + GltfChunkHeaderSize + json_length + GltfChunkHeaderSize + bin_length > size)
+ {
+ return cgltf_result_data_too_short;
+ }
+
+ // Bin chunk: magic
+ memcpy(&tmp, bin_chunk + 4, 4);
+ if (tmp != GltfMagicBinChunk)
+ {
+ return cgltf_result_unknown_format;
+ }
+
+ bin_chunk += GltfChunkHeaderSize;
+
+ out_data->file_type = cgltf_file_type_glb;
+ out_data->bin = bin_chunk;
+ out_data->bin_size = bin_length;
+ }
+
+ return cgltf_result_success;
+}
+
+void cgltf_free(cgltf_data* data)
+{
+ 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)
+ {
+ 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)
+ {
+ data->memory_free(data->memory_user_data, data->meshes[i].primitives[j].attributes);
+ }
+ data->memory_free(data->memory_user_data, data->meshes[i].primitives);
+ }
+ data->memory_free(data->memory_user_data, data->meshes);
+
+ for (cgltf_size i = 0; i < data->materials_count; ++i)
+ {
+ data->memory_free(data->memory_user_data, data->materials[i].name);
+ }
+
+ data->memory_free(data->memory_user_data, data->materials);
+
+ for (cgltf_size i = 0; i < data->images_count; ++i)
+ {
+ 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')
+ {
+ c = 'a' + (c - 'A');
+ }
+ return c;
+}
+
+static int cgltf_json_strcmp(jsmntok_t const* tok, const uint8_t* json_chunk, const char* str)
+{
+ 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)
+ {
+ for (int i = 0; i < str_len; ++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;
+ }
+ }
+ 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);
+}
+
+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);
+}
+
+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)
+ return 1;
+
+ return 0;
+}
+
+static int cgltf_skip_json(jsmntok_t const* tokens, int i)
+{
+ if (tokens[i].type == JSMN_ARRAY)
+ {
+ int size = tokens[i].size;
+ ++i;
+ for (int j = 0; j < size; ++j)
+ {
+ i = cgltf_skip_json(tokens, i);
+ }
+ }
+ else if (tokens[i].type == JSMN_OBJECT)
+ {
+ int size = tokens[i].size;
+ ++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_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);
+
+ int size = tokens[i].size;
+ ++i;
+
+ out_prim->indices = (void* )-1;
+ out_prim->material = NULL;
+
+ for (int j = 0; j < size; ++j)
+ {
+ if (cgltf_json_strcmp(tokens+i, json_chunk, "mode") == 0)
+ {
+ ++i;
+ out_prim->type
+ = (cgltf_primitive_type)
+ cgltf_json_to_int(tokens+i, json_chunk);
+ ++i;
+ }
+ 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);
+ ++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);
+ ++i;
+ }
+ else if (cgltf_json_strcmp(tokens+i, json_chunk, "attributes") == 0)
+ {
+ ++i;
+ if (tokens[i].type != JSMN_OBJECT)
+ {
+ return -1;
+ }
+ 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)
+ {
+ 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)
+ {
+ out_prim->attributes[iattr].name = cgltf_attribute_type_position;
+ }
+ 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
+ {
+ i = cgltf_skip_json(tokens, i+1);
+ }
+ }
+
+ 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)
+{
+ 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)
+ {
+ 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;
+ }
+ else if (cgltf_json_strcmp(tokens+i, json_chunk, "primitives") == 0)
+ {
+ ++i;
+ if (tokens[i].type != JSMN_ARRAY)
+ {
+ return -1;
+ }
+ 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);
+ ++i;
+
+ for (cgltf_size prim_index = 0;
+ prim_index < out_data->meshes[mesh_index].primitives_count;
+ ++prim_index)
+ {
+ i = cgltf_parse_json_primitive(options, tokens, i, json_chunk,
+ &out_data->meshes[mesh_index].primitives[prim_index]);
+ if (i < 0)
+ {
+ return i;
+ }
+ }
+ }
+ else
+ {
+ 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_accessor(tokens, i, json_chunk, j, out_data);
+ if (i < 0)
+ {
+ return i;
+ }
+ }
+ return i;
+}
+
+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;
+ for (cgltf_size j = 0; j < out_data->materials_count; ++j)
+ {
+ i = cgltf_parse_json_material(options, tokens, i, json_chunk, j, out_data);
+ if (i < 0)
+ {
+ return i;
+ }
+ }
+ 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;
+
+ 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) {
+ 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;
+
+ 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) {
+ 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;
+
+ 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) {
+ 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)
+{
+ 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)
+ {
+ 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);
+ ++i;
+ }
+ else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0)
+ {
+ ++i;
+ out_data->buffer_views[buffer_view_index].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 =
+ 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 =
+ cgltf_json_to_int(tokens+i, json_chunk);
+ ++i;
+ }
+ else if (cgltf_json_strcmp(tokens+i, json_chunk, "target") == 0)
+ {
+ ++i;
+ int type = cgltf_json_to_int(tokens+i, json_chunk);
+ switch (type)
+ {
+ case 34962:
+ type = cgltf_buffer_view_type_vertices;
+ break;
+ case 34963:
+ type = cgltf_buffer_view_type_indices;
+ break;
+ default:
+ type = cgltf_buffer_view_type_invalid;
+ break;
+ }
+ out_data->buffer_views[buffer_view_index].type = type;
+ ++i;
+ }
+ else
+ {
+ i = cgltf_skip_json(tokens, i+1);
+ }
+ }
+
+ return 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_buffer_view(tokens, i, json_chunk, j, out_data);
+ if (i < 0)
+ {
+ return i;
+ }
+ }
+ 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)
+{
+ 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)
+ {
+ if (cgltf_json_strcmp(tokens+i, json_chunk, "byteLength") == 0)
+ {
+ ++i;
+ out_data->buffers[buffer_index].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;
+ }
+ else
+ {
+ i = cgltf_skip_json(tokens, i+1);
+ }
+ }
+
+ return i;
+}
+
+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_buffer(options, tokens, i, json_chunk, j, out_data);
+ if (i < 0)
+ {
+ return i;
+ }
+ }
+ return i;
+}
+
+static cgltf_size cgltf_calc_size(cgltf_type type, cgltf_component_type component_type)
+{
+ cgltf_type size = 0;
+
+ switch (component_type)
+ {
+ 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;
+ }
+
+ switch (type)
+ {
+ case cgltf_type_vec2:
+ size *= 2;
+ break;
+ case cgltf_type_vec3:
+ size *= 3;
+ break;
+ case cgltf_type_vec4:
+ size *= 4;
+ break;
+ case cgltf_type_mat2:
+ size *= 4;
+ break;
+ case cgltf_type_mat3:
+ size *= 9;
+ break;
+ case cgltf_type_mat4:
+ size *= 16;
+ break;
+ case cgltf_type_invalid:
+ case cgltf_type_scalar:
+ default:
+ size *= 1;
+ break;
+ }
+
+ return size;
+}
+
+cgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* json_chunk, cgltf_size size, cgltf_data* out_data)
+{
+ jsmn_parser parser = {0};
+
+ if (options->json_token_count == 0)
+ {
+ options->json_token_count = jsmn_parse(&parser, (const char*)json_chunk, size, NULL, 0);
+ }
+
+ jsmntok_t* tokens = options->memory_alloc(options->memory_user_data, sizeof(jsmntok_t) * options->json_token_count);
+
+ 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)
+ {
+ return cgltf_result_invalid_json;
+ }
+
+ // The root is an object.
+
+ for (int i = 1; i < token_count; )
+ {
+ 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);
+
+ /* Fix up pointers */
+ for (cgltf_size i = 0; i < out_data->meshes_count; ++i)
+ {
+ 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];
+ }
+ }
+ }
+
+ for (cgltf_size i = 0; i < out_data->accessors_count; ++i)
+ {
+ 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);
+ }
+ }
+
+ 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];
+ }
+
+ 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];
+ }
+
+ return cgltf_result_success;
+}
+
+/*
+ * -- jsmn.c start --
+ * Source: https://github.com/zserge/jsmn
+ * License: MIT
+ */
+/**
+ * Allocates a fresh unused token from the token pull.
+ */
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
+ jsmntok_t *tokens, size_t num_tokens) {
+ jsmntok_t *tok;
+ if (parser->toknext >= num_tokens) {
+ return NULL;
+ }
+ tok = &tokens[parser->toknext++];
+ tok->start = tok->end = -1;
+ tok->size = 0;
+#ifdef JSMN_PARENT_LINKS
+ tok->parent = -1;
+#endif
+ return tok;
+}
+
+/**
+ * Fills token type and boundaries.
+ */
+static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
+ int start, int end) {
+ token->type = type;
+ token->start = start;
+ token->end = end;
+ token->size = 0;
+}
+
+/**
+ * Fills next available token with JSON primitive.
+ */
+static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
+ size_t len, jsmntok_t *tokens, size_t num_tokens) {
+ jsmntok_t *token;
+ int start;
+
+ start = parser->pos;
+
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+ switch (js[parser->pos]) {
+#ifndef JSMN_STRICT
+ /* In strict mode primitive must be followed by "," or "}" or "]" */
+ case ':':
+#endif
+ case '\t' : case '\r' : case '\n' : case ' ' :
+ case ',' : case ']' : case '}' :
+ goto found;
+ }
+ if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ }
+#ifdef JSMN_STRICT
+ /* In strict mode primitive must be followed by a comma/object/array */
+ parser->pos = start;
+ return JSMN_ERROR_PART;
+#endif
+
+found:
+ if (tokens == NULL) {
+ parser->pos--;
+ return 0;
+ }
+ token = jsmn_alloc_token(parser, tokens, num_tokens);
+ if (token == NULL) {
+ parser->pos = start;
+ return JSMN_ERROR_NOMEM;
+ }
+ jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+ token->parent = parser->toksuper;
+#endif
+ parser->pos--;
+ return 0;
+}
+
+/**
+ * Fills next token with JSON string.
+ */
+static int jsmn_parse_string(jsmn_parser *parser, const char *js,
+ size_t len, jsmntok_t *tokens, size_t num_tokens) {
+ jsmntok_t *token;
+
+ int start = parser->pos;
+
+ parser->pos++;
+
+ /* Skip starting quote */
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+ char c = js[parser->pos];
+
+ /* Quote: end of string */
+ if (c == '\"') {
+ if (tokens == NULL) {
+ return 0;
+ }
+ token = jsmn_alloc_token(parser, tokens, num_tokens);
+ if (token == NULL) {
+ parser->pos = start;
+ return JSMN_ERROR_NOMEM;
+ }
+ jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+ token->parent = parser->toksuper;
+#endif
+ return 0;
+ }
+
+ /* Backslash: Quoted symbol expected */
+ if (c == '\\' && parser->pos + 1 < len) {
+ int i;
+ parser->pos++;
+ switch (js[parser->pos]) {
+ /* Allowed escaped symbols */
+ case '\"': case '/' : case '\\' : case 'b' :
+ case 'f' : case 'r' : case 'n' : case 't' :
+ break;
+ /* Allows escaped symbol \uXXXX */
+ case 'u':
+ parser->pos++;
+ for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) {
+ /* If it isn't a hex character we have an error */
+ if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
+ (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
+ (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ parser->pos++;
+ }
+ parser->pos--;
+ break;
+ /* Unexpected symbol */
+ default:
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ }
+ }
+ parser->pos = start;
+ return JSMN_ERROR_PART;
+}
+
+/**
+ * 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) {
+ int r;
+ int i;
+ jsmntok_t *token;
+ int count = parser->toknext;
+
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+ char c;
+ jsmntype_t type;
+
+ c = js[parser->pos];
+ switch (c) {
+ case '{': case '[':
+ count++;
+ if (tokens == NULL) {
+ break;
+ }
+ token = jsmn_alloc_token(parser, tokens, num_tokens);
+ if (token == NULL)
+ return JSMN_ERROR_NOMEM;
+ if (parser->toksuper != -1) {
+ tokens[parser->toksuper].size++;
+#ifdef JSMN_PARENT_LINKS
+ token->parent = parser->toksuper;
+#endif
+ }
+ token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
+ token->start = parser->pos;
+ parser->toksuper = parser->toknext - 1;
+ break;
+ case '}': case ']':
+ if (tokens == NULL)
+ break;
+ type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
+#ifdef JSMN_PARENT_LINKS
+ if (parser->toknext < 1) {
+ return JSMN_ERROR_INVAL;
+ }
+ token = &tokens[parser->toknext - 1];
+ for (;;) {
+ if (token->start != -1 && token->end == -1) {
+ if (token->type != type) {
+ return JSMN_ERROR_INVAL;
+ }
+ token->end = parser->pos + 1;
+ parser->toksuper = token->parent;
+ break;
+ }
+ if (token->parent == -1) {
+ if(token->type != type || parser->toksuper == -1) {
+ return JSMN_ERROR_INVAL;
+ }
+ break;
+ }
+ token = &tokens[token->parent];
+ }
+#else
+ for (i = parser->toknext - 1; i >= 0; i--) {
+ token = &tokens[i];
+ if (token->start != -1 && token->end == -1) {
+ if (token->type != type) {
+ return JSMN_ERROR_INVAL;
+ }
+ parser->toksuper = -1;
+ token->end = parser->pos + 1;
+ break;
+ }
+ }
+ /* Error if unmatched closing bracket */
+ if (i == -1) return JSMN_ERROR_INVAL;
+ for (; i >= 0; i--) {
+ token = &tokens[i];
+ if (token->start != -1 && token->end == -1) {
+ parser->toksuper = i;
+ break;
+ }
+ }
+#endif
+ break;
+ case '\"':
+ r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
+ if (r < 0) return r;
+ count++;
+ if (parser->toksuper != -1 && tokens != NULL)
+ tokens[parser->toksuper].size++;
+ break;
+ case '\t' : case '\r' : case '\n' : case ' ':
+ break;
+ case ':':
+ parser->toksuper = parser->toknext - 1;
+ break;
+ case ',':
+ if (tokens != NULL && parser->toksuper != -1 &&
+ tokens[parser->toksuper].type != JSMN_ARRAY &&
+ tokens[parser->toksuper].type != JSMN_OBJECT) {
+#ifdef JSMN_PARENT_LINKS
+ parser->toksuper = tokens[parser->toksuper].parent;
+#else
+ for (i = parser->toknext - 1; i >= 0; i--) {
+ if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
+ if (tokens[i].start != -1 && tokens[i].end == -1) {
+ parser->toksuper = i;
+ break;
+ }
+ }
+ }
+#endif
+ }
+ break;
+#ifdef JSMN_STRICT
+ /* In strict mode primitives are: numbers and booleans */
+ case '-': case '0': case '1' : case '2': case '3' : case '4':
+ case '5': case '6': case '7' : case '8': case '9':
+ case 't': case 'f': case 'n' :
+ /* And they must not be keys of the object */
+ if (tokens != NULL && parser->toksuper != -1) {
+ jsmntok_t *t = &tokens[parser->toksuper];
+ if (t->type == JSMN_OBJECT ||
+ (t->type == JSMN_STRING && t->size != 0)) {
+ return JSMN_ERROR_INVAL;
+ }
+ }
+#else
+ /* In non-strict mode every unquoted value is a primitive */
+ default:
+#endif
+ r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
+ if (r < 0) return r;
+ count++;
+ if (parser->toksuper != -1 && tokens != NULL)
+ tokens[parser->toksuper].size++;
+ break;
+
+#ifdef JSMN_STRICT
+ /* Unexpected char in strict mode */
+ default:
+ return JSMN_ERROR_INVAL;
+#endif
+ }
+ }
+
+ if (tokens != NULL) {
+ for (i = parser->toknext - 1; i >= 0; i--) {
+ /* Unmatched opened object or array */
+ if (tokens[i].start != -1 && tokens[i].end == -1) {
+ return JSMN_ERROR_PART;
+ }
+ }
+ }
+
+ return count;
+}
+
+/**
+ * Creates a new parser based over a given buffer with an array of tokens
+ * available.
+ */
+void jsmn_init(jsmn_parser *parser) {
+ parser->pos = 0;
+ parser->toknext = 0;
+ parser->toksuper = -1;
+}
+/*
+ * -- jsmn.c end --
+ */
+
+#endif /* #ifdef CGLTF_IMPLEMENTATION */
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/external/mini_al.h b/src/external/mini_al.h
index 829a7396..0b9a74e1 100644
--- a/src/external/mini_al.h
+++ b/src/external/mini_al.h
@@ -1,5 +1,5 @@
// Audio playback and capture library. Public domain. See "unlicense" statement at the end of this file.
-// mini_al - v0.8.5-rc - 2018-xx-xx
+// mini_al - v0.8.5 - 2018-08-12
//
// David Reid - [email protected]
@@ -21,7 +21,7 @@
// - PulseAudio
// - JACK
// - sndio (OpenBSD)
-// - audioio (NetBSD)
+// - audio(4) (NetBSD and OpenBSD)
// - OSS (FreeBSD)
// - OpenSL|ES (Android only)
// - OpenAL
@@ -121,6 +121,7 @@
// - Sample data is always native-endian and interleaved. For example, mal_format_s16 means signed 16-bit
// integer samples, interleaved. Let me know if you need non-interleaved and I'll look into it.
// - 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.
//
//
//
@@ -186,8 +187,8 @@
// #define MAL_NO_SNDIO
// Disables the sndio backend.
//
-// #define MAL_NO_AUDIOIO
-// Disables the audioio backend.
+// #define MAL_NO_AUDIO4
+// Disables the audio(4) backend.
//
// #define MAL_NO_OSS
// Disables the OSS backend.
@@ -244,6 +245,9 @@
//
// #define MAL_DEBUT_OUTPUT
// Enable printf() debug output.
+//
+// #ifndef MAL_COINIT_VALUE
+// Windows only. The value to pass to internal calls to CoInitializeEx(). Defaults to COINIT_MULTITHREADED.
#ifndef mini_al_h
#define mini_al_h
@@ -508,6 +512,7 @@ typedef int mal_result;
#define MAL_INVALID_DEVICE_CONFIG -31
#define MAL_ACCESS_DENIED -32
#define MAL_TOO_LARGE -33
+#define MAL_DEVICE_UNAVAILABLE -34
// Standard sample rates.
#define MAL_SAMPLE_RATE_8000 8000
@@ -582,6 +587,7 @@ typedef enum
mal_standard_channel_map_rfc3551, // Based off AIFF.
mal_standard_channel_map_flac,
mal_standard_channel_map_vorbis,
+ mal_standard_channel_map_sound4, // FreeBSD's sound(4).
mal_standard_channel_map_sndio, // www.sndio.org/tips.html
mal_standard_channel_map_default = mal_standard_channel_map_microsoft
} mal_standard_channel_map;
@@ -1155,8 +1161,8 @@ void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_form
#if defined(__OpenBSD__) // <-- Change this to "#if defined(MAL_BSD)" to enable sndio on all BSD flavors.
#define MAL_SUPPORT_SNDIO // sndio is only supported on OpenBSD for now. May be expanded later if there's demand.
#endif
- #if defined(__NetBSD__)
- #define MAL_SUPPORT_AUDIOIO // Only support audioio on platforms with known support.
+ #if defined(__NetBSD__) || defined(__OpenBSD__)
+ #define MAL_SUPPORT_AUDIO4 // Only support audio(4) on platforms with known support.
#endif
#if defined(__FreeBSD__) || defined(__DragonFly__)
#define MAL_SUPPORT_OSS // Only support OSS on specific platforms with known support.
@@ -1199,8 +1205,8 @@ void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_form
#if !defined(MAL_NO_SNDIO) && defined(MAL_SUPPORT_SNDIO)
#define MAL_ENABLE_SNDIO
#endif
-#if !defined(MAL_NO_AUDIOIO) && defined(MAL_SUPPORT_AUDIOIO)
- #define MAL_ENABLE_AUDIOIO
+#if !defined(MAL_NO_AUDIO4) && defined(MAL_SUPPORT_AUDIO4)
+ #define MAL_ENABLE_AUDIO4
#endif
#if !defined(MAL_NO_OSS) && defined(MAL_SUPPORT_OSS)
#define MAL_ENABLE_OSS
@@ -1230,7 +1236,7 @@ typedef enum
mal_backend_jack,
mal_backend_coreaudio,
mal_backend_sndio,
- mal_backend_audioio,
+ mal_backend_audio4,
mal_backend_oss,
mal_backend_opensl,
mal_backend_openal,
@@ -1368,8 +1374,8 @@ typedef union
#ifdef MAL_SUPPORT_SNDIO
char sndio[256]; // "snd/0", etc.
#endif
-#ifdef MAL_SUPPORT_AUDIOIO
- char audioio[256]; // "/dev/audio", etc.
+#ifdef MAL_SUPPORT_AUDIO4
+ char audio4[256]; // "/dev/audio", etc.
#endif
#ifdef MAL_SUPPORT_OSS
char oss[64]; // "dev/dsp0", etc. "dev/dsp" for the default device.
@@ -1420,6 +1426,7 @@ typedef struct
mal_uint32 sampleRate;
mal_channel channelMap[MAL_MAX_CHANNELS];
mal_uint32 bufferSizeInFrames;
+ mal_uint32 bufferSizeInMilliseconds;
mal_uint32 periods;
mal_share_mode shareMode;
mal_performance_profile performanceProfile;
@@ -1702,11 +1709,11 @@ struct mal_context
mal_proc sio_initpar;
} sndio;
#endif
-#ifdef MAL_SUPPORT_AUDIOIO
+#ifdef MAL_SUPPORT_AUDIO4
struct
{
int _unused;
- } audioio;
+ } audio4;
#endif
#ifdef MAL_SUPPORT_OSS
struct
@@ -1891,6 +1898,7 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device
mal_uint32 sampleRate;
mal_channel channelMap[MAL_MAX_CHANNELS];
mal_uint32 bufferSizeInFrames;
+ mal_uint32 bufferSizeInMilliseconds;
mal_uint32 periods;
mal_uint32 state;
mal_recv_proc onRecv;
@@ -2009,14 +2017,14 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device
void* pIntermediaryBuffer;
} sndio;
#endif
-#ifdef MAL_SUPPORT_AUDIOIO
+#ifdef MAL_SUPPORT_AUDIO4
struct
{
int fd;
mal_uint32 fragmentSizeInFrames;
mal_bool32 breakFromMainLoop;
void* pIntermediaryBuffer;
- } audioio;
+ } audio4;
#endif
#ifdef MAL_SUPPORT_OSS
struct
@@ -2092,7 +2100,7 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device
// - WinMM
// - Core Audio (Apple)
// - sndio
-// - audioio
+// - audio(4)
// - OSS
// - PulseAudio
// - ALSA
@@ -2220,9 +2228,11 @@ mal_result mal_context_get_device_info(mal_context* pContext, mal_device_type ty
//
// 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. If <bufferSizeInFrames> is 0, it will default to
-// MAL_DEFAULT_BUFFER_SIZE_IN_MILLISECONDS. If <periods> is set to 0 it will default to
-// MAL_DEFAULT_PERIODS.
+// 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 MAL_BASE_BUFFER_SIZE_IN_MILLISECONDS_LOW_LATENCY or
+// MAL_BASE_BUFFER_SIZE_IN_MILLISECONDS_CONSERVATIVE, depending on whether or not performanceProfile
+// is set to mal_performance_profile_low_latency or mal_performance_profile_conservative.
//
// When sending or receiving data to/from a device, mini_al will internally perform a format
// conversion to convert between the format specified by pConfig and the format used internally by
@@ -2489,20 +2499,22 @@ void mal_mutex_unlock(mal_mutex* pMutex);
// Retrieves a friendly name for a backend.
const char* mal_get_backend_name(mal_backend backend);
-// Calculates a scaling factor relative to speed of the system.
-//
-// This could be useful for dynamically determining the size of a device's internal buffer based on the speed of the system.
-//
-// This is a slow API because it performs a profiling test.
-float mal_calculate_cpu_speed_factor(void);
-
// 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.
mal_uint32 mal_scale_buffer_size(mal_uint32 baseBufferSize, float scale);
+// Calculates a buffer size in milliseconds from the specified number of frames and sample rate.
+mal_uint32 mal_calculate_buffer_size_in_milliseconds_from_frames(mal_uint32 bufferSizeInFrames, mal_uint32 sampleRate);
+
+// Calculates a buffer size in frames from the specified number of milliseconds and sample rate.
+mal_uint32 mal_calculate_buffer_size_in_frames_from_milliseconds(mal_uint32 bufferSizeInMilliseconds, mal_uint32 sampleRate);
+
+// Retrieves the default buffer size in milliseconds based on the specified performance profile.
+mal_uint32 mal_get_default_buffer_size_in_milliseconds(mal_performance_profile performanceProfile);
+
// Calculates a buffer size in frames for the specified performance profile and scale factor.
-mal_uint32 mal_calculate_default_buffer_size_in_frames(mal_performance_profile performanceProfile, mal_uint32 sampleRate, float scale);
+mal_uint32 mal_get_default_buffer_size_in_frames(mal_performance_profile performanceProfile, mal_uint32 sampleRate);
#endif // MAL_NO_DEVICE_IO
@@ -2620,8 +2632,9 @@ typedef struct
double time;
} mal_sine_wave;
-mal_result mal_sine_wave_init(double amplitude, double period, mal_uint32 sampleRate, mal_sine_wave* pSignWave);
-mal_uint64 mal_sine_wave_read(mal_sine_wave* pSignWave, mal_uint64 count, float* pSamples);
+mal_result mal_sine_wave_init(double amplitude, double period, mal_uint32 sampleRate, mal_sine_wave* pSineWave);
+mal_uint64 mal_sine_wave_read(mal_sine_wave* pSineWave, mal_uint64 count, float* pSamples);
+mal_uint64 mal_sine_wave_read_ex(mal_sine_wave* pSineWave, mal_uint64 frameCount, mal_uint32 channels, mal_stream_layout layout, float** ppFrames);
#ifdef __cplusplus
@@ -3007,6 +3020,11 @@ static MAL_INLINE mal_bool32 mal_is_big_endian()
}
+#ifndef MAL_COINIT_VALUE
+#define MAL_COINIT_VALUE 0 /* 0 = COINIT_MULTITHREADED*/
+#endif
+
+
#ifndef MAL_PI
#define MAL_PI 3.14159265358979323846264f
@@ -3044,12 +3062,12 @@ static MAL_INLINE mal_bool32 mal_is_big_endian()
// The base buffer size in milliseconds for low latency mode.
#ifndef MAL_BASE_BUFFER_SIZE_IN_MILLISECONDS_LOW_LATENCY
-#define MAL_BASE_BUFFER_SIZE_IN_MILLISECONDS_LOW_LATENCY 10
+#define MAL_BASE_BUFFER_SIZE_IN_MILLISECONDS_LOW_LATENCY 25
#endif
// The base buffer size in milliseconds for conservative mode.
#ifndef MAL_BASE_BUFFER_SIZE_IN_MILLISECONDS_CONSERVATIVE
-#define MAL_BASE_BUFFER_SIZE_IN_MILLISECONDS_CONSERVATIVE 50
+#define MAL_BASE_BUFFER_SIZE_IN_MILLISECONDS_CONSERVATIVE 150
#endif
@@ -3694,8 +3712,8 @@ mal_uint32 mal_get_standard_sample_rate_priority_index(mal_uint32 sampleRate)
#ifdef MAL_ENABLE_SNDIO
#define MAL_HAS_SNDIO
#endif
-#ifdef MAL_ENABLE_AUDIOIO
- #define MAL_HAS_AUDIOIO // When enabled, always assume audioio is available.
+#ifdef MAL_ENABLE_AUDIO4
+ #define MAL_HAS_AUDIO4 // When enabled, always assume audio(4) is available.
#endif
#ifdef MAL_ENABLE_OSS
#define MAL_HAS_OSS // OSS is the only supported backend for Unix and BSD, so it must be present else this library is useless.
@@ -3741,7 +3759,7 @@ const mal_backend g_malDefaultBackends[] = {
mal_backend_winmm,
mal_backend_coreaudio,
mal_backend_sndio,
- mal_backend_audioio,
+ mal_backend_audio4,
mal_backend_oss,
mal_backend_pulseaudio,
mal_backend_alsa,
@@ -3765,7 +3783,7 @@ const char* mal_get_backend_name(mal_backend backend)
case mal_backend_jack: return "JACK";
case mal_backend_coreaudio: return "Core Audio";
case mal_backend_sndio: return "sndio";
- case mal_backend_audioio: return "audioio";
+ case mal_backend_audio4: return "audio(4)";
case mal_backend_oss: return "OSS";
case mal_backend_opensl: return "OpenSL|ES";
case mal_backend_openal: return "OpenAL";
@@ -4398,133 +4416,43 @@ mal_uint32 mal_get_closest_standard_sample_rate(mal_uint32 sampleRateIn)
}
-typedef struct
-{
- mal_uint8* pInputFrames;
- mal_uint32 framesRemaining;
-} mal_calculate_cpu_speed_factor_data;
-
-mal_uint32 mal_calculate_cpu_speed_factor__on_read(mal_dsp* pDSP, mal_uint32 framesToRead, void* pFramesOut, void* pUserData)
+mal_uint32 mal_scale_buffer_size(mal_uint32 baseBufferSize, float scale)
{
- mal_calculate_cpu_speed_factor_data* pData = (mal_calculate_cpu_speed_factor_data*)pUserData;
- mal_assert(pData != NULL);
-
- if (framesToRead > pData->framesRemaining) {
- framesToRead = pData->framesRemaining;
- }
-
- mal_copy_memory(pFramesOut, pData->pInputFrames, framesToRead*pDSP->formatConverterIn.config.channels * sizeof(*pData->pInputFrames));
-
- pData->pInputFrames += framesToRead;
- pData->framesRemaining -= framesToRead;
-
- return framesToRead;
+ return mal_max(1, (mal_uint32)(baseBufferSize*scale));
}
-float mal_calculate_cpu_speed_factor()
+mal_uint32 mal_calculate_buffer_size_in_milliseconds_from_frames(mal_uint32 bufferSizeInFrames, mal_uint32 sampleRate)
{
- // Our profiling test is based on how quick it can process 1 second worth of samples through mini_al's data conversion pipeline.
-
- // This factor is multiplied with the profiling time. May need to fiddle with this to get an accurate value.
- double f = 1000;
-
- // Experiment: Reduce the factor a little when debug mode is used to reduce a blowout.
-#if !defined(NDEBUG) || defined(_DEBUG)
- f /= 2;
-#endif
-
- mal_uint32 sampleRateIn = 44100;
- mal_uint32 sampleRateOut = 48000;
- mal_uint32 channelsIn = 2;
- mal_uint32 channelsOut = 6;
-
- // Using the heap here to avoid an unnecessary static memory allocation. Also too big for the stack.
- mal_uint8* pInputFrames = NULL;
- float* pOutputFrames = NULL;
-
- size_t inputDataSize = sampleRateIn * channelsIn * sizeof(*pInputFrames);
- size_t outputDataSize = sampleRateOut * channelsOut * sizeof(*pOutputFrames);
-
- void* pData = mal_malloc(inputDataSize + outputDataSize);
- if (pData == NULL) {
- return 1;
- }
-
- pInputFrames = (mal_uint8*)pData;
- pOutputFrames = (float*)(pInputFrames + inputDataSize);
-
-
-
-
- mal_calculate_cpu_speed_factor_data data;
- data.pInputFrames = pInputFrames;
- data.framesRemaining = sampleRateIn;
-
- mal_dsp_config config = mal_dsp_config_init(mal_format_u8, channelsIn, sampleRateIn, mal_format_f32, channelsOut, sampleRateOut, mal_calculate_cpu_speed_factor__on_read, &data);
-
- // Use linear sample rate conversion because it's the simplest and least likely to cause skewing as a result of tweaks to default
- // configurations in the future.
- config.srcAlgorithm = mal_src_algorithm_linear;
-
- // Experiment: Disable SIMD extensions when profiling just to try and keep things a bit more consistent. The idea is to get a general
- // indication on the speed of the system, but SIMD is used more heavily in the DSP pipeline than in the general case which may make
- // the results a little less realistic.
- config.noSSE2 = MAL_TRUE;
- config.noAVX2 = MAL_TRUE;
- config.noAVX512 = MAL_TRUE;
- config.noNEON = MAL_TRUE;
-
- mal_dsp dsp;
- mal_result result = mal_dsp_init(&config, &dsp);
- if (result != MAL_SUCCESS) {
- mal_free(pData);
- return 1;
- }
-
-
- int iterationCount = 2;
-
- mal_timer timer;
- mal_timer_init(&timer);
- double startTime = mal_timer_get_time_in_seconds(&timer);
- {
- for (int i = 0; i < iterationCount; ++i) {
- mal_dsp_read(&dsp, sampleRateOut, pOutputFrames, &data);
- data.pInputFrames = pInputFrames;
- data.framesRemaining = sampleRateIn;
- }
- }
- double executionTimeInSeconds = mal_timer_get_time_in_seconds(&timer) - startTime;
- executionTimeInSeconds /= iterationCount;
-
-
- mal_free(pData);
-
- // Guard against extreme blowouts.
- return (float)mal_clamp(executionTimeInSeconds * f, 0.1, 100.0);
+ return bufferSizeInFrames / (sampleRate/1000);
}
-mal_uint32 mal_scale_buffer_size(mal_uint32 baseBufferSize, float scale)
+mal_uint32 mal_calculate_buffer_size_in_frames_from_milliseconds(mal_uint32 bufferSizeInMilliseconds, mal_uint32 sampleRate)
{
- return mal_max(1, (mal_uint32)(baseBufferSize*scale));
+ return bufferSizeInMilliseconds * (sampleRate/1000);
}
-mal_uint32 mal_calculate_default_buffer_size_in_frames(mal_performance_profile performanceProfile, mal_uint32 sampleRate, float scale)
+mal_uint32 mal_get_default_buffer_size_in_milliseconds(mal_performance_profile performanceProfile)
{
- mal_uint32 baseLatency;
if (performanceProfile == mal_performance_profile_low_latency) {
- baseLatency = MAL_BASE_BUFFER_SIZE_IN_MILLISECONDS_LOW_LATENCY;
+ return MAL_BASE_BUFFER_SIZE_IN_MILLISECONDS_LOW_LATENCY;
} else {
- baseLatency = MAL_BASE_BUFFER_SIZE_IN_MILLISECONDS_CONSERVATIVE;
+ return MAL_BASE_BUFFER_SIZE_IN_MILLISECONDS_CONSERVATIVE;
}
+}
- mal_uint32 sampleRateMS = (sampleRate/1000);
+mal_uint32 mal_get_default_buffer_size_in_frames(mal_performance_profile performanceProfile, mal_uint32 sampleRate)
+{
+ mal_uint32 bufferSizeInMilliseconds = mal_get_default_buffer_size_in_milliseconds(performanceProfile);
+ if (bufferSizeInMilliseconds == 0) {
+ bufferSizeInMilliseconds = 1;
+ }
- mal_uint32 minBufferSize = sampleRateMS * mal_min(baseLatency / 5, 1); // <-- Guard against multiply by zero.
- mal_uint32 maxBufferSize = sampleRateMS * (baseLatency * 40);
+ mal_uint32 sampleRateMS = (sampleRate/1000);
+ if (sampleRateMS == 0) {
+ sampleRateMS = 1;
+ }
- mal_uint32 bufferSize = mal_scale_buffer_size((sampleRate/1000) * baseLatency, scale);
- return mal_clamp(bufferSize, minBufferSize, maxBufferSize);
+ return bufferSizeInMilliseconds * sampleRateMS;
}
@@ -4865,19 +4793,21 @@ mal_result mal_device_init__null(mal_context* pContext, mal_device_type type, co
(void)pContext;
(void)type;
(void)pDeviceID;
+ (void)pConfig;
mal_assert(pDevice != NULL);
mal_zero_object(&pDevice->null_device);
- pDevice->bufferSizeInFrames = pConfig->bufferSizeInFrames;
- pDevice->periods = pConfig->periods;
-
if (type == mal_device_type_playback) {
mal_strncpy_s(pDevice->name, sizeof(pDevice->name), "NULL Playback Device", (size_t)-1);
} else {
mal_strncpy_s(pDevice->name, sizeof(pDevice->name), "NULL Capture Device", (size_t)-1);
}
+ if (pDevice->bufferSizeInFrames == 0) {
+ pDevice->bufferSizeInFrames = mal_calculate_buffer_size_in_frames_from_milliseconds(pDevice->bufferSizeInMilliseconds, pDevice->sampleRate);
+ }
+
pDevice->null_device.pBuffer = (mal_uint8*)mal_malloc(pDevice->bufferSizeInFrames * pDevice->channels * mal_get_bytes_per_sample(pDevice->format));
if (pDevice->null_device.pBuffer == NULL) {
return MAL_OUT_OF_MEMORY;
@@ -6321,33 +6251,11 @@ mal_result mal_device_init__wasapi(mal_context* pContext, mal_device_type type,
mal_channel_mask_to_channel_map__win32(wf.dwChannelMask, pDevice->internalChannels, pDevice->internalChannelMap);
// If we're using a default buffer size we need to calculate it based on the efficiency of the system.
- if (pDevice->usingDefaultBufferSize) {
- // CPU speed is a factor to consider when determine how large of a buffer we need.
- float fCPUSpeed = mal_calculate_cpu_speed_factor();
-
- // We need a slightly bigger buffer if we're using shared mode to cover the inherent tax association with shared mode.
- float fShareMode;
- if (pConfig->shareMode == mal_share_mode_shared) {
- fShareMode = 1.0f;
- } else {
- fShareMode = 0.8f;
- }
-
- // In my testing, capture seems to have worse latency than playback for some reason.
- float fType;
- if (type == mal_device_type_playback) {
- fType = 1.0f;
- } else {
- fType = 2.0f;
- }
-
- // Backend tax. Need to fiddle with this.
- float fBackend = 1.0;
-
- pDevice->bufferSizeInFrames = mal_calculate_default_buffer_size_in_frames(pConfig->performanceProfile, pConfig->sampleRate, fCPUSpeed*fShareMode*fType*fBackend);
+ if (pDevice->bufferSizeInFrames == 0) {
+ pDevice->bufferSizeInFrames = mal_calculate_buffer_size_in_frames_from_milliseconds(pDevice->bufferSizeInMilliseconds, pDevice->internalSampleRate);
}
- bufferDurationInMicroseconds = ((mal_uint64)pDevice->bufferSizeInFrames * 1000 * 1000) / pConfig->sampleRate;
+ bufferDurationInMicroseconds = ((mal_uint64)pDevice->bufferSizeInFrames * 1000 * 1000) / pDevice->internalSampleRate;
// Slightly different initialization for shared and exclusive modes. We try exclusive mode first, and if it fails, fall back to shared mode.
if (shareMode == MAL_AUDCLNT_SHAREMODE_EXCLUSIVE) {
@@ -6561,56 +6469,66 @@ mal_result mal_device__break_main_loop__wasapi(mal_device* pDevice)
return MAL_SUCCESS;
}
-mal_uint32 mal_device__get_available_frames__wasapi(mal_device* pDevice)
+mal_result mal_device__get_available_frames__wasapi(mal_device* pDevice, mal_uint32* pFrameCount)
{
mal_assert(pDevice != NULL);
+ mal_assert(pFrameCount != NULL);
+
+ *pFrameCount = 0;
#if 0
if (pDevice->type == mal_device_type_playback) {
mal_uint32 paddingFramesCount;
HRESULT hr = mal_IAudioClient_GetCurrentPadding((mal_IAudioClient*)pDevice->wasapi.pAudioClient, &paddingFramesCount);
if (FAILED(hr)) {
- return 0;
+ return MAL_ERROR;
}
if (pDevice->exclusiveMode) {
- return paddingFramesCount;
+ *pFrameCount = paddingFramesCount;
+ return MAL_SUCCESS;
} else {
- return pDevice->bufferSizeInFrames - paddingFramesCount;
+ *pFrameCount = pDevice->bufferSizeInFrames - paddingFramesCount;
+ return MAL_SUCCESS;
}
} else {
mal_uint32 framesAvailable;
HRESULT hr = mal_IAudioCaptureClient_GetNextPacketSize((mal_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, &framesAvailable);
if (FAILED(hr)) {
- return 0;
+ return MAL_ERROR;
}
- return framesAvailable;
+ *pFrameCount = framesAvailable;
+ return MAL_SUCCESS;
}
#else
mal_uint32 paddingFramesCount;
HRESULT hr = mal_IAudioClient_GetCurrentPadding((mal_IAudioClient*)pDevice->wasapi.pAudioClient, &paddingFramesCount);
if (FAILED(hr)) {
- return 0;
+ return MAL_DEVICE_UNAVAILABLE;
}
// Slightly different rules for exclusive and shared modes.
if (pDevice->exclusiveMode) {
- return paddingFramesCount;
+ *pFrameCount = paddingFramesCount;
} else {
if (pDevice->type == mal_device_type_playback) {
- return pDevice->bufferSizeInFrames - paddingFramesCount;
+ *pFrameCount = pDevice->bufferSizeInFrames - paddingFramesCount;
} else {
- return paddingFramesCount;
+ *pFrameCount = paddingFramesCount;
}
}
+
+ return MAL_SUCCESS;
#endif
}
-mal_uint32 mal_device__wait_for_frames__wasapi(mal_device* pDevice)
+mal_result mal_device__wait_for_frames__wasapi(mal_device* pDevice, mal_uint32* pFrameCount)
{
mal_assert(pDevice != NULL);
+ mal_result result;
+
while (!pDevice->wasapi.breakFromMainLoop) {
// Wait for a buffer to become available or for the stop event to be signalled.
HANDLE hEvents[2];
@@ -6626,14 +6544,18 @@ mal_uint32 mal_device__wait_for_frames__wasapi(mal_device* pDevice)
break;
}
- mal_uint32 framesAvailable = mal_device__get_available_frames__wasapi(pDevice);
- if (framesAvailable > 0) {
- return framesAvailable;
+ result = mal_device__get_available_frames__wasapi(pDevice, pFrameCount);
+ if (result != MAL_SUCCESS) {
+ return result;
+ }
+
+ if (*pFrameCount > 0) {
+ return MAL_SUCCESS;
}
}
// We'll get here if the loop was terminated. Just return whatever's available.
- return mal_device__get_available_frames__wasapi(pDevice);
+ return mal_device__get_available_frames__wasapi(pDevice, pFrameCount);
}
mal_result mal_device__main_loop__wasapi(mal_device* pDevice)
@@ -6645,14 +6567,19 @@ mal_result mal_device__main_loop__wasapi(mal_device* pDevice)
pDevice->wasapi.breakFromMainLoop = MAL_FALSE;
while (!pDevice->wasapi.breakFromMainLoop) {
- mal_uint32 framesAvailable = mal_device__wait_for_frames__wasapi(pDevice);
+ mal_uint32 framesAvailable;
+ mal_result result = mal_device__wait_for_frames__wasapi(pDevice, &framesAvailable);
+ if (result != MAL_SUCCESS) {
+ return result;
+ }
+
if (framesAvailable == 0) {
continue;
}
// If it's a playback device, don't bother grabbing more data if the device is being stopped.
if (pDevice->wasapi.breakFromMainLoop && pDevice->type == mal_device_type_playback) {
- return MAL_FALSE;
+ return MAL_SUCCESS;
}
if (pDevice->type == mal_device_type_playback) {
@@ -7609,25 +7536,6 @@ mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type type,
return MAL_FORMAT_NOT_SUPPORTED;
}
- if (pDevice->usingDefaultBufferSize) {
- // CPU speed is a factor to consider when determine how large of a buffer we need.
- float fCPUSpeed = mal_calculate_cpu_speed_factor();
-
- // In my testing, capture seems to have worse latency than playback for some reason.
- float fType;
- if (type == mal_device_type_playback) {
- fType = 1.0f;
- } else {
- fType = 2.0f;
- }
-
- // Backend tax. Need to fiddle with this.
- float fBackend = 3.0;
-
- pDevice->bufferSizeInFrames = mal_calculate_default_buffer_size_in_frames(pConfig->performanceProfile, pConfig->sampleRate, fCPUSpeed*fType*fBackend);
- }
-
-
WAVEFORMATEXTENSIBLE wf;
mal_zero_object(&wf);
wf.Format.cbSize = sizeof(wf);
@@ -7727,6 +7635,11 @@ mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type type,
mal_channel_mask_to_channel_map__win32(wf.dwChannelMask, pDevice->internalChannels, pDevice->internalChannelMap);
}
+ // We need to wait until we know the sample rate before we can calculate the buffer size.
+ if (pDevice->bufferSizeInFrames == 0) {
+ pDevice->bufferSizeInFrames = mal_calculate_buffer_size_in_frames_from_milliseconds(pDevice->bufferSizeInMilliseconds, pDevice->internalSampleRate);
+ }
+
bufferSizeInBytes = pDevice->bufferSizeInFrames * pDevice->internalChannels * mal_get_bytes_per_sample(pDevice->internalFormat);
@@ -7784,6 +7697,10 @@ mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type type,
wf.Samples.wValidBitsPerSample = wf.Format.wBitsPerSample;
wf.SubFormat = MAL_GUID_KSDATAFORMAT_SUBTYPE_PCM;
+ if (pDevice->bufferSizeInFrames == 0) {
+ pDevice->bufferSizeInFrames = mal_calculate_buffer_size_in_frames_from_milliseconds(pDevice->bufferSizeInMilliseconds, wf.Format.nSamplesPerSec);
+ }
+
bufferSizeInBytes = pDevice->bufferSizeInFrames * wf.Format.nChannels * mal_get_bytes_per_sample(pDevice->format);
MAL_DSCBUFFERDESC descDS;
@@ -8655,23 +8572,12 @@ mal_result mal_device_init__winmm(mal_context* pContext, mal_device_type type, c
mal_get_standard_channel_map(mal_standard_channel_map_microsoft, pDevice->internalChannels, pDevice->internalChannelMap);
- // Latency with WinMM seems pretty bad from my testing... Need to increase the default buffer size.
- if (pDevice->usingDefaultBufferSize) {
- // CPU speed is a factor to consider when determine how large of a buffer we need.
- float fCPUSpeed = mal_calculate_cpu_speed_factor();
-
- // In my testing, capture seems to have worse latency than playback for some reason.
- float fType;
- if (type == mal_device_type_playback) {
- fType = 1.0f;
- } else {
- fType = 2.0f;
+ if (pDevice->bufferSizeInFrames == 0) {
+ pDevice->bufferSizeInFrames = mal_calculate_buffer_size_in_frames_from_milliseconds(pDevice->bufferSizeInMilliseconds, pDevice->internalSampleRate);
+ if (pDevice->usingDefaultBufferSize) {
+ float bufferSizeScaleFactor = 4; // <-- Latency with WinMM seems pretty bad from my testing...
+ pDevice->bufferSizeInFrames = mal_scale_buffer_size(pDevice->bufferSizeInFrames, bufferSizeScaleFactor);
}
-
- // Backend tax. Need to fiddle with this.
- float fBackend = 10.0;
-
- pDevice->bufferSizeInFrames = mal_calculate_default_buffer_size_in_frames(pConfig->performanceProfile, pConfig->sampleRate, fCPUSpeed*fType*fBackend);
}
// The size of the intermediary buffer needs to be able to fit every fragment.
@@ -8847,7 +8753,7 @@ mal_result mal_device__main_loop__winmm(mal_device* pDevice)
MMRESULT resultMM = ((MAL_PFN_waveOutUnprepareHeader)pDevice->pContext->winmm.waveOutUnprepareHeader)((HWAVEOUT)pDevice->winmm.hDevice, &((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i], sizeof(WAVEHDR));
if (resultMM != MMSYSERR_NOERROR) {
mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[WinMM] Failed to unprepare header for playback device in preparation for sending a new block of data to the device for playback.", mal_result_from_MMRESULT(resultMM));
- break;
+ return MAL_DEVICE_UNAVAILABLE;
}
mal_zero_object(&((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i]);
@@ -8861,7 +8767,7 @@ mal_result mal_device__main_loop__winmm(mal_device* pDevice)
resultMM = ((MAL_PFN_waveOutPrepareHeader)pDevice->pContext->winmm.waveOutPrepareHeader)((HWAVEOUT)pDevice->winmm.hDevice, &((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i], sizeof(WAVEHDR));
if (resultMM != MMSYSERR_NOERROR) {
mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[WinMM] Failed to prepare header for playback device in preparation for sending a new block of data to the device for playback.", mal_result_from_MMRESULT(resultMM));
- break;
+ return MAL_DEVICE_UNAVAILABLE;
}
} else {
// Capture.
@@ -8873,7 +8779,7 @@ mal_result mal_device__main_loop__winmm(mal_device* pDevice)
MMRESULT resultMM = ((MAL_PFN_waveInUnprepareHeader)pDevice->pContext->winmm.waveInUnprepareHeader)((HWAVEIN)pDevice->winmm.hDevice, &((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i], sizeof(WAVEHDR));
if (resultMM != MMSYSERR_NOERROR) {
mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[WinMM] Failed to unprepare header for capture device in preparation for adding a new capture buffer for the device.", mal_result_from_MMRESULT(resultMM));
- break;
+ return MAL_DEVICE_UNAVAILABLE;
}
mal_zero_object(&((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i]);
@@ -8886,7 +8792,7 @@ mal_result mal_device__main_loop__winmm(mal_device* pDevice)
resultMM = ((MAL_PFN_waveInPrepareHeader)pDevice->pContext->winmm.waveInPrepareHeader)((HWAVEIN)pDevice->winmm.hDevice, &((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i], sizeof(WAVEHDR));
if (resultMM != MMSYSERR_NOERROR) {
mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[WinMM] Failed to prepare header for capture device in preparation for adding a new capture buffer for the device.", mal_result_from_MMRESULT(resultMM));
- break;
+ return MAL_DEVICE_UNAVAILABLE;
}
}
@@ -8906,14 +8812,14 @@ mal_result mal_device__main_loop__winmm(mal_device* pDevice)
MMRESULT resultMM = ((MAL_PFN_waveOutWrite)pDevice->pContext->winmm.waveOutWrite)((HWAVEOUT)pDevice->winmm.hDevice, &((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i], sizeof(WAVEHDR));
if (resultMM != MMSYSERR_NOERROR) {
mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[WinMM] Failed to write data to the internal playback device.", mal_result_from_MMRESULT(resultMM));
- break;
+ return MAL_DEVICE_UNAVAILABLE;
}
} else {
// Capture.
MMRESULT resultMM = ((MAL_PFN_waveInAddBuffer)pDevice->pContext->winmm.waveInAddBuffer)((HWAVEIN)pDevice->winmm.hDevice, &((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i], sizeof(WAVEHDR));
if (resultMM != MMSYSERR_NOERROR) {
mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[WinMM] Failed to add new capture buffer to the internal capture device.", mal_result_from_MMRESULT(resultMM));
- break;
+ return MAL_DEVICE_UNAVAILABLE;
}
}
}
@@ -8985,6 +8891,7 @@ mal_result mal_context_init__winmm(mal_context* pContext)
///////////////////////////////////////////////////////////////////////////////
#ifdef MAL_HAS_ALSA
+#include <alsa/asoundlib.h>
#ifdef MAL_NO_RUNTIME_LINKING
#include <alsa/asoundlib.h>
typedef snd_pcm_uframes_t mal_snd_pcm_uframes_t;
@@ -9248,8 +9155,8 @@ static struct
const char* name;
float scale;
} g_malDefaultBufferSizeScalesALSA[] = {
- {"bcm2835 IEC958/HDMI", 6.0f},
- {"bcm2835 ALSA", 6.0f}
+ {"bcm2835 IEC958/HDMI", 2.0f},
+ {"bcm2835 ALSA", 2.0f}
};
float mal_find_default_buffer_size_scale__alsa(const char* deviceName)
@@ -9929,22 +9836,9 @@ mal_uint32 mal_device__wait_for_frames__alsa(mal_device* pDevice, mal_bool32* pR
if (pRequiresRestart) *pRequiresRestart = MAL_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.
+ mal_uint32 periodSizeInFrames = pDevice->bufferSizeInFrames / pDevice->periods;
while (!pDevice->alsa.breakFromMainLoop) {
- int waitResult = ((mal_snd_pcm_wait_proc)pDevice->pContext->alsa.snd_pcm_wait)((mal_snd_pcm_t*)pDevice->alsa.pPCM, -1);
- if (waitResult < 0) {
- if (waitResult == -EPIPE) {
- if (((mal_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((mal_snd_pcm_t*)pDevice->alsa.pPCM, waitResult, MAL_TRUE) < 0) {
- return 0;
- }
-
- if (pRequiresRestart) *pRequiresRestart = MAL_TRUE; // A device recovery means a restart for mmap mode.
- }
- }
-
- if (pDevice->alsa.breakFromMainLoop) {
- return 0;
- }
-
mal_snd_pcm_sframes_t framesAvailable = ((mal_snd_pcm_avail_update_proc)pDevice->pContext->alsa.snd_pcm_avail_update)((mal_snd_pcm_t*)pDevice->alsa.pPCM);
if (framesAvailable < 0) {
if (framesAvailable == -EPIPE) {
@@ -9952,7 +9846,10 @@ mal_uint32 mal_device__wait_for_frames__alsa(mal_device* pDevice, mal_bool32* pR
return 0;
}
- if (pRequiresRestart) *pRequiresRestart = MAL_TRUE; // A device recovery means a restart for mmap mode.
+ // A device recovery means a restart for mmap mode.
+ if (pRequiresRestart) {
+ *pRequiresRestart = MAL_TRUE;
+ }
// Try again, but if it fails this time just return an error.
framesAvailable = ((mal_snd_pcm_avail_update_proc)pDevice->pContext->alsa.snd_pcm_avail_update)((mal_snd_pcm_t*)pDevice->alsa.pPCM);
@@ -9962,21 +9859,26 @@ mal_uint32 mal_device__wait_for_frames__alsa(mal_device* pDevice, mal_bool32* pR
}
}
- // Ideally I'd like to keep the number of frames consistent with the period size, but unfortunately it appears
- // this does not work correctly in some situations. In my testing, this breaks when the period size is <= 1024
- // when using "hw:0,0" in a VirtualBox guest. What's happening is that it looks like snd_pcm_writei() (and
- // snd_pcm_mmap_commit() in MMAP mode) are not physically writing the data to the internal buffers. As a result,
- // snd_pcm_wait() is returning immediately, always reporting the full buffer size as available. I'm not sure if
- // this is me not doing something right, or if it's some kind of driver bug, but to fix this we just need to
- // report the exact value returned by snd_pcm_avail_update() and not clamp it to the period size.
-#if 1
- return framesAvailable;
-#else
- mal_uint32 periodSizeInFrames = pDevice->bufferSizeInFrames / pDevice->periods;
if (framesAvailable >= periodSizeInFrames) {
return periodSizeInFrames;
}
-#endif
+
+ if (framesAvailable < periodSizeInFrames) {
+ // Less than a whole period is available so keep waiting.
+ int waitResult = ((mal_snd_pcm_wait_proc)pDevice->pContext->alsa.snd_pcm_wait)((mal_snd_pcm_t*)pDevice->alsa.pPCM, -1);
+ if (waitResult < 0) {
+ if (waitResult == -EPIPE) {
+ if (((mal_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((mal_snd_pcm_t*)pDevice->alsa.pPCM, waitResult, MAL_TRUE) < 0) {
+ return 0;
+ }
+
+ // A device recovery means a restart for mmap mode.
+ if (pRequiresRestart) {
+ *pRequiresRestart = MAL_TRUE;
+ }
+ }
+ }
+ }
}
// We'll get here if the loop was terminated. Just return whatever's available.
@@ -10214,32 +10116,15 @@ mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type type, co
return result;
}
- // If using the default buffer size we do some calculations based on a simple profiling test.
+ // We may be scaling the size of the buffer.
+ float bufferSizeScaleFactor = 1;
+
+ // If using the default buffer size we may want to apply some device-specific scaling for known devices that have peculiar latency characteristics (looking at you Raspberry Pi!).
if (pDevice->usingDefaultBufferSize) {
mal_snd_pcm_info_t* pInfo = (mal_snd_pcm_info_t*)alloca(((mal_snd_pcm_info_sizeof_proc)pContext->alsa.snd_pcm_info_sizeof)());
mal_zero_memory(pInfo, ((mal_snd_pcm_info_sizeof_proc)pContext->alsa.snd_pcm_info_sizeof)());
- // CPU speed is a factor to consider when determine how large of a buffer we need.
- float fCPUSpeed = mal_calculate_cpu_speed_factor();
-
- // We need a slightly bigger buffer if we're using shared mode to cover the inherent tax association with shared mode.
- float fShareMode;
- if (pConfig->shareMode == mal_share_mode_shared) {
- fShareMode = 1.0f;
- } else {
- fShareMode = 0.8f;
- }
-
- // In my testing, capture seems to have worse latency than playback for some reason.
- float fType;
- if (type == mal_device_type_playback) {
- fType = 1.0f;
- } else {
- fType = 2.0f;
- }
-
// We may need to scale the size of the buffer depending on the device.
- float fDevice = 1;
if (((mal_snd_pcm_info_proc)pContext->alsa.snd_pcm_info)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pInfo) == 0) {
const char* deviceName = ((mal_snd_pcm_info_get_name_proc)pContext->alsa.snd_pcm_info_get_name)(pInfo);
if (deviceName != NULL) {
@@ -10260,7 +10145,7 @@ mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type type, co
if ((type == mal_device_type_playback && (IOID == NULL || mal_strcmp(IOID, "Output") == 0)) ||
(type == mal_device_type_capture && (IOID != NULL && mal_strcmp(IOID, "Input" ) == 0))) {
if (mal_strcmp(NAME, deviceName) == 0) {
- fDevice = mal_find_default_buffer_size_scale__alsa(DESC);
+ bufferSizeScaleFactor = mal_find_default_buffer_size_scale__alsa(DESC);
foundDevice = MAL_TRUE;
}
}
@@ -10277,17 +10162,13 @@ mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type type, co
((mal_snd_device_name_free_hint_proc)pContext->alsa.snd_device_name_free_hint)((void**)ppDeviceHints);
} else {
- fDevice = mal_find_default_buffer_size_scale__alsa(deviceName);
+ bufferSizeScaleFactor = mal_find_default_buffer_size_scale__alsa(deviceName);
}
}
-
- pDevice->bufferSizeInFrames = mal_calculate_default_buffer_size_in_frames(pConfig->performanceProfile, pConfig->sampleRate, fCPUSpeed*fShareMode*fType*fDevice);
}
}
-
-
// Hardware parameters.
mal_snd_pcm_hw_params_t* pHWParams = (mal_snd_pcm_hw_params_t*)alloca(((mal_snd_pcm_hw_params_sizeof_proc)pContext->alsa.snd_pcm_hw_params_sizeof)());
mal_zero_memory(pHWParams, ((mal_snd_pcm_hw_params_sizeof_proc)pContext->alsa.snd_pcm_hw_params_sizeof)());
@@ -10302,7 +10183,7 @@ mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type type, co
//
// Try using interleaved MMAP access. If this fails, fall back to standard readi/writei.
pDevice->alsa.isUsingMMap = MAL_FALSE;
- if (!pConfig->alsa.noMMap && pDevice->type != mal_device_type_capture) { // <-- Disabling MMAP mode for capture devices because I apparently do not have a device that supports it so I can test it... Contributions welcome.
+ if (!pConfig->alsa.noMMap && pDevice->type != mal_device_type_capture) { // <-- Disabling MMAP mode for capture devices because I apparently do not have a device that supports it which means I can't test it... Contributions welcome.
if (((mal_snd_pcm_hw_params_set_access_proc)pContext->alsa.snd_pcm_hw_params_set_access)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, MAL_SND_PCM_ACCESS_MMAP_INTERLEAVED) == 0) {
pDevice->alsa.isUsingMMap = MAL_TRUE;
}
@@ -10331,18 +10212,18 @@ mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type type, co
if (!((mal_snd_pcm_format_mask_test_proc)pContext->alsa.snd_pcm_format_mask_test)(pFormatMask, formatALSA)) {
// The requested format is not supported so now try running through the list of formats and return the best one.
mal_snd_pcm_format_t preferredFormatsALSA[] = {
+ MAL_SND_PCM_FORMAT_S16_LE, // mal_format_s16
MAL_SND_PCM_FORMAT_FLOAT_LE, // mal_format_f32
MAL_SND_PCM_FORMAT_S32_LE, // mal_format_s32
MAL_SND_PCM_FORMAT_S24_3LE, // mal_format_s24
- MAL_SND_PCM_FORMAT_S16_LE, // mal_format_s16
MAL_SND_PCM_FORMAT_U8 // mal_format_u8
};
if (mal_is_big_endian()) {
- preferredFormatsALSA[0] = MAL_SND_PCM_FORMAT_FLOAT_BE;
- preferredFormatsALSA[1] = MAL_SND_PCM_FORMAT_S32_BE;
- preferredFormatsALSA[2] = MAL_SND_PCM_FORMAT_S24_3BE;
- preferredFormatsALSA[3] = MAL_SND_PCM_FORMAT_S16_BE;
+ preferredFormatsALSA[0] = MAL_SND_PCM_FORMAT_S16_BE;
+ preferredFormatsALSA[1] = MAL_SND_PCM_FORMAT_FLOAT_BE;
+ preferredFormatsALSA[2] = MAL_SND_PCM_FORMAT_S32_BE;
+ preferredFormatsALSA[3] = MAL_SND_PCM_FORMAT_S24_3BE;
preferredFormatsALSA[4] = MAL_SND_PCM_FORMAT_U8;
}
@@ -10407,14 +10288,10 @@ mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type type, co
pDevice->internalSampleRate = (mal_uint32)sampleRate;
- // Periods.
- mal_uint32 periods = pConfig->periods;
- int dir = 0;
- if (((mal_snd_pcm_hw_params_set_periods_near_proc)pContext->alsa.snd_pcm_hw_params_set_periods_near)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &periods, &dir) < 0) {
- mal_device_uninit__alsa(pDevice);
- return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[ALSA] Failed to set period count. snd_pcm_hw_params_set_periods_near() failed.", MAL_FORMAT_NOT_SUPPORTED);
+ // At this point we know the internal sample rate which means we can calculate the buffer size in frames.
+ if (pDevice->bufferSizeInFrames == 0) {
+ pDevice->bufferSizeInFrames = mal_scale_buffer_size(mal_calculate_buffer_size_in_frames_from_milliseconds(pDevice->bufferSizeInMilliseconds, pDevice->internalSampleRate), bufferSizeScaleFactor);
}
- pDevice->periods = periods;
// Buffer Size
mal_snd_pcm_uframes_t actualBufferSize = pDevice->bufferSizeInFrames;
@@ -10424,6 +10301,14 @@ mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type type, co
}
pDevice->bufferSizeInFrames = actualBufferSize;
+ // Periods.
+ mal_uint32 periods = pConfig->periods;
+ if (((mal_snd_pcm_hw_params_set_periods_near_proc)pContext->alsa.snd_pcm_hw_params_set_periods_near)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &periods, NULL) < 0) {
+ mal_device_uninit__alsa(pDevice);
+ return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[ALSA] Failed to set period count. snd_pcm_hw_params_set_periods_near() failed.", MAL_FORMAT_NOT_SUPPORTED);
+ }
+ pDevice->periods = periods;
+
// Apply hardware parameters.
if (((mal_snd_pcm_hw_params_proc)pContext->alsa.snd_pcm_hw_params)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pHWParams) < 0) {
@@ -12124,23 +12009,20 @@ mal_result mal_device_init__pulse(mal_context* pContext, mal_device_type type, c
}
#endif
- // If using the default buffer size try to find an appropriate default.
- if (pDevice->usingDefaultBufferSize) {
- // CPU speed is a factor to consider when determine how large of a buffer we need.
- float fCPUSpeed = mal_calculate_cpu_speed_factor();
-
- // In my testing, capture seems to have worse latency than playback for some reason.
- float fType;
- if (type == mal_device_type_playback) {
- fType = 1.0f;
- } else {
- fType = 2.0f;
- }
+ // Buffer size.
+ bufferSizeInFrames = pDevice->bufferSizeInFrames;
+ if (bufferSizeInFrames == 0) {
+ bufferSizeInFrames = mal_calculate_buffer_size_in_frames_from_milliseconds(pDevice->bufferSizeInMilliseconds, ss.rate);
- // Backend tax. Need to fiddle with this.
- float fBackend = 1.2;
+ // PulseAudio seems to need a bit of an bit of size to the buffer to be reliable.
+ if (pDevice->usingDefaultBufferSize) {
+ float bufferSizeScaleFactor = 1.0f;
+ if (type == mal_device_type_capture) {
+ bufferSizeScaleFactor = 2.0f;
+ }
- bufferSizeInFrames = mal_calculate_default_buffer_size_in_frames(pConfig->performanceProfile, pConfig->sampleRate, fCPUSpeed*fType*fBackend);
+ bufferSizeInFrames = mal_scale_buffer_size(bufferSizeInFrames, bufferSizeScaleFactor);
+ }
}
attr.maxlength = bufferSizeInFrames * mal_get_bytes_per_sample(mal_format_from_pulse(ss.format))*ss.channels;
@@ -14713,32 +14595,10 @@ mal_result mal_device_init__coreaudio(mal_context* pContext, mal_device_type dev
// Buffer size. Not allowing this to be configurable on iOS.
mal_uint32 actualBufferSizeInFrames = pDevice->bufferSizeInFrames;
- if (actualBufferSizeInFrames < pDevice->periods) {
- actualBufferSizeInFrames = pDevice->periods;
- }
#if defined(MAL_APPLE_DESKTOP)
- if (pDevice->usingDefaultBufferSize) {
- // CPU speed is a factor to consider when determine how large of a buffer we need.
- float fCPUSpeed = mal_calculate_cpu_speed_factor();
-
- // In my admittedly limited testing, capture latency seems to be about the same as playback with Core Audio, at least on my MacBook Pro. On other
- // backends, however, this is often different. I am therefore leaving the logic below in place just in case I need to do some capture/playback
- // specific tweaking.
- float fDeviceType;
- if (deviceType == mal_device_type_playback) {
- fDeviceType = 1.0f;
- } else {
- fDeviceType = 6.0f;
- }
-
- // Backend tax. Need to fiddle with this.
- float fBackend = 1.0f;
-
- actualBufferSizeInFrames = mal_calculate_default_buffer_size_in_frames(pConfig->performanceProfile, pConfig->sampleRate, fCPUSpeed*fDeviceType*fBackend);
- if (actualBufferSizeInFrames < pDevice->periods) {
- actualBufferSizeInFrames = pDevice->periods;
- }
+ if (actualBufferSizeInFrames == 0) {
+ actualBufferSizeInFrames = mal_calculate_buffer_size_in_frames_from_milliseconds(pDevice->bufferSizeInMilliseconds, pDevice->internalSampleRate);
}
actualBufferSizeInFrames = actualBufferSizeInFrames / pDevice->periods;
@@ -15550,19 +15410,10 @@ mal_result mal_device_init__sndio(mal_context* pContext, mal_device_type deviceT
// Try calculating an appropriate default buffer size after we have the sample rate.
mal_uint32 desiredBufferSizeInFrames = pDevice->bufferSizeInFrames;
- if (pDevice->usingDefaultBufferSize) {
- // CPU speed factor.
- float fCPUSpeed = mal_calculate_cpu_speed_factor();
-
- // Playback vs capture latency.
- float fDeviceType = 1;
-
- // Backend tax.
- float fBackend = 1;
-
- desiredBufferSizeInFrames = mal_calculate_default_buffer_size_in_frames(pConfig->performanceProfile, par.rate, fCPUSpeed*fDeviceType*fBackend);
+ if (desiredBufferSizeInFrames == 0) {
+ desiredBufferSizeInFrames = mal_calculate_buffer_size_in_frames_from_milliseconds(pDevice->bufferSizeInMilliseconds, par.rate);
}
-
+
par.round = desiredBufferSizeInFrames / pDevice->periods;
par.appbufsz = par.round * pDevice->periods;
@@ -15766,17 +15617,24 @@ mal_result mal_context_init__sndio(mal_context* pContext)
///////////////////////////////////////////////////////////////////////////////
//
-// audioio Backend
+// audio(4) Backend
//
///////////////////////////////////////////////////////////////////////////////
-#ifdef MAL_HAS_AUDIOIO
+#ifdef MAL_HAS_AUDIO4
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/audioio.h>
-void mal_construct_device_id__audioio(char* id, size_t idSize, const char* base, int deviceIndex)
+#if defined(__OpenBSD__)
+ #include <sys/param.h>
+ #if defined(OpenBSD) && OpenBSD >= 201709
+ #define MAL_AUDIO4_USE_NEW_API
+ #endif
+#endif
+
+void mal_construct_device_id__audio4(char* id, size_t idSize, const char* base, int deviceIndex)
{
mal_assert(id != NULL);
mal_assert(idSize > 0);
@@ -15789,7 +15647,7 @@ void mal_construct_device_id__audioio(char* id, size_t idSize, const char* base,
mal_itoa_s(deviceIndex, id+baseLen, idSize-baseLen, 10);
}
-mal_result mal_extract_device_index_from_id__audioio(const char* id, const char* base, int* pIndexOut)
+mal_result mal_extract_device_index_from_id__audio4(const char* id, const char* base, int* pIndexOut)
{
mal_assert(id != NULL);
mal_assert(base != NULL);
@@ -15817,17 +15675,18 @@ mal_result mal_extract_device_index_from_id__audioio(const char* id, const char*
return MAL_SUCCESS;
}
-mal_bool32 mal_context_is_device_id_equal__audioio(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1)
+mal_bool32 mal_context_is_device_id_equal__audio4(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1)
{
mal_assert(pContext != NULL);
mal_assert(pID0 != NULL);
mal_assert(pID1 != NULL);
(void)pContext;
- return mal_strcmp(pID0->audioio, pID1->audioio) == 0;
+ return mal_strcmp(pID0->audio4, pID1->audio4) == 0;
}
-mal_format mal_format_from_encoding__audioio(unsigned int encoding, unsigned int precision)
+#if !defined(MAL_AUDIO4_USE_NEW_API)
+mal_format mal_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 mal_format_u8;
@@ -15854,12 +15713,32 @@ mal_format mal_format_from_encoding__audioio(unsigned int encoding, unsigned int
return mal_format_unknown; // Encoding not supported.
}
-mal_format mal_format_from_prinfo__audioio(struct audio_prinfo* prinfo)
+mal_format mal_format_from_prinfo__audio4(struct audio_prinfo* prinfo)
+{
+ return mal_format_from_encoding__audio4(prinfo->encoding, prinfo->precision);
+}
+#else
+mal_format mal_format_from_swpar__audio4(struct audio_swpar* par)
{
- return mal_format_from_encoding__audioio(prinfo->encoding, prinfo->precision);
+ if (par->bits == 8 && par->bps == 1 && par->sig == 0) {
+ return mal_format_u8;
+ }
+ if (par->bits == 16 && par->bps == 2 && par->sig == 1 && par->le == mal_is_little_endian()) {
+ return mal_format_s16;
+ }
+ if (par->bits == 24 && par->bps == 3 && par->sig == 1 && par->le == mal_is_little_endian()) {
+ return mal_format_s24;
+ }
+ if (par->bits == 32 && par->bps == 4 && par->sig == 1 && par->le == mal_is_little_endian()) {
+ return mal_format_f32;
+ }
+
+ // Format not supported.
+ return mal_format_unknown;
}
+#endif
-mal_result mal_context_get_device_info_from_fd__audioio(mal_context* pContext, mal_device_type deviceType, int fd, mal_device_info* pInfoOut)
+mal_result mal_context_get_device_info_from_fd__audio4(mal_context* pContext, mal_device_type deviceType, int fd, mal_device_info* pInfoOut)
{
mal_assert(pContext != NULL);
mal_assert(fd >= 0);
@@ -15876,6 +15755,7 @@ mal_result mal_context_get_device_info_from_fd__audioio(mal_context* pContext, m
// Name.
mal_strcpy_s(pInfoOut->name, sizeof(pInfoOut->name), fdDevice.name);
+#if !defined(MAL_AUDIO4_USE_NEW_API)
// Supported formats. We get this by looking at the encodings.
int counter = 0;
for (;;) {
@@ -15886,7 +15766,7 @@ mal_result mal_context_get_device_info_from_fd__audioio(mal_context* pContext, m
break;
}
- mal_format format = mal_format_from_encoding__audioio(encoding.encoding, encoding.precision);
+ mal_format format = mal_format_from_encoding__audio4(encoding.encoding, encoding.precision);
if (format != mal_format_unknown) {
pInfoOut->formats[pInfoOut->formatCount++] = format;
}
@@ -15910,11 +15790,34 @@ mal_result mal_context_get_device_info_from_fd__audioio(mal_context* pContext, m
pInfoOut->minSampleRate = fdInfo.record.sample_rate;
pInfoOut->maxSampleRate = fdInfo.record.sample_rate;
}
+#else
+ struct audio_swpar fdPar;
+ if (ioctl(fd, AUDIO_GETPAR, &fdPar) < 0) {
+ return MAL_ERROR;
+ }
+
+ mal_format format = mal_format_from_swpar__audio4(&fdPar);
+ if (format == mal_format_unknown) {
+ return MAL_FORMAT_NOT_SUPPORTED;
+ }
+ pInfoOut->formats[pInfoOut->formatCount++] = format;
+
+ if (deviceType == mal_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 MAL_SUCCESS;
}
-mal_result mal_context_enumerate_devices__audioio(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData)
+mal_result mal_context_enumerate_devices__audio4(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData)
{
mal_assert(pContext != NULL);
mal_assert(callback != NULL);
@@ -15944,8 +15847,8 @@ mal_result mal_context_enumerate_devices__audioio(mal_context* pContext, mal_enu
// Supports playback.
mal_device_info deviceInfo;
mal_zero_object(&deviceInfo);
- mal_construct_device_id__audioio(deviceInfo.id.audioio, sizeof(deviceInfo.id.audioio), "/dev/audio", iDevice);
- if (mal_context_get_device_info_from_fd__audioio(pContext, mal_device_type_playback, fd, &deviceInfo) == MAL_SUCCESS) {
+ mal_construct_device_id__audio4(deviceInfo.id.audio4, sizeof(deviceInfo.id.audio4), "/dev/audio", iDevice);
+ if (mal_context_get_device_info_from_fd__audio4(pContext, mal_device_type_playback, fd, &deviceInfo) == MAL_SUCCESS) {
isTerminating = !callback(pContext, mal_device_type_playback, &deviceInfo, pUserData);
}
@@ -15960,8 +15863,8 @@ mal_result mal_context_enumerate_devices__audioio(mal_context* pContext, mal_enu
// Supports capture.
mal_device_info deviceInfo;
mal_zero_object(&deviceInfo);
- mal_construct_device_id__audioio(deviceInfo.id.audioio, sizeof(deviceInfo.id.audioio), "/dev/audio", iDevice);
- if (mal_context_get_device_info_from_fd__audioio(pContext, mal_device_type_capture, fd, &deviceInfo) == MAL_SUCCESS) {
+ mal_construct_device_id__audio4(deviceInfo.id.audio4, sizeof(deviceInfo.id.audio4), "/dev/audio", iDevice);
+ if (mal_context_get_device_info_from_fd__audio4(pContext, mal_device_type_capture, fd, &deviceInfo) == MAL_SUCCESS) {
isTerminating = !callback(pContext, mal_device_type_capture, &deviceInfo, pUserData);
}
@@ -15977,7 +15880,7 @@ mal_result mal_context_enumerate_devices__audioio(mal_context* pContext, mal_enu
return MAL_SUCCESS;
}
-mal_result mal_context_get_device_info__audioio(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo)
+mal_result mal_context_get_device_info__audio4(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo)
{
mal_assert(pContext != NULL);
(void)shareMode;
@@ -15992,12 +15895,12 @@ mal_result mal_context_get_device_info__audioio(mal_context* pContext, mal_devic
mal_strcpy_s(ctlid, sizeof(ctlid), "/dev/audioctl");
} else {
// Specific device. We need to convert from "/dev/audioN" to "/dev/audioctlN".
- mal_result result = mal_extract_device_index_from_id__audioio(pDeviceID->audioio, "/dev/audio", &deviceIndex);
+ mal_result result = mal_extract_device_index_from_id__audio4(pDeviceID->audio4, "/dev/audio", &deviceIndex);
if (result != MAL_SUCCESS) {
return result;
}
- mal_construct_device_id__audioio(ctlid, sizeof(ctlid), "/dev/audioctl", deviceIndex);
+ mal_construct_device_id__audio4(ctlid, sizeof(ctlid), "/dev/audioctl", deviceIndex);
}
fd = open(ctlid, (deviceType == mal_device_type_playback) ? O_WRONLY : O_RDONLY, 0);
@@ -16006,43 +15909,44 @@ mal_result mal_context_get_device_info__audioio(mal_context* pContext, mal_devic
}
if (deviceIndex == -1) {
- mal_strcpy_s(pDeviceInfo->id.audioio, sizeof(pDeviceInfo->id.audioio), "/dev/audio");
+ mal_strcpy_s(pDeviceInfo->id.audio4, sizeof(pDeviceInfo->id.audio4), "/dev/audio");
} else {
- mal_construct_device_id__audioio(pDeviceInfo->id.audioio, sizeof(pDeviceInfo->id.audioio), "/dev/audio", deviceIndex);
+ mal_construct_device_id__audio4(pDeviceInfo->id.audio4, sizeof(pDeviceInfo->id.audio4), "/dev/audio", deviceIndex);
}
- mal_result result = mal_context_get_device_info_from_fd__audioio(pContext, deviceType, fd, pDeviceInfo);
+ mal_result result = mal_context_get_device_info_from_fd__audio4(pContext, deviceType, fd, pDeviceInfo);
close(fd);
return result;
}
-void mal_device_uninit__audioio(mal_device* pDevice)
+void mal_device_uninit__audio4(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
- close(pDevice->audioio.fd);
- mal_free(pDevice->audioio.pIntermediaryBuffer);
+ close(pDevice->audio4.fd);
+ mal_free(pDevice->audio4.pIntermediaryBuffer);
}
-mal_result mal_device_init__audioio(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice)
+mal_result mal_device_init__audio4(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice)
{
(void)pContext;
mal_assert(pDevice != NULL);
- mal_zero_object(&pDevice->audioio);
+ mal_zero_object(&pDevice->audio4);
// The first thing to do is open the file.
const char* deviceName = "/dev/audio";
if (pDeviceID != NULL) {
- deviceName = pDeviceID->audioio;
+ deviceName = pDeviceID->audio4;
}
- pDevice->audioio.fd = open(deviceName, (deviceType == mal_device_type_playback) ? O_WRONLY : O_RDONLY, 0);
- if (pDevice->audioio.fd == -1) {
- return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audioio] Failed to open device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE);
+ pDevice->audio4.fd = open(deviceName, (deviceType == mal_device_type_playback) ? O_WRONLY : O_RDONLY, 0);
+ if (pDevice->audio4.fd == -1) {
+ return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to open device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE);
}
+#if !defined(MAL_AUDIO4_USE_NEW_API)
audio_info_t fdInfo;
AUDIO_INITINFO(&fdInfo);
@@ -16055,7 +15959,7 @@ mal_result mal_device_init__audioio(mal_context* pContext, mal_device_type devic
fdInfo.mode = AUMODE_RECORD;
}
- // Format. Note that it looks like audioio does not support floating point formats. In this case
+ // Format. Note that it looks like audio4 does not support floating point formats. In this case
// we just fall back to s16.
switch (pDevice->format)
{
@@ -16090,8 +15994,8 @@ mal_result mal_device_init__audioio(mal_context* pContext, mal_device_type devic
mal_device_info nativeInfo;
mal_result result = mal_context_get_device_info(pContext, deviceType, pDeviceID, pConfig->shareMode, &nativeInfo);
if (result != MAL_SUCCESS) {
- close(pDevice->audioio.fd);
- return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audioio] Failed to retrieve device format.", result);
+ close(pDevice->audio4.fd);
+ return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve device format.", result);
}
prinfo->channels = nativeInfo.maxChannels;
@@ -16099,20 +16003,20 @@ mal_result mal_device_init__audioio(mal_context* pContext, mal_device_type devic
// We need to apply the settings so far so we can get back the actual sample rate which we need for calculating
// the default buffer size below.
- if (ioctl(pDevice->audioio.fd, AUDIO_SETINFO, &fdInfo) < 0) {
- close(pDevice->audioio.fd);
- return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audioio] Failed to set device format. AUDIO_SETINFO failed.", MAL_FORMAT_NOT_SUPPORTED);
+ if (ioctl(pDevice->audio4.fd, AUDIO_SETINFO, &fdInfo) < 0) {
+ close(pDevice->audio4.fd);
+ return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to set device format. AUDIO_SETINFO failed.", MAL_FORMAT_NOT_SUPPORTED);
}
- if (ioctl(pDevice->audioio.fd, AUDIO_GETINFO, &fdInfo) < 0) {
- close(pDevice->audioio.fd);
- return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audioio] AUDIO_GETINFO failed.", MAL_FORMAT_NOT_SUPPORTED);
+ if (ioctl(pDevice->audio4.fd, AUDIO_GETINFO, &fdInfo) < 0) {
+ close(pDevice->audio4.fd);
+ return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] AUDIO_GETINFO failed.", MAL_FORMAT_NOT_SUPPORTED);
}
- pDevice->internalFormat = mal_format_from_prinfo__audioio(prinfo);
+ pDevice->internalFormat = mal_format_from_prinfo__audio4(prinfo);
if (pDevice->internalFormat == mal_format_unknown) {
- close(pDevice->audioio.fd);
- return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audioio] The device's internal device format is not supported by mini_al. The device is unusable.", MAL_FORMAT_NOT_SUPPORTED);
+ close(pDevice->audio4.fd);
+ return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by mini_al. The device is unusable.", MAL_FORMAT_NOT_SUPPORTED);
}
pDevice->internalChannels = prinfo->channels;
@@ -16121,20 +16025,11 @@ mal_result mal_device_init__audioio(mal_context* pContext, mal_device_type devic
// Try calculating an appropriate default buffer size.
- if (pDevice->usingDefaultBufferSize) {
- // CPU speed factor.
- float fCPUSpeed = mal_calculate_cpu_speed_factor();
-
- // Playback vs capture latency.
- float fDeviceType = 1;
-
- // Backend tax.
- float fBackend = 1;
-
- pDevice->bufferSizeInFrames = mal_calculate_default_buffer_size_in_frames(pConfig->performanceProfile, pDevice->internalSampleRate, fCPUSpeed*fDeviceType*fBackend);
+ if (pDevice->bufferSizeInFrames == 0) {
+ pDevice->bufferSizeInFrames = mal_calculate_buffer_size_in_frames_from_milliseconds(pDevice->bufferSizeInMilliseconds, pDevice->internalSampleRate);
}
- // What mini_al calls a fragment, audioio calls a block.
+ // What mini_al calls a fragment, audio4 calls a block.
mal_uint32 fragmentSizeInBytes = pDevice->bufferSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
if (fragmentSizeInBytes < 16) {
fragmentSizeInBytes = 16;
@@ -16142,38 +16037,90 @@ mal_result mal_device_init__audioio(mal_context* pContext, mal_device_type devic
AUDIO_INITINFO(&fdInfo);
- fdInfo.blocksize = fragmentSizeInBytes;
fdInfo.hiwat = mal_max(pDevice->periods, 5);
fdInfo.lowat = (unsigned int)(fdInfo.hiwat * 0.75);
- if (ioctl(pDevice->audioio.fd, AUDIO_SETINFO, &fdInfo) < 0) {
- close(pDevice->audioio.fd);
- return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audioio] Failed to set internal buffer size. AUDIO_SETINFO failed.", MAL_FORMAT_NOT_SUPPORTED);
+ fdInfo.blocksize = fragmentSizeInBytes / fdInfo.hiwat;
+ if (ioctl(pDevice->audio4.fd, AUDIO_SETINFO, &fdInfo) < 0) {
+ close(pDevice->audio4.fd);
+ return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to set internal buffer size. AUDIO_SETINFO failed.", MAL_FORMAT_NOT_SUPPORTED);
}
pDevice->periods = fdInfo.hiwat;
pDevice->bufferSizeInFrames = (fdInfo.blocksize * fdInfo.hiwat) / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
-
- pDevice->audioio.fragmentSizeInFrames = fdInfo.blocksize / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
+ pDevice->audio4.fragmentSizeInFrames = fdInfo.blocksize / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->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.
+ struct audio_swpar fdPar;
+ if (ioctl(pDevice->audio4.fd, AUDIO_GETPAR, &fdPar) < 0) {
+ close(pDevice->audio4.fd);
+ return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve initial device parameters.", MAL_FORMAT_NOT_SUPPORTED);
+ }
+
+ // Set the initial internal formats so we can do calculations below.
+ pDevice->internalFormat = mal_format_from_swpar__audio4(&fdPar);
+ if (deviceType == mal_device_type_playback) {
+ pDevice->internalChannels = fdPar.pchan;
+ } else {
+ pDevice->internalChannels = fdPar.rchan;
+ }
+ pDevice->internalSampleRate = fdPar.rate;
+
+ if (pDevice->bufferSizeInFrames == 0) {
+ pDevice->bufferSizeInFrames = mal_calculate_buffer_size_in_frames_from_milliseconds(pDevice->bufferSizeInMilliseconds, pDevice->internalSampleRate);
+ }
+
+ // What mini_al calls a fragment, audio4 calls a block.
+ mal_uint32 bufferSizeInBytes = pDevice->bufferSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
+ if (bufferSizeInBytes < 16) {
+ bufferSizeInBytes = 16;
+ }
+
+ fdPar.nblks = pDevice->periods;
+ fdPar.round = bufferSizeInBytes / fdPar.nblks;
+
+ if (ioctl(pDevice->audio4.fd, AUDIO_SETPAR, &fdPar) < 0) {
+ close(pDevice->audio4.fd);
+ return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to set device parameters.", MAL_FORMAT_NOT_SUPPORTED);
+ }
+
+ if (ioctl(pDevice->audio4.fd, AUDIO_GETPAR, &fdPar) < 0) {
+ close(pDevice->audio4.fd);
+ return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve actual device parameters.", MAL_FORMAT_NOT_SUPPORTED);
+ }
+
+ pDevice->internalFormat = mal_format_from_swpar__audio4(&fdPar);
+ if (deviceType == mal_device_type_playback) {
+ pDevice->internalChannels = fdPar.pchan;
+ } else {
+ pDevice->internalChannels = fdPar.rchan;
+ }
+ pDevice->internalSampleRate = fdPar.rate;
+
+ pDevice->periods = fdPar.nblks;
+ pDevice->bufferSizeInFrames = (fdPar.nblks * fdPar.round) / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
+ pDevice->audio4.fragmentSizeInFrames = fdPar.round / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
+#endif
// For the channel map, I'm not sure how to query the channel map (or if it's even possible). I'm just
- // using mini_al's default channel map for now.
- mal_get_standard_channel_map(mal_standard_channel_map_default, pDevice->internalChannels, pDevice->internalChannelMap);
+ // using the channels defined in FreeBSD's sound(4) man page.
+ mal_get_standard_channel_map(mal_standard_channel_map_sound4, pDevice->internalChannels, pDevice->internalChannelMap);
// When not using MMAP mode we need to use an intermediary buffer to the data transfer between the client
// and device. Everything is done by the size of a fragment.
- pDevice->audioio.pIntermediaryBuffer = mal_malloc(fdInfo.blocksize);
- if (pDevice->audioio.pIntermediaryBuffer == NULL) {
- close(pDevice->audioio.fd);
- return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audioio] Failed to allocate memory for intermediary buffer.", MAL_OUT_OF_MEMORY);
+ pDevice->audio4.pIntermediaryBuffer = mal_malloc(pDevice->audio4.fragmentSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels));
+ if (pDevice->audio4.pIntermediaryBuffer == NULL) {
+ close(pDevice->audio4.fd);
+ return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to allocate memory for intermediary buffer.", MAL_OUT_OF_MEMORY);
}
return MAL_SUCCESS;
}
-mal_result mal_device__start_backend__audioio(mal_device* pDevice)
+mal_result mal_device__start_backend__audio4(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
@@ -16184,11 +16131,11 @@ mal_result mal_device__start_backend__audioio(mal_device* pDevice)
if (pDevice->type == mal_device_type_playback) {
// Playback. Need to load the entire buffer, which means we need to write a fragment for each period.
for (mal_uint32 iPeriod = 0; iPeriod < pDevice->periods; iPeriod += 1) {
- mal_device__read_frames_from_client(pDevice, pDevice->audioio.fragmentSizeInFrames, pDevice->audioio.pIntermediaryBuffer);
+ mal_device__read_frames_from_client(pDevice, pDevice->audio4.fragmentSizeInFrames, pDevice->audio4.pIntermediaryBuffer);
- int bytesWritten = write(pDevice->audioio.fd, pDevice->audioio.pIntermediaryBuffer, pDevice->audioio.fragmentSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels));
+ int bytesWritten = write(pDevice->audio4.fd, pDevice->audio4.pIntermediaryBuffer, pDevice->audio4.fragmentSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels));
if (bytesWritten == -1) {
- return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audioio] Failed to send initial chunk of data to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE);
+ return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to send initial chunk of data to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE);
}
}
} else {
@@ -16198,31 +16145,37 @@ mal_result mal_device__start_backend__audioio(mal_device* pDevice)
return MAL_SUCCESS;
}
-mal_result mal_device__stop_backend__audioio(mal_device* pDevice)
+mal_result mal_device__stop_backend__audio4(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
- if (ioctl(pDevice->audioio.fd, AUDIO_FLUSH, 0) < 0) {
- return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audioio] Failed to stop device. AUDIO_FLUSH failed.", MAL_FAILED_TO_STOP_BACKEND_DEVICE);
+#if defined(__NetBSD__)
+ if (ioctl(pDevice->audio4.fd, AUDIO_FLUSH, 0) < 0) {
+ return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to stop device. AUDIO_FLUSH failed.", MAL_FAILED_TO_STOP_BACKEND_DEVICE);
}
+#else
+ if (ioctl(pDevice->audio4.fd, AUDIO_STOP, 0) < 0) {
+ return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to stop device. AUDIO_FLUSH failed.", MAL_FAILED_TO_STOP_BACKEND_DEVICE);
+ }
+#endif
return MAL_SUCCESS;
}
-mal_result mal_device__break_main_loop__audioio(mal_device* pDevice)
+mal_result mal_device__break_main_loop__audio4(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
- pDevice->audioio.breakFromMainLoop = MAL_TRUE;
+ pDevice->audio4.breakFromMainLoop = MAL_TRUE;
return MAL_SUCCESS;
}
-mal_result mal_device__main_loop__audioio(mal_device* pDevice)
+mal_result mal_device__main_loop__audio4(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
- pDevice->audioio.breakFromMainLoop = MAL_FALSE;
- while (!pDevice->audioio.breakFromMainLoop) {
+ pDevice->audio4.breakFromMainLoop = MAL_FALSE;
+ while (!pDevice->audio4.breakFromMainLoop) {
// Break from the main loop if the device isn't started anymore. Likely what's happened is the application
// has requested that the device be stopped.
if (!mal_device_is_started(pDevice)) {
@@ -16231,54 +16184,54 @@ mal_result mal_device__main_loop__audioio(mal_device* pDevice)
if (pDevice->type == mal_device_type_playback) {
// Playback.
- mal_device__read_frames_from_client(pDevice, pDevice->audioio.fragmentSizeInFrames, pDevice->audioio.pIntermediaryBuffer);
+ mal_device__read_frames_from_client(pDevice, pDevice->audio4.fragmentSizeInFrames, pDevice->audio4.pIntermediaryBuffer);
- int bytesWritten = write(pDevice->audioio.fd, pDevice->audioio.pIntermediaryBuffer, pDevice->audioio.fragmentSizeInFrames * pDevice->internalChannels * mal_get_bytes_per_sample(pDevice->internalFormat));
+ int bytesWritten = write(pDevice->audio4.fd, pDevice->audio4.pIntermediaryBuffer, pDevice->audio4.fragmentSizeInFrames * pDevice->internalChannels * mal_get_bytes_per_sample(pDevice->internalFormat));
if (bytesWritten < 0) {
- return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audioio] Failed to send data from the client to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE);
+ return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to send data from the client to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE);
}
} else {
// Capture.
- int bytesRead = read(pDevice->audioio.fd, pDevice->audioio.pIntermediaryBuffer, pDevice->audioio.fragmentSizeInFrames * mal_get_bytes_per_sample(pDevice->internalFormat));
+ int bytesRead = read(pDevice->audio4.fd, pDevice->audio4.pIntermediaryBuffer, pDevice->audio4.fragmentSizeInFrames * mal_get_bytes_per_sample(pDevice->internalFormat));
if (bytesRead < 0) {
- return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audioio] Failed to read data from the device to be sent to the client.", MAL_FAILED_TO_READ_DATA_FROM_DEVICE);
+ return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to read data from the device to be sent to the client.", MAL_FAILED_TO_READ_DATA_FROM_DEVICE);
}
mal_uint32 framesRead = (mal_uint32)bytesRead / pDevice->internalChannels / mal_get_bytes_per_sample(pDevice->internalFormat);
- mal_device__send_frames_to_client(pDevice, framesRead, pDevice->audioio.pIntermediaryBuffer);
+ mal_device__send_frames_to_client(pDevice, framesRead, pDevice->audio4.pIntermediaryBuffer);
}
}
return MAL_SUCCESS;
}
-mal_result mal_context_uninit__audioio(mal_context* pContext)
+mal_result mal_context_uninit__audio4(mal_context* pContext)
{
mal_assert(pContext != NULL);
- mal_assert(pContext->backend == mal_backend_audioio);
+ mal_assert(pContext->backend == mal_backend_audio4);
(void)pContext;
return MAL_SUCCESS;
}
-mal_result mal_context_init__audioio(mal_context* pContext)
+mal_result mal_context_init__audio4(mal_context* pContext)
{
mal_assert(pContext != NULL);
- pContext->onUninit = mal_context_uninit__audioio;
- pContext->onDeviceIDEqual = mal_context_is_device_id_equal__audioio;
- pContext->onEnumDevices = mal_context_enumerate_devices__audioio;
- pContext->onGetDeviceInfo = mal_context_get_device_info__audioio;
- pContext->onDeviceInit = mal_device_init__audioio;
- pContext->onDeviceUninit = mal_device_uninit__audioio;
- pContext->onDeviceStart = mal_device__start_backend__audioio;
- pContext->onDeviceStop = mal_device__stop_backend__audioio;
- pContext->onDeviceBreakMainLoop = mal_device__break_main_loop__audioio;
- pContext->onDeviceMainLoop = mal_device__main_loop__audioio;
+ pContext->onUninit = mal_context_uninit__audio4;
+ pContext->onDeviceIDEqual = mal_context_is_device_id_equal__audio4;
+ pContext->onEnumDevices = mal_context_enumerate_devices__audio4;
+ pContext->onGetDeviceInfo = mal_context_get_device_info__audio4;
+ pContext->onDeviceInit = mal_device_init__audio4;
+ pContext->onDeviceUninit = mal_device_uninit__audio4;
+ pContext->onDeviceStart = mal_device__start_backend__audio4;
+ pContext->onDeviceStop = mal_device__stop_backend__audio4;
+ pContext->onDeviceBreakMainLoop = mal_device__break_main_loop__audio4;
+ pContext->onDeviceMainLoop = mal_device__main_loop__audio4;
return MAL_SUCCESS;
}
-#endif // audioio
+#endif // audio4
///////////////////////////////////////////////////////////////////////////////
@@ -16574,22 +16527,8 @@ mal_result mal_device_init__oss(mal_context* pContext, mal_device_type type, con
// Try calculating an appropriate default buffer size.
- if (pDevice->usingDefaultBufferSize) {
- // CPU speed is a factor to consider when determine how large of a buffer we need.
- float fCPUSpeed = mal_calculate_cpu_speed_factor();
-
- // In my testing, capture seems to have worse latency than playback for some reason.
- float fType;
- if (type == mal_device_type_playback) {
- fType = 1.0f;
- } else {
- fType = 2.0f;
- }
-
- // Backend tax. Need to fiddle with this.
- float fBackend = 1.0;
-
- pDevice->bufferSizeInFrames = mal_calculate_default_buffer_size_in_frames(pConfig->performanceProfile, pConfig->sampleRate, fCPUSpeed*fType*fBackend);
+ if (pDevice->bufferSizeInFrames == 0) {
+ pDevice->bufferSizeInFrames = mal_calculate_buffer_size_in_frames_from_milliseconds(pDevice->bufferSizeInMilliseconds, pDevice->internalSampleRate);
}
// The documentation says that the fragment settings should be set as soon as possible, but I'm not sure if
@@ -16621,8 +16560,8 @@ mal_result mal_device_init__oss(mal_context* pContext, mal_device_type type, con
pDevice->periods = (mal_uint32)(ossFragment >> 16);
pDevice->bufferSizeInFrames = (mal_uint32)(pDevice->oss.fragmentSizeInFrames * pDevice->periods);
- // Set the internal channel map. Not sure if this can be queried. For now just using our default assumptions.
- mal_get_standard_channel_map(mal_standard_channel_map_default, pDevice->internalChannels, pDevice->internalChannelMap);
+ // Set the internal channel map. Not sure if this can be queried. For now just using the channel layouts defined in FreeBSD's sound(4) man page.
+ mal_get_standard_channel_map(mal_standard_channel_map_sound4, pDevice->internalChannels, pDevice->internalChannelMap);
// When not using MMAP mode, we need to use an intermediary buffer for the client <-> device transfer. We do
@@ -17215,29 +17154,6 @@ mal_result mal_device_init__opensl(mal_context* pContext, mal_device_type type,
mal_assert(pDevice != NULL);
mal_zero_object(&pDevice->opensl);
- // Try calculating an appropriate default buffer size.
- if (pDevice->usingDefaultBufferSize) {
- // CPU speed is a factor to consider when determine how large of a buffer we need.
- float fCPUSpeed = mal_calculate_cpu_speed_factor();
-
- // In my testing, capture seems to have worse latency than playback for some reason.
- float fType;
- if (type == mal_device_type_playback) {
- fType = 1.0f;
- } else {
- fType = 2.0f;
- }
-
- // Backend tax. Need to fiddle with this.
- float fBackend = 1.5f;
-
- pDevice->bufferSizeInFrames = mal_calculate_default_buffer_size_in_frames(pConfig->performanceProfile, pConfig->sampleRate, fCPUSpeed*fType*fBackend);
- }
-
- pDevice->opensl.currentBufferIndex = 0;
- pDevice->opensl.periodSizeInFrames = pDevice->bufferSizeInFrames / pConfig->periods;
- pDevice->bufferSizeInFrames = pDevice->opensl.periodSizeInFrames * pConfig->periods;
-
SLDataLocator_AndroidSimpleBufferQueue queue;
queue.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
queue.numBuffers = pConfig->periods;
@@ -17461,6 +17377,14 @@ mal_result mal_device_init__opensl(mal_context* pContext, mal_device_type type,
pDevice->internalSampleRate = pFormat->samplesPerSec / 1000;
mal_channel_mask_to_channel_map__opensl(pFormat->channelMask, pDevice->internalChannels, pDevice->internalChannelMap);
+ // Try calculating an appropriate default buffer size.
+ if (pDevice->bufferSizeInFrames == 0) {
+ pDevice->bufferSizeInFrames = mal_calculate_buffer_size_in_frames_from_milliseconds(pDevice->bufferSizeInMilliseconds, pDevice->internalSampleRate);
+ }
+
+ pDevice->opensl.currentBufferIndex = 0;
+ pDevice->opensl.periodSizeInFrames = pDevice->bufferSizeInFrames / pConfig->periods;
+ pDevice->bufferSizeInFrames = pDevice->opensl.periodSizeInFrames * pConfig->periods;
size_t bufferSizeInBytes = pDevice->bufferSizeInFrames * pDevice->internalChannels * mal_get_bytes_per_sample(pDevice->internalFormat);
pDevice->opensl.pBuffer = (mal_uint8*)mal_malloc(bufferSizeInBytes);
@@ -17973,22 +17897,12 @@ mal_result mal_device_init__openal(mal_context* pContext, mal_device_type type,
}
// Try calculating an appropriate default buffer size.
- if (pDevice->usingDefaultBufferSize) {
- // CPU speed is a factor to consider when determine how large of a buffer we need.
- float fCPUSpeed = mal_calculate_cpu_speed_factor();
-
- // In my testing, capture seems to have worse latency than playback for some reason.
- float fType;
- if (type == mal_device_type_playback) {
- fType = 1.0f;
- } else {
- fType = 2.0f;
+ if (pDevice->bufferSizeInFrames == 0) {
+ pDevice->bufferSizeInFrames = mal_calculate_buffer_size_in_frames_from_milliseconds(pDevice->bufferSizeInMilliseconds, pDevice->sampleRate);
+ if (pDevice->usingDefaultBufferSize) {
+ float bufferSizeScaleFactor = 3;
+ pDevice->bufferSizeInFrames = mal_scale_buffer_size(pDevice->bufferSizeInFrames, bufferSizeScaleFactor);
}
-
- // Backend tax. Need to fiddle with this. OpenAL has bad latency in my testing :(
- float fBackend = 8.0;
-
- pDevice->bufferSizeInFrames = mal_calculate_default_buffer_size_in_frames(pConfig->performanceProfile, pConfig->sampleRate, fCPUSpeed*fType*fBackend);
}
mal_ALCsizei bufferSizeInSamplesAL = pDevice->bufferSizeInFrames;
@@ -18905,32 +18819,13 @@ mal_result mal_device_init__sdl(mal_context* pContext, mal_device_type type, con
(void)pContext;
+ if (pDevice->bufferSizeInFrames == 0) {
+ pDevice->bufferSizeInFrames = mal_calculate_buffer_size_in_frames_from_milliseconds(pDevice->bufferSizeInMilliseconds, pDevice->sampleRate);
+ }
+
// SDL wants the buffer size to be a power of 2. The SDL_AudioSpec property for this is only a Uint16, so we need
// to explicitly clamp this because it will be easy to overflow.
mal_uint32 bufferSize = pConfig->bufferSizeInFrames;
- if (pDevice->usingDefaultBufferSize) {
- // CPU speed is a factor to consider when determine how large of a buffer we need.
- float fCPUSpeed = mal_calculate_cpu_speed_factor();
-
- // In my testing, capture seems to have worse latency than playback for some reason.
- float fType;
- if (type == mal_device_type_playback) {
- fType = 1.0f;
- } else {
- fType = 2.0f;
- }
-
- // Backend tax. Need to fiddle with this. Keep in mind that SDL always rounds the buffer size up to the next
- // power of two which should cover the natural API overhead. Special case for Emscripten.
- #if defined(__EMSCRIPTEN__)
- float fBackend = 1.0f;
- #else
- float fBackend = 1.0f;
- #endif
-
- bufferSize = mal_calculate_default_buffer_size_in_frames(pConfig->performanceProfile, pConfig->sampleRate, fCPUSpeed*fType*fBackend);
- }
-
if (bufferSize > 32768) {
bufferSize = 32768;
} else {
@@ -18992,10 +18887,11 @@ mal_result mal_device_init__sdl(mal_context* pContext, mal_device_type type, con
pDevice->internalFormat = mal_format_from_sdl(obtainedSpec.format);
pDevice->internalChannels = obtainedSpec.channels;
pDevice->internalSampleRate = (mal_uint32)obtainedSpec.freq;
+ mal_get_standard_channel_map(mal_standard_channel_map_default, pDevice->internalChannels, pDevice->internalChannelMap);
pDevice->bufferSizeInFrames = obtainedSpec.samples;
pDevice->periods = 1; // SDL doesn't seem to tell us what the period count is. Just set this 1.
-#if 0
+#ifdef MAL_DEBUG_OUTPUT
printf("=== SDL CONFIG ===\n");
printf("REQUESTED -> RECEIVED\n");
printf(" FORMAT: %s -> %s\n", mal_get_format_name(pConfig->format), mal_get_format_name(pDevice->internalFormat));
@@ -19173,7 +19069,7 @@ mal_thread_result MAL_THREADCALL mal_worker_thread(void* pData)
mal_assert(pDevice != NULL);
#ifdef MAL_WIN32
- mal_CoInitializeEx(pDevice->pContext, NULL, 0); // 0 = COINIT_MULTITHREADED
+ mal_CoInitializeEx(pDevice->pContext, NULL, MAL_COINIT_VALUE);
#endif
#if 1
@@ -19353,7 +19249,7 @@ mal_result mal_context_init_backend_apis__win32(mal_context* pContext)
pContext->win32.RegQueryValueExA = (mal_proc)mal_dlsym(pContext->win32.hAdvapi32DLL, "RegQueryValueExA");
#endif
- mal_CoInitializeEx(pContext, NULL, 0); // 0 = COINIT_MULTITHREADED
+ mal_CoInitializeEx(pContext, NULL, MAL_COINIT_VALUE);
return MAL_SUCCESS;
}
#else
@@ -19541,10 +19437,10 @@ mal_result mal_context_init(const mal_backend backends[], mal_uint32 backendCoun
result = mal_context_init__sndio(pContext);
} break;
#endif
- #ifdef MAL_HAS_AUDIOIO
- case mal_backend_audioio:
+ #ifdef MAL_HAS_AUDIO4
+ case mal_backend_audio4:
{
- result = mal_context_init__audioio(pContext);
+ result = mal_context_init__audio4(pContext);
} break;
#endif
#ifdef MAL_HAS_OSS
@@ -19827,11 +19723,13 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi
}
- // Default buffer size and periods.
- if (config.bufferSizeInFrames == 0) {
- config.bufferSizeInFrames = (config.sampleRate/1000) * MAL_BASE_BUFFER_SIZE_IN_MILLISECONDS_LOW_LATENCY;
+ // Default buffer size.
+ if (config.bufferSizeInMilliseconds == 0 && config.bufferSizeInFrames == 0) {
+ config.bufferSizeInMilliseconds = (config.performanceProfile == mal_performance_profile_low_latency) ? MAL_BASE_BUFFER_SIZE_IN_MILLISECONDS_LOW_LATENCY : MAL_BASE_BUFFER_SIZE_IN_MILLISECONDS_CONSERVATIVE;
pDevice->usingDefaultBufferSize = MAL_TRUE;
}
+
+ // Default periods.
if (config.periods == 0) {
config.periods = MAL_DEFAULT_PERIODS;
pDevice->usingDefaultPeriods = MAL_TRUE;
@@ -19843,6 +19741,7 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi
pDevice->sampleRate = config.sampleRate;
mal_copy_memory(pDevice->channelMap, config.channelMap, sizeof(config.channelMap[0]) * config.channels);
pDevice->bufferSizeInFrames = config.bufferSizeInFrames;
+ pDevice->bufferSizeInMilliseconds = config.bufferSizeInMilliseconds;
pDevice->periods = config.periods;
// The internal format, channel count and sample rate can be modified by the backend.
@@ -19903,6 +19802,13 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi
}
}
+
+ // Make sure the internal channel map was set correctly by the backend. If it's not valid, just fall back to defaults.
+ if (!mal_channel_map_valid(pDevice->internalChannels, pDevice->internalChannelMap)) {
+ mal_get_standard_channel_map(mal_standard_channel_map_default, pDevice->internalChannels, pDevice->internalChannelMap);
+ }
+
+
// If the format/channels/rate is using defaults we need to set these to be the same as the internal config.
if (pDevice->usingDefaultFormat) {
pDevice->format = pDevice->internalFormat;
@@ -19917,6 +19823,9 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi
mal_copy_memory(pDevice->channelMap, pDevice->internalChannelMap, sizeof(pDevice->channelMap));
}
+ // Buffer size. The backend will have set bufferSizeInFrames. We need to calculate bufferSizeInMilliseconds here.
+ pDevice->bufferSizeInMilliseconds = pDevice->bufferSizeInFrames / (pDevice->internalSampleRate/1000);
+
// We need a DSP object which is where samples are moved through in order to convert them to the
// format required by the backend.
@@ -20633,6 +20542,88 @@ void mal_get_standard_channel_map_vorbis(mal_uint32 channels, mal_channel channe
}
}
+void mal_get_standard_channel_map_sound4(mal_uint32 channels, mal_channel channelMap[MAL_MAX_CHANNELS])
+{
+ switch (channels)
+ {
+ case 1:
+ {
+ channelMap[0] = MAL_CHANNEL_MONO;
+ } break;
+
+ case 2:
+ {
+ channelMap[0] = MAL_CHANNEL_LEFT;
+ channelMap[1] = MAL_CHANNEL_RIGHT;
+ } break;
+
+ case 3:
+ {
+ channelMap[0] = MAL_CHANNEL_FRONT_LEFT;
+ channelMap[1] = MAL_CHANNEL_FRONT_RIGHT;
+ channelMap[2] = MAL_CHANNEL_BACK_CENTER;
+ } break;
+
+ case 4:
+ {
+ channelMap[0] = MAL_CHANNEL_FRONT_LEFT;
+ channelMap[1] = MAL_CHANNEL_FRONT_RIGHT;
+ channelMap[2] = MAL_CHANNEL_BACK_LEFT;
+ channelMap[3] = MAL_CHANNEL_BACK_RIGHT;
+ } break;
+
+ case 5:
+ {
+ channelMap[0] = MAL_CHANNEL_FRONT_LEFT;
+ channelMap[1] = MAL_CHANNEL_FRONT_RIGHT;
+ channelMap[2] = MAL_CHANNEL_BACK_LEFT;
+ channelMap[3] = MAL_CHANNEL_BACK_RIGHT;
+ channelMap[4] = MAL_CHANNEL_FRONT_CENTER;
+ } break;
+
+ case 6:
+ {
+ channelMap[0] = MAL_CHANNEL_FRONT_LEFT;
+ channelMap[1] = MAL_CHANNEL_FRONT_RIGHT;
+ channelMap[2] = MAL_CHANNEL_BACK_LEFT;
+ channelMap[3] = MAL_CHANNEL_BACK_RIGHT;
+ channelMap[4] = MAL_CHANNEL_FRONT_CENTER;
+ channelMap[5] = MAL_CHANNEL_LFE;
+ } break;
+
+ case 7:
+ {
+ channelMap[0] = MAL_CHANNEL_FRONT_LEFT;
+ channelMap[1] = MAL_CHANNEL_FRONT_RIGHT;
+ channelMap[2] = MAL_CHANNEL_BACK_LEFT;
+ channelMap[3] = MAL_CHANNEL_BACK_RIGHT;
+ channelMap[4] = MAL_CHANNEL_FRONT_CENTER;
+ channelMap[5] = MAL_CHANNEL_BACK_CENTER;
+ channelMap[6] = MAL_CHANNEL_LFE;
+ } break;
+
+ case 8:
+ default:
+ {
+ channelMap[0] = MAL_CHANNEL_FRONT_LEFT;
+ channelMap[1] = MAL_CHANNEL_FRONT_RIGHT;
+ channelMap[2] = MAL_CHANNEL_BACK_LEFT;
+ channelMap[3] = MAL_CHANNEL_BACK_RIGHT;
+ channelMap[4] = MAL_CHANNEL_FRONT_CENTER;
+ channelMap[5] = MAL_CHANNEL_LFE;
+ channelMap[6] = MAL_CHANNEL_SIDE_LEFT;
+ channelMap[7] = MAL_CHANNEL_SIDE_RIGHT;
+ } break;
+ }
+
+ // Remainder.
+ if (channels > 8) {
+ for (mal_uint32 iChannel = 8; iChannel < MAL_MAX_CHANNELS; ++iChannel) {
+ channelMap[iChannel] = (mal_channel)(MAL_CHANNEL_AUX_0 + (iChannel-8));
+ }
+ }
+}
+
void mal_get_standard_channel_map_sndio(mal_uint32 channels, mal_channel channelMap[MAL_MAX_CHANNELS])
{
switch (channels)
@@ -20715,6 +20706,11 @@ void mal_get_standard_channel_map(mal_standard_channel_map standardChannelMap, m
{
mal_get_standard_channel_map_vorbis(channels, channelMap);
} break;
+
+ case mal_standard_channel_map_sound4:
+ {
+ mal_get_standard_channel_map_sound4(channels, channelMap);
+ } break;
case mal_standard_channel_map_sndio:
{
@@ -27360,20 +27356,35 @@ mal_result mal_sine_wave_init(double amplitude, double periodsPerSecond, mal_uin
mal_uint64 mal_sine_wave_read(mal_sine_wave* pSineWave, mal_uint64 count, float* pSamples)
{
+ return mal_sine_wave_read_ex(pSineWave, count, 1, mal_stream_layout_interleaved, &pSamples);
+}
+
+mal_uint64 mal_sine_wave_read_ex(mal_sine_wave* pSineWave, mal_uint64 frameCount, mal_uint32 channels, mal_stream_layout layout, float** ppFrames)
+{
if (pSineWave == NULL) {
return 0;
}
- if (pSamples != NULL) {
- for (mal_uint64 i = 0; i < count; i += 1) {
- pSamples[i] = (float)(sin(pSineWave->time * pSineWave->periodsPerSecond) * pSineWave->amplitude);
+ if (ppFrames != NULL) {
+ for (mal_uint64 iFrame = 0; iFrame < frameCount; iFrame += 1) {
+ float s = (float)(sin(pSineWave->time * pSineWave->periodsPerSecond) * pSineWave->amplitude);
pSineWave->time += pSineWave->delta;
+
+ if (layout == mal_stream_layout_interleaved) {
+ for (mal_uint32 iChannel = 0; iChannel < channels; iChannel += 1) {
+ ppFrames[0][iFrame*channels + iChannel] = s;
+ }
+ } else {
+ for (mal_uint32 iChannel = 0; iChannel < channels; iChannel += 1) {
+ ppFrames[iChannel][iFrame] = s;
+ }
+ }
}
} else {
- pSineWave->time += pSineWave->delta * count;
+ pSineWave->time += pSineWave->delta * frameCount;
}
- return count;
+ return frameCount;
}
@@ -27383,12 +27394,20 @@ mal_uint64 mal_sine_wave_read(mal_sine_wave* pSineWave, mal_uint64 count, float*
// REVISION HISTORY
// ================
//
-// v0.8.5-rc - 2018-xx-xx
+// 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 audioio backend for NetBSD.
+// - 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:
diff --git a/src/models.c b/src/models.c
index b1abe66d..bf52b30a 100644
--- a/src/models.c
+++ b/src/models.c
@@ -5,10 +5,10 @@
* CONFIGURATION:
*
* #define SUPPORT_FILEFORMAT_OBJ
-* Selected desired fileformats to be supported for loading.
-*
* #define SUPPORT_FILEFORMAT_MTL
-* Selected desired fileformats to be supported for loading.
+* #define SUPPORT_FILEFORMAT_IQM
+* #define SUPPORT_FILEFORMAT_GLTF
+* Selected desired fileformats to be supported for model data loading.
*
* #define SUPPORT_MESH_GENERATION
* Support procedural mesh generation functions, uses external par_shapes.h library
@@ -48,8 +48,20 @@
#include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 2.1, 3.3+ or ES2
-#define PAR_SHAPES_IMPLEMENTATION
-#include "external/par_shapes.h" // Shapes 3d parametric generation
+#if defined(SUPPORT_FILEFORMAT_IQM)
+ #define RIQM_IMPLEMENTATION
+ #include "external/riqm.h" // IQM file format loading
+#endif
+
+#if defined(SUPPORT_FILEFORMAT_GLTF)
+ #define CGLTF_IMPLEMENTATION
+ #include "external/cgltf.h" // glTF file format loading
+#endif
+
+#if defined(SUPPORT_MESH_GENERATION)
+ #define PAR_SHAPES_IMPLEMENTATION
+ #include "external/par_shapes.h" // Shapes 3d parametric generation
+#endif
//----------------------------------------------------------------------------------
// Defines and Macros
@@ -75,6 +87,12 @@ static Mesh LoadOBJ(const char *fileName); // Load OBJ mesh data
#if defined(SUPPORT_FILEFORMAT_MTL)
static Material LoadMTL(const char *fileName); // Load MTL material 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
+#endif
//----------------------------------------------------------------------------------
// Module Functions Definition
@@ -699,7 +717,7 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ)
resZ++;
// Vertices definition
- int vertexCount = resX*resZ*6; // 6 vertex by quad
+ int vertexCount = resX*resZ; // vertices get reused for the faces
Vector3 *vertices = (Vector3 *)malloc(vertexCount*sizeof(Vector3));
for (int z = 0; z < resZ; z++)
@@ -2206,9 +2224,8 @@ void MeshBinormals(Mesh *mesh)
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 );
+ // Vector3 binormal = Vector3Multiply(Vector3CrossProduct(normal, tangent), tangentW);
}
}
@@ -2631,3 +2648,59 @@ static Material LoadMTL(const char *fileName)
return material;
}
#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)
+{
+ Mesh mesh = { 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;
+ }
+
+ fseek(gltfFile, 0, SEEK_END);
+ int size = ftell(gltfFile);
+ fseek(gltfFile, 0, SEEK_SET);
+
+ void *buffer = malloc(size);
+ fread(buffer, size, 1, gltfFile);
+
+ fclose(gltfFile);
+
+ // GLTF data loading
+ cgltf_options options = {0};
+ cgltf_data data;
+ 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);
+
+ free(buffer);
+ cgltf_free(&data);
+
+ return mesh;
+}
+#endif
diff --git a/src/raylib.h b/src/raylib.h
index af31e779..5be60836 100644
--- a/src/raylib.h
+++ b/src/raylib.h
@@ -136,14 +136,42 @@
#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_GRAVE 96
-#define KEY_SLASH 47
+#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
@@ -154,6 +182,8 @@
#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
@@ -412,6 +442,7 @@ typedef struct RenderTexture2D {
// RenderTexture type, same as RenderTexture2D
typedef RenderTexture2D RenderTexture;
+// N-Patch layout info
typedef struct NPatchInfo {
Rectangle sourceRec; // Region in the texture
int left; // left border offset
@@ -690,6 +721,13 @@ typedef enum {
WRAP_MIRROR
} 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
+} FontType;
+
// Color blending modes (pre-defined)
typedef enum {
BLEND_ALPHA = 0,
@@ -1025,7 +1063,7 @@ RLAPI void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle
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 CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int charsCount, bool sdf); // Load font data for further use
+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 fontSize, int charsCount, int padding, int packMethod); // Generate image font atlas using chars info
RLAPI void UnloadFont(Font font); // Unload Font from GPU memory (VRAM)
diff --git a/src/raymath.h b/src/raymath.h
index 073a47b2..33116532 100644
--- a/src/raymath.h
+++ b/src/raymath.h
@@ -155,6 +155,12 @@ RMDEF float Clamp(float value, float min, float max)
return res > max ? max : res;
}
+// Calculate linear interpolation between two vectors
+RMDEF float Lerp(float start, float end, float amount)
+{
+ return start + amount*(end - start);
+}
+
//----------------------------------------------------------------------------------
// Module Functions Definition - Vector2 math
//----------------------------------------------------------------------------------
@@ -258,6 +264,17 @@ RMDEF Vector2 Vector2Normalize(Vector2 v)
return result;
}
+// Calculate linear interpolation between two vectors
+RMDEF Vector2 Vector2Lerp(Vector2 v1, Vector2 v2, float amount)
+{
+ Vector2 result = { 0 };
+
+ result.x = v1.x + amount*(v2.x - v1.x);
+ result.y = v1.y + amount*(v2.y - v1.y);
+
+ return result;
+}
+
//----------------------------------------------------------------------------------
// Module Functions Definition - Vector3 math
//----------------------------------------------------------------------------------
diff --git a/src/rlgl.h b/src/rlgl.h
index 09f99ea2..75246cab 100644
--- a/src/rlgl.h
+++ b/src/rlgl.h
@@ -562,7 +562,7 @@ int GetPixelDataSize(int width, int height, int format);// Get pixel data size i
#define WINGDIAPI __declspec(dllimport)
#endif
- #include <GL/gl.h> // OpenGL 1.1 library
+ #include <GL/gl.h> // OpenGL 1.1 library
#endif
#endif
@@ -808,25 +808,25 @@ typedef struct VrStereoConfig {
static Matrix stack[MATRIX_STACK_SIZE];
static int stackCounter = 0;
-static Matrix modelview;
-static Matrix projection;
-static Matrix *currentMatrix;
-static int currentMatrixMode;
+static Matrix modelview = { 0 };
+static Matrix projection = { 0 };
+static Matrix *currentMatrix = NULL;
+static int currentMatrixMode = -1;
-static int currentDrawMode;
+static int currentDrawMode = -1;
static float currentDepth = -1.0f;
-static DynamicBuffer lines; // Default dynamic buffer for lines data
-static DynamicBuffer triangles; // Default dynamic buffer for triangles data
-static DynamicBuffer quads; // Default dynamic buffer for quads data (used to draw textures)
+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 buffers draw calls
-static DrawCall *draws;
-static int drawsCounter;
+static DrawCall *draws = NULL;
+static int drawsCounter = 0;
// Temp vertex buffer to be used with rlTranslate, rlRotate, rlScale
-static Vector3 *tempBuffer;
+static Vector3 *tempBuffer = NULL;
static int tempBufferCount = 0;
static bool useTempBuffer = false;
@@ -1211,15 +1211,27 @@ 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)) rlglDraw();
+ if ((lines.vCounter/2 >= (MAX_LINES_BATCH - 2)) ||
+ (triangles.vCounter/3 >= (MAX_TRIANGLES_BATCH - 3)) ||
+ (quads.vCounter/4 >= (MAX_QUADS_BATCH - 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();
+ rlglDraw();
+ }
}
// Define one vertex (position)
void rlVertex3f(float x, float y, float z)
{
- if (useTempBuffer)
+ // 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))
{
tempBuffer[tempBufferCount].x = x;
tempBuffer[tempBufferCount].y = y;
@@ -1381,11 +1393,7 @@ void rlEnableTexture(unsigned int id)
{
if (draws[drawsCounter - 1].vertexCount > 0) drawsCounter++;
- if (drawsCounter >= MAX_DRAWS_BY_TEXTURE)
- {
- rlglDraw();
- drawsCounter = 1;
- }
+ if (drawsCounter >= MAX_DRAWS_BY_TEXTURE) rlglDraw();
draws[drawsCounter - 1].textureId = id;
draws[drawsCounter - 1].vertexCount = 0;
@@ -1765,13 +1773,18 @@ void rlglInit(int width, int height)
for (int i = 0; i < MAX_DRAWS_BY_TEXTURE; i++)
{
- draws[i].textureId = 0;
draws[i].vertexCount = 0;
+ draws[i].vaoId = 0;
+ draws[i].shaderId = 0;
+ draws[i].textureId = 0;
+
+ draws[i].projection = MatrixIdentity();
+ draws[i].modelview = MatrixIdentity();
}
drawsCounter = 1;
- draws[drawsCounter - 1].textureId = whiteTexture;
- currentDrawMode = RL_TRIANGLES; // Set default draw mode
+ 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();
@@ -1828,6 +1841,7 @@ void rlglClose(void)
TraceLog(LOG_INFO, "[TEX ID %i] Unloaded texture data (base white texture) from VRAM", whiteTexture);
free(draws);
+ free(tempBuffer);
#endif
}
@@ -2732,7 +2746,7 @@ void *rlReadTexturePixels(Texture2D texture)
glPixelStorei(GL_PACK_ALIGNMENT, 1);
int glInternalFormat, glFormat, glType;
- GetGlFormats(texture.format, &glInternalFormat, &glFormat, &glType);
+ GetGlFormats(texture.format, &glInternalFormat, &glFormat, &glType);
unsigned int size = GetPixelDataSize(texture.width, texture.height, texture.format);
if ((glInternalFormat != -1) && (texture.format < COMPRESSED_DXT1_RGB))
@@ -2818,12 +2832,12 @@ 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;
- draws[drawsCounter].vertexCount = currentState.vertexCount;
drawsCounter++;
#endif
@@ -4271,8 +4285,6 @@ static void DrawBuffersDefault(void)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quads.vboId[3]);
}
- //TraceLog(LOG_DEBUG, "Draws required per frame: %i", drawsCounter);
-
for (int i = 0; i < drawsCounter; i++)
{
quadsCount = draws[i].vertexCount/4;
@@ -4309,11 +4321,6 @@ static void DrawBuffersDefault(void)
glUseProgram(0); // Unbind shader program
}
- // Reset draws counter
- drawsCounter = 1;
- draws[0].textureId = whiteTexture;
- draws[0].vertexCount = 0;
-
// Reset vertex counters for next frame
lines.vCounter = 0;
lines.cCounter = 0;
@@ -4329,6 +4336,11 @@ static void DrawBuffersDefault(void)
// Restore projection/modelview matrices
projection = matProjection;
modelview = matModelView;
+
+ // Reset draws counter
+ drawsCounter = 1;
+ draws[0].textureId = whiteTexture;
+ draws[0].vertexCount = 0;
}
// Unload default internal buffers vertex data from CPU and GPU
diff --git a/src/shapes.c b/src/shapes.c
index 833f1613..90f95826 100644
--- a/src/shapes.c
+++ b/src/shapes.c
@@ -647,11 +647,9 @@ bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2
bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2)
{
bool collision = false;
-
- float dx = (float)fabs((rec1.x + rec1.width/2) - (rec2.x + rec2.width/2));
- float dy = (float)fabs((rec1.y + rec1.height/2) - (rec2.y + rec2.height/2));
-
- if ((dx <= (rec1.width/2 + rec2.width/2)) && ((dy <= (rec1.height/2 + rec2.height/2)))) 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;
}
diff --git a/src/text.c b/src/text.c
index 9ed283b2..1e702c0e 100644
--- a/src/text.c
+++ b/src/text.c
@@ -221,7 +221,7 @@ 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.y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor));
defaultFont.chars[i].rec.width = (float)charsWidth[i];
defaultFont.chars[i].rec.height = (float)charsHeight;
@@ -234,7 +234,7 @@ extern void LoadDefaultFont(void)
testPosX = currentPosX;
defaultFont.chars[i].rec.x = (float)charsDivisor;
- defaultFont.chars[i].rec.y = (float)charsDivisor + currentLine*(charsHeight + charsDivisor);
+ defaultFont.chars[i].rec.y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor));
}
else currentPosX = testPosX;
@@ -244,7 +244,7 @@ extern void LoadDefaultFont(void)
defaultFont.chars[i].advanceX = 0;
}
- defaultFont.baseSize = (int) defaultFont.chars[0].rec.height;
+ defaultFont.baseSize = (int)defaultFont.chars[0].rec.height;
TraceLog(LOG_INFO, "[TEX ID %i] Default font loaded successfully", defaultFont.texture.id);
}
@@ -283,7 +283,7 @@ Font LoadFont(const char *fileName)
{
font.baseSize = DEFAULT_TTF_FONTSIZE;
font.charsCount = DEFAULT_TTF_NUMCHARS;
- font.chars = LoadFontData(fileName, font.baseSize, NULL, font.charsCount, false);
+ font.chars = LoadFontData(fileName, font.baseSize, NULL, font.charsCount, FONT_DEFAULT);
Image atlas = GenImageFontAtlas(font.chars, font.charsCount, font.baseSize, 4, 0);
font.texture = LoadTextureFromImage(atlas);
UnloadImage(atlas);
@@ -319,8 +319,8 @@ Font LoadFontEx(const char *fileName, int fontSize, int charsCount, int *fontCha
font.baseSize = fontSize;
font.charsCount = (charsCount > 0) ? charsCount : 95;
- font.chars = LoadFontData(fileName, font.baseSize, fontChars, font.charsCount, false);
- Image atlas = GenImageFontAtlas(font.chars, font.charsCount, font.baseSize, 0, 0);
+ font.chars = LoadFontData(fileName, font.baseSize, fontChars, font.charsCount, FONT_DEFAULT);
+ Image atlas = GenImageFontAtlas(font.chars, font.charsCount, font.baseSize, 2, 0);
font.texture = LoadTextureFromImage(atlas);
UnloadImage(atlas);
@@ -329,7 +329,7 @@ Font LoadFontEx(const char *fileName, int fontSize, int charsCount, int *fontCha
// 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, bool sdf)
+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
@@ -337,6 +337,8 @@ CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int c
#define SDF_ON_EDGE_VALUE 128
#define SDF_PIXEL_DIST_SCALE 64.0f
+ #define BITMAP_ALPHA_THRESHOLD 80
+
// In case no chars count provided, default to 95
charsCount = (charsCount > 0) ? charsCount : 95;
@@ -367,8 +369,6 @@ CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int c
// NOTE: ascent is equivalent to font baseline
int ascent, descent, lineGap;
stbtt_GetFontVMetrics(&fontInfo, &ascent, &descent, &lineGap);
- ascent *= (int) scaleFactor;
- descent *= (int) scaleFactor;
// Fill fontChars in case not provided externally
// NOTE: By default we fill charsCount consecutevely, starting at 32 (Space)
@@ -392,22 +392,33 @@ CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int c
// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be
// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide
- if (!sdf) chars[i].data = stbtt_GetCodepointBitmap(&fontInfo, scaleFactor, scaleFactor, ch, &chw, &chh, &chars[i].offsetX, &chars[i].offsetY);
+ 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 += ascent;
+ 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", ascent + chY1);
+ TraceLog(LOG_DEBUG, "Character offsetY: %i", (int)((float)ascent*scaleFactor) + chY1);
stbtt_GetCodepointHMetrics(&fontInfo, ch, &chars[i].advanceX, NULL);
- chars[i].advanceX *= (int) scaleFactor;
+ chars[i].advanceX *= scaleFactor;
}
free(fontBuffer);
@@ -887,7 +898,7 @@ static Font LoadImageFont(Image image, Color key, int firstChar)
spriteFont.chars[i].advanceX = 0;
}
- spriteFont.baseSize = (int) spriteFont.chars[0].rec.height;
+ spriteFont.baseSize = (int)spriteFont.chars[0].rec.height;
TraceLog(LOG_INFO, "Image file loaded correctly as Font");
diff --git a/src/textures.c b/src/textures.c
index 700b4be9..9e86af92 100644
--- a/src/textures.c
+++ b/src/textures.c
@@ -1555,7 +1555,9 @@ Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Co
// Define ImageFont struct? or include Image spritefont in Font struct?
Image imFont = GetTextureData(font.texture);
- ImageColorTint(&imFont, tint); // Apply color tint to font
+ 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);