diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Makefile | 8 | ||||
| -rw-r--r-- | src/core.c | 23 | ||||
| -rw-r--r-- | src/external/cgltf.h | 2023 | ||||
| -rw-r--r-- | src/external/mini_al.h | 1113 | ||||
| -rw-r--r-- | src/models.c | 89 | ||||
| -rw-r--r-- | src/raylib.h | 44 | ||||
| -rw-r--r-- | src/raymath.h | 17 | ||||
| -rw-r--r-- | src/rlgl.h | 78 | ||||
| -rw-r--r-- | src/shapes.c | 8 | ||||
| -rw-r--r-- | src/text.c | 39 | ||||
| -rw-r--r-- | src/textures.c | 4 |
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 @@ -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 //---------------------------------------------------------------------------------- @@ -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; } @@ -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); |
