summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitya Selivanov <automainint@guattari.tech>2025-04-27 21:38:47 +0200
committerMitya Selivanov <automainint@guattari.tech>2025-04-27 21:38:47 +0200
commit8ce2615ecec92f30a7b014e6c09bab7b16b21d4f (patch)
tree86cc7377369c101440ffd24648c8d6dda8920cb2
parentb3cb112815b7892f3eaed092602f3b66bee102ff (diff)
downloadreduced_system_layer-8ce2615ecec92f30a7b014e6c09bab7b16b21d4f.zip
WAV file format
-rw-r--r--runtime.c518
1 files changed, 477 insertions, 41 deletions
diff --git a/runtime.c b/runtime.c
index af5d6bc..67c01bd 100644
--- a/runtime.c
+++ b/runtime.c
@@ -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;
}
// ================================================================