diff options
author | Mitya Selivanov <automainint@guattari.tech> | 2025-04-27 21:38:47 +0200 |
---|---|---|
committer | Mitya Selivanov <automainint@guattari.tech> | 2025-04-27 21:38:47 +0200 |
commit | 8ce2615ecec92f30a7b014e6c09bab7b16b21d4f (patch) | |
tree | 86cc7377369c101440ffd24648c8d6dda8920cb2 | |
parent | b3cb112815b7892f3eaed092602f3b66bee102ff (diff) | |
download | reduced_system_layer-8ce2615ecec92f30a7b014e6c09bab7b16b21d4f.zip |
WAV file format
-rw-r--r-- | runtime.c | 518 |
1 files changed, 477 insertions, 41 deletions
@@ -16,6 +16,9 @@ // - Minimalistic feature set. For graphics, you have access to the // pixel buffer, and that's it. // +// - Self-contained. Everything is implemented in-house, no external +// build dependencies, very minimal runtime dependencies. +// // - No implicit control flow. No callbacks. You write your own // main and call everything explicitly. But the number of things // you have to call to do something is as little as possible. @@ -27,12 +30,10 @@ // // - Pascal_Snake_Case - Type name. // - snake_case - Non-type name. -// - UPPER_SNAKE_CASE - Macro or constant. -// - UPPER_Pascal_Snake_Case - Constant. -// -// - g_ prefix - Global variable name. -// - _ prefix - Name of a global variable that is not part of the user API. -// - _ suffix - Name of a procedure that is not part of the user API. +// - UPPER_SNAKE_CASE prefix - Macro or constant. +// - g_ prefix - Global variable name. +// - _ prefix - Name of a global variable that is not part of the user API. +// - _ suffix - Name of a procedure that is not part of the user API. // // Most procedures have long and descriptive names. // Some procedures have prefixes according to their domain. @@ -76,15 +77,18 @@ // - Chat // - Long term // - Utility -// - Improve microbenchmarks library // - Parsing // - Printing // - Logging // - Terminal colors -// - Big integer +// - Expandable memory buffer +// - [ ] Use only relative pointers +// - [ ] Handle out-of-memory errors +// - Improve microbenchmarks library // - Mersenne Twister 64 // - Arithmetic coding // - A* search +// - Big integer // - Graphics // - Vector math // - Bezier curves @@ -960,6 +964,8 @@ static u8 LOG_level = LOG_Level_Info; #if defined(__wasm__) void log_impl(i32 mode, i32 file_len, c8 const *file, i32 line, i32 func_len, c8 const *func, i32 text_len, c8 const *text); + +#ifndef LOG_trace #define LOG_trace() \ do { \ if (LOG_level >= LOG_Level_Trace) \ @@ -970,6 +976,9 @@ void log_impl(i32 mode, i32 file_len, c8 const *file, i32 line, i32 func_len, c8 sizeof(__func__) - 1, __func__, \ 0, NULL); \ } while (0) +#endif + +#ifndef LOG_print #define LOG_print(text_, ...) \ do { \ if (LOG_level >= LOG_Level_Info) { \ @@ -983,6 +992,25 @@ void log_impl(i32 mode, i32 file_len, c8 const *file, i32 line, i32 func_len, c8 len, text_); \ } \ } while (0) +#endif + +#ifndef LOG_verbose +#define LOG_verbose(text_, ...) \ + do { \ + if (LOG_level >= LOG_Level_Verbose) { \ + i32 len = 0; \ + while ((text_)[len] != '\0') ++len; \ + log_impl( \ + 1, \ + sizeof(__FILE__) - 1, __FILE__, \ + __LINE__, \ + sizeof(__func__) - 1, __func__, \ + len, text_); \ + } \ + } while (0) +#endif + +#ifndef LOG_error #define LOG_error(text_, ...) \ do { \ if (LOG_level >= LOG_Level_Error) { \ @@ -996,13 +1024,23 @@ void log_impl(i32 mode, i32 file_len, c8 const *file, i32 line, i32 func_len, c8 len, text_); \ } \ } while (0) -#else +#endif + +#endif // defined(__wasm__) + +#if !defined(__wasm__) + #include <stdio.h> + +#ifndef LOG_trace #define LOG_trace() \ do { \ if (LOG_level >= LOG_Level_Trace) \ fprintf(stdout, "%s:%d, %s\n", __FILE__, __LINE__, __func__); \ } while (0) +#endif + +#ifndef LOG_print #define LOG_print(...) \ do { \ if (LOG_level >= LOG_Level_Info) { \ @@ -1010,6 +1048,20 @@ void log_impl(i32 mode, i32 file_len, c8 const *file, i32 line, i32 func_len, c8 fprintf(stdout, "\n"); \ } \ } while (0) +#endif + +#ifndef LOG_verbose +#define LOG_verbose(...) \ + do { \ + if (LOG_level >= LOG_Level_Verbose) { \ + fprintf(stderr, "%s:%d, %s: ", __FILE__, __LINE__, __func__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + } \ + } while (0) +#endif + +#ifndef LOG_error #define LOG_error(...) \ do { \ if (LOG_level >= LOG_Level_Error) { \ @@ -1020,6 +1072,8 @@ void log_impl(i32 mode, i32 file_len, c8 const *file, i32 line, i32 func_len, c8 } while (0) #endif +#endif // !defined(__wasm__) + // ================================================================ // // BLAKE2B @@ -1045,12 +1099,14 @@ typedef struct { u8 depth; u32 leaf_length; u64 node_offset; + u8 node_depth; u8 inner_length; - u8 reserved[14]; - u8 salt[BLAKE2B_SALTBYTES]; - u8 personal[BLAKE2B_PERSONALBYTES]; -} Blake2b_Param; + u8 _align[14]; + + u8 salt [BLAKE2B_SALTBYTES]; + u8 personal [BLAKE2B_PERSONALBYTES]; +} Blake2b_Param_; typedef struct { u64 h[8]; @@ -1168,7 +1224,7 @@ static i32 blake2b_init0_(Blake2b_State *S) { return 0; } -static i32 blake2b_init_param_(Blake2b_State *S, Blake2b_Param *P) { +static i32 blake2b_init_param_(Blake2b_State *S, Blake2b_Param_ *P) { blake2b_init0_( S ); u8 *p = ( u8 * )( P ); u64 i; @@ -1180,7 +1236,7 @@ static i32 blake2b_init_param_(Blake2b_State *S, Blake2b_Param *P) { } i32 blake2b_init(Blake2b_State *S, u8 outlen) { - Blake2b_Param P[1]; + Blake2b_Param_ P[1]; if ( ( !outlen ) || ( outlen > BLAKE2B_OUTBYTES ) ) return -1; @@ -1192,7 +1248,7 @@ i32 blake2b_init(Blake2b_State *S, u8 outlen) { store64_( &P->node_offset, 0 ); P->node_depth = 0; P->inner_length = 0; - mem_set_( P->reserved, 0, sizeof( P->reserved ) ); + mem_set_( P->_align, 0, sizeof( P->_align) ); mem_set_( P->salt, 0, sizeof( P->salt ) ); mem_set_( P->personal, 0, sizeof( P->personal ) ); return blake2b_init_param_( S, P ); @@ -1286,7 +1342,7 @@ i32 blake2b_update(Blake2b_State *S, u8 *in, u64 inlen) { } i32 blake2b_init_key(Blake2b_State *S, u8 outlen, void *key, u8 keylen) { - Blake2b_Param P[1]; + Blake2b_Param_ P[1]; if ( ( !outlen ) || ( outlen > BLAKE2B_OUTBYTES ) ) return -1; @@ -1300,7 +1356,7 @@ i32 blake2b_init_key(Blake2b_State *S, u8 outlen, void *key, u8 keylen) { store64_( &P->node_offset, 0 ); P->node_depth = 0; P->inner_length = 0; - mem_set_( P->reserved, 0, sizeof( P->reserved ) ); + mem_set_( P->_align, 0, sizeof( P->_align) ); mem_set_( P->salt, 0, sizeof( P->salt ) ); mem_set_( P->personal, 0, sizeof( P->personal ) ); @@ -2497,8 +2553,8 @@ b8 bmp_read_from_memory(i64 *width, i64 *height, i64 pixels_len, vec4_f32 *pixel // Only validate headers. return 1; - *width = image_width; - *height = image_height; + if (width != NULL) *width = image_width; + if (height != NULL) *height = image_height; if (pixels_len == 0 && pixels == NULL) // Only validate headers and return width and height. @@ -2792,8 +2848,8 @@ b8 ppm_read_from_memory(i64 *width, i64 *height, i64 pixels_len, vec4_f32 *pixel return 0; } - *width = image_width; - *height = image_height; + if (width != NULL) *width = image_width; + if (height != NULL) *height = image_height; if (pixels_len == 0 && pixels == NULL) // Only validate format and return width and height. @@ -3019,33 +3075,413 @@ b8 ppm_write_to_memory(i64 *dst_len, u8 *dst, i64 width, i64 height, i64 stride, return 1; } +static b8 u16_from_(u16 *dst, u8 *src, u8 *eof) { + if (dst == NULL || src == NULL || eof == NULL) { + LOG_error("Sanity"); + return 0; + } + + if (src + sizeof *dst > eof) + return 0; + + mem_cpy_(dst, src, sizeof *dst); + return 1; +} + +static b8 u32_from_(u32 *dst, u8 *src, u8 *eof) { + if (dst == NULL || src == NULL || eof == NULL) { + LOG_error("Sanity"); + return 0; + } + + if (src + sizeof *dst > eof) + return 0; + + mem_cpy_(dst, src, sizeof *dst); + return 1; +} + +static b8 u64_from_(u64 *dst, u8 *src, u8 *eof) { + if (dst == NULL || src == NULL || eof == NULL) { + LOG_error("Sanity"); + return 0; + } + + if (src + sizeof *dst > eof) + return 0; + + mem_cpy_(dst, src, sizeof *dst); + return 1; +} + +#define RIFF_Id_ ('R' | ('I' << 8) | ('F' << 16) | ('F' << 24)) +#define WAVE_Id_ ('W' | ('A' << 8) | ('V' << 16) | ('E' << 24)) + +#define WAVE_CHUNK_fmt_ ('f' | ('m' << 8) | ('t' << 16) | (' ' << 24)) +#define WAVE_CHUNK_data_ ('d' | ('a' << 8) | ('t' << 16) | ('a' << 24)) + +enum { + WAVE_Format_Integer_ = 1, + WAVE_Format_Float_ = 3, +}; + b8 wav_read_from_memory(i64 *sample_rate_hz, i64 *num_channels, i64 *num_samples, i64 frames_len, f32 *frames, i64 src_len, u8 *src) { - // TODO + if (frames_len < 0 || src_len < 0 || src == NULL) { + LOG_error("Sanity"); + return 0; + } - (void) sample_rate_hz; - (void) num_channels; - (void) num_samples; - (void) frames_len; - (void) frames; - (void) src_len; - (void) src; + if (frames_len > 0 && frames == NULL) { + LOG_error("Sanity"); + return 0; + } - LOG_error("Not implemented."); - return 0; + if (src_len < 16) { + LOG_error("File too small."); + return 0; + } + + u8 *eof = src + src_len; + + u32 riff_id; + u32 size_left; + u32 wave_id; + + if (!u32_from_(&riff_id, src, eof) || + !u32_from_(&size_left, src + 4, eof) || + !u32_from_(&wave_id, src + 8, eof) + ) { + LOG_error("Sanity"); + return 0; + } + + if (riff_id != RIFF_Id_) { + LOG_error("Invalid RIFF identifier."); + return 0; + } + + if (wave_id != WAVE_Id_) { + LOG_error("Invalid WAVE identifier."); + return 0; + } + + if (size_left + 8 < src_len) { + LOG_error("Invalid file size value."); + return 0; + } + + if (size_left < 36) { + LOG_error("File too small."); + return 0; + } + + // Read format chunk + + b8 found_format = 0; + + u16 read_format = 0; + u32 read_sample_rate_hz = 0; + u16 read_num_channels = 0; + u16 read_bits_per_frame = 0; + + for (u8 *s = src + 12; s < eof;) { + u32 id; + u32 chunk_size; + + if (!u32_from_(&id, s, eof) + || !u32_from_(&chunk_size, s + 4, eof)) + break; + + if (id != WAVE_CHUNK_fmt_) { + s += 8 + chunk_size; + continue; + } + + if (s + 8 + chunk_size > eof) { + LOG_error("Invalid chunk size value."); + return 0; + } + + if (!u16_from_(&read_format, s + 8, eof) + || !u16_from_(&read_num_channels, s + 10, eof) + || !u32_from_(&read_sample_rate_hz, s + 12, eof) + || !u16_from_(&read_bits_per_frame, s + 22, eof) + ) { + LOG_error("Sanity"); + return 0; + } + + found_format = 1; + break; + } + + if (!found_format) { + LOG_error("Format info not found."); + return 0; + } + + if (read_format != WAVE_Format_Integer_ && read_format != WAVE_Format_Float_) { + LOG_error("Unsupported audio format."); + return 0; + } + + if (read_num_channels == 0) { + LOG_error("Invalid num channels value."); + return 0; + } + + if (read_sample_rate_hz == 0) { + LOG_error("Invalid sample rate value."); + return 0; + } + + if (read_bits_per_frame == 0) { + LOG_error("Invalid bits per frame value."); + return 0; + } + + if (read_format == WAVE_Format_Integer_ && (read_bits_per_frame != 8 && read_bits_per_frame != 16 && read_bits_per_frame != 24 && read_bits_per_frame != 32 && read_bits_per_frame != 64)) { + LOG_error("Unsupported bits per frame value."); + } + + if (read_format == WAVE_Format_Float_ && (read_bits_per_frame != 32 && read_bits_per_frame != 64)) { + LOG_error("Unsupported bits per frame value."); + } + + // Read data chunk + + i64 data_len = 0; + u8 *data = NULL; + + for (u8 *s = src + 12; s < eof;) { + u32 id; + u32 chunk_size; + + if (!u32_from_(&id, s, eof) + || !u32_from_(&chunk_size, s + 4, eof)) + break; + + if (id != WAVE_CHUNK_data_) { + s += 8 + chunk_size; + continue; + } + + if (s + 8 + chunk_size > eof) { + LOG_error("Invalid chunk size value."); + return 0; + } + + data_len = (i64) (u64) chunk_size; + data = s + 8; + break; + } + + if (data == NULL) { + LOG_error("Data chunk not found."); + return 0; + } + + i64 bytes_per_frame = read_bits_per_frame / 8; + i64 bytes_per_sample = (i64) read_num_channels * bytes_per_frame; + i64 read_num_samples = data_len / bytes_per_sample; + + if (sample_rate_hz != NULL) *sample_rate_hz = read_sample_rate_hz; + if (num_channels != NULL) *num_channels = read_num_channels; + if (num_samples != NULL) *num_samples = read_num_samples; + + if (frames == NULL) + // Only validate the format and return info. + return 1; + + // Read the data. + + for (i64 i = 0; i < read_num_samples; ++i) + for (u16 k = 0; k < read_num_channels; ++k) { + u8 *s = data + i * bytes_per_sample + k * bytes_per_frame; + f32 *d = frames + i * read_num_channels + k; + + if (read_format == WAVE_Format_Integer_) + switch (bytes_per_frame) { + case 1: { + // Unsigned 8-bit integer value. + + *d = ((f32) *s - 128.0) / 127.0; + } break; + + case 2: { + // Signed 16-bit integer value. + + i16 x; + u16_from_((u16 *) &x, s, eof); + + *d = (f32) x / 32767.0; + } break; + + case 3: { + // Signed 24-bit integer value. + u32 x; + mem_cpy_(&x, s, 3); + + // Expand sign bit. + if ((x & 0x00800000u) != 0) + x |= 0xff000000u; + + *d = (f32) (i32) x / 8388607.0; + } break; + + case 4: { + // Signed 32-bit integer value. + + i32 x; + u32_from_((u32 *) &x, s, eof); + + *d = (f32) x / 2147483647.0; + } break; + + case 8: { + // Signed 64-bit integer value. + + i64 x; + u64_from_((u64 *) &x, s, eof); + + *d = (long double) x / 9223372036854775808.0L; + } break; + + default: + LOG_error("Sanity"); + return 0; + } + else if (read_format == WAVE_Format_Float_) + switch (bytes_per_frame) { + case 4: { + // 32-bit float value. + + f32 x; + u32_from_((u32 *) &x, s, eof); + + *d = x; + } break; + + case 8: { + // 64-bit float value. + + f64 x; + u64_from_((u64 *) &x, s, eof); + + *d = x; + } break; + + default: + LOG_error("Sanity"); + return 0; + } + else { + LOG_error("Sanity"); + return 0; + } + } + + return 1; } b8 wav_write_to_memory(i64 *dst_len, u8 *dst, i64 sample_rate_hz, i64 num_channels, i64 num_samples, f32 *frames) { - // TODO + if (sample_rate_hz <= 0 || num_channels <= 0 || num_samples < 0) { + LOG_error("Sanity"); + return 0; + } + + if (dst_len == NULL || frames == NULL) { + LOG_error("Sanity"); + return 0; + } - (void) dst_len; - (void) dst; - (void) num_channels; - (void) sample_rate_hz; - (void) num_samples; - (void) frames; + if (sample_rate_hz > 0xffffffffll) { + LOG_error("Invalid sample rate value."); + return 0; + } - LOG_error("Not implemented."); - return 0; + if (num_channels > 0xffff) { + LOG_error("Invalid num channels value."); + return 0; + } + + if (num_samples > 0xffffffffll) { + LOG_error("Invalid num samples value."); + return 0; + } + + i64 num_frames = num_channels * num_samples; + + if (num_frames > 0xffffffffll) { + LOG_error("Invalid num channels and num samples values."); + return 0; + } + + i64 file_size = 0; + + file_size += 4; // riff id + file_size += 4; // size left + file_size += 4; // wave id + file_size += 24; // format info + file_size += 8; // data header + file_size += num_frames * 4; // data + + if (dst == NULL) { + // Only calculate required file size. + *dst_len = file_size; + return 1; + } + + if (*dst_len < file_size) { + *dst_len = file_size; + + LOG_error("Not enough space."); + return 0; + } + + u16 bits_per_sample = 32; + + i64 bytes_per_block = num_channels * (i64) (bits_per_sample / 8); + i64 bytes_per_second = sample_rate_hz * bytes_per_block; + + if (bytes_per_block > 0xffffffffll) { + LOG_error("Invalid num channels value."); + return 0; + } + + if (bytes_per_second > 0xffffffffll) { + LOG_error("Invalid num channels and sample rate values."); + return 0; + } + + mem_cpy_(dst, &(u32) { RIFF_Id_ }, 4); + mem_cpy_(dst + 4, &(u32) { file_size - 8 }, 4); + mem_cpy_(dst + 8, &(u32) { WAVE_Id_ }, 4); + + mem_set_(dst + 12, 0, 24); + mem_cpy_(dst + 12, &(u32) { WAVE_CHUNK_fmt_ }, 4); + mem_cpy_(dst + 16, &(u32) { 16 }, 4); + mem_cpy_(dst + 20, &(u16) { WAVE_Format_Float_ }, 2); + mem_cpy_(dst + 22, &(u16) { num_channels }, 2); + mem_cpy_(dst + 24, &(u32) { sample_rate_hz }, 4); + mem_cpy_(dst + 28, &(u32) { bytes_per_second }, 4); + mem_cpy_(dst + 32, &(u16) { bytes_per_block }, 2); + mem_cpy_(dst + 34, &(u16) { bits_per_sample }, 2); + + mem_cpy_(dst + 36, &(u32) { WAVE_CHUNK_data_ }, 4); + mem_cpy_(dst + 40, &(u32) { num_frames * 4 }, 4); + + f32 *d = (f32 *) (dst + 44); + + for (i64 i = 0; i < num_samples; ++i) + for (i64 k = 0; k < num_channels; ++k) + mem_cpy_( + d + i * num_channels + k, + frames + i * num_channels + k, + sizeof *d + ); + + return 1; } // ================================================================ |