From 9b0b3d9d19f5adbf73423f9146974bf9213624d5 Mon Sep 17 00:00:00 2001 From: steven-na Date: Fri, 31 Jul 2026 20:40:49 -0700 Subject: Wav funcs mostly complete --- src/wav.c | 38 +++++++------- src/wav.h | 8 +-- tests/wav.c | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 177 insertions(+), 38 deletions(-) diff --git a/src/wav.c b/src/wav.c index 665998d..9698001 100644 --- a/src/wav.c +++ b/src/wav.c @@ -7,7 +7,7 @@ b32 seek_to_chunk(FILE *file, char const *chunk_name) { char cur_name[4]; - u32 chunk_size; + u32 chunk_size; fseek(file, 0, SEEK_END); const i64 max_pos = ftell(file); @@ -29,11 +29,9 @@ b32 seek_to_chunk(FILE *file, char const *chunk_name) { return true; } -wav_data_t load_wav_file(smrt_arena_t *arena, char *filename, wav_master_chunk_t *master_o, wav_fmt_chunk_t *format_o) { +wav_data_t load_wav_file(smrt_arena_t *arena, FILE *wav_file, wav_master_chunk_t *master_o, wav_fmt_chunk_t *format_o) { wav_data_t data = { 0 }; - FILE *wav_file = fopen(filename, "rb"); - if (!wav_file) return data; fread(master_o, sizeof(wav_master_chunk_t), 1, wav_file); @@ -41,6 +39,11 @@ wav_data_t load_wav_file(smrt_arena_t *arena, char *filename, wav_master_chunk_t if (seek_to_chunk(wav_file, "fmt ") == false) return data; fread(format_o, sizeof(wav_fmt_chunk_t), 1, wav_file); + if (format_o->audio_format != 1) { + perror("Attempted to read non pcm integer wave file."); + return data; + } + if (seek_to_chunk(wav_file, "data") == false) return data; fseek(wav_file, 4, SEEK_CUR); @@ -56,39 +59,38 @@ wav_data_t load_wav_file(smrt_arena_t *arena, char *filename, wav_master_chunk_t return data; } -b32 write_wav_file(char *filename, wav_fmt_chunk_t *fmt_chunk_i, wav_data_t data_i) { - FILE *wav_file = fopen(filename, "wb"); - +b32 write_wav_file(FILE *wav_file, wav_fmt_chunk_t *fmt_chunk_i, wav_data_t data_i) { if (!wav_file) return false; - u64 data_size_bytes = data_i.sample_count * fmt_chunk_i->bytes_per_block; + u32 data_size_bytes = data_i.sample_count * fmt_chunk_i->bytes_per_block; - wav_master_chunk_t mchunk = { - .file_size = sizeof(wav_master_chunk_t) + sizeof(wav_fmt_chunk_t) + sizeof(wav_data_t) + data_size_bytes + wav_master_chunk_t mchunk = { // MASTER + FMT + "data" + sizeof(data_size_bytes) + data_size_bytes (-8 bytes) + .file_size = sizeof(wav_master_chunk_t) + sizeof(wav_fmt_chunk_t) + data_size_bytes }; - memcpy(&mchunk.chunk_id, "RIFF", 4); memcpy(&mchunk.file_format_id, "WAVE", 4); fwrite(&mchunk, sizeof(wav_master_chunk_t), 1, wav_file); - fwrite(fmt_chunk_i, sizeof(wav_data_t), 1, wav_file); + fwrite(fmt_chunk_i, sizeof(wav_fmt_chunk_t), 1, wav_file); + + fwrite("data", 4, 1, wav_file); + fwrite(&data_size_bytes, 4, 1, wav_file); fwrite(data_i.samples, sizeof(u8), data_size_bytes, wav_file); return true; } -wav_fmt_chunk_t make_wav_fmt_chunk(u32 num_channels, u32 sample_rate, u16 bits_per_sample, wav_data_t data_i) { - u64 data_size_bytes = data_i.sample_count * (num_channels * (bits_per_sample / 8)); +wav_fmt_chunk_t make_wav_fmt_chunk(u32 num_channels, u32 sample_rate, u16 bits_per_sample) { wav_fmt_chunk_t o = { - .chunk_size = sizeof(wav_fmt_chunk_t) + data_size_bytes - 8, .bits_per_sample = bits_per_sample, .num_channels = num_channels, .sample_rate = sample_rate, - .bytes_per_block = num_channels * (bits_per_sample / 8), .audio_format = 1, + .bytes_per_block = num_channels * (bits_per_sample / 8), + .chunk_size = sizeof(wav_fmt_chunk_t) - 8, }; - o.bytes_per_sec = o.bytes_per_block * sample_rate; - memcpy(&o.chunk_id, "data", 4); + o.bytes_per_sec = (u32)o.bytes_per_block * sample_rate; + memcpy(&o.chunk_id, "fmt ", 4); return o; } diff --git a/src/wav.h b/src/wav.h index 773cc2a..7e3c861 100644 --- a/src/wav.h +++ b/src/wav.h @@ -3,6 +3,8 @@ #include "common.h" #include "smrt_arena.h" +#include + #define WAV_DATA_BASE_POS sizeof(wav_data_t) typedef struct { @@ -42,6 +44,6 @@ typedef struct { } wav_fmt_chunk_t; #pragma pack(pop) - wav_data_t load_wav_file(smrt_arena_t *arena, char *filename, wav_master_chunk_t *master_o, wav_fmt_chunk_t *format_o); - b32 write_wav_file(char *filename, wav_fmt_chunk_t *fmt_chunk_i, wav_data_t data_i); -wav_fmt_chunk_t make_wav_fmt_chunk(u32 num_channels, u32 sample_rate, u16 bits_per_sample, wav_data_t data_i); + wav_data_t load_wav_file(smrt_arena_t *arena, FILE *wav_file, wav_master_chunk_t *master_o, wav_fmt_chunk_t *format_o); + b32 write_wav_file(FILE *wav_file, wav_fmt_chunk_t *fmt_chunk_i, wav_data_t data_i); +wav_fmt_chunk_t make_wav_fmt_chunk(u32 num_channels, u32 sample_rate, u16 bits_per_sample); diff --git a/tests/wav.c b/tests/wav.c index 81b860e..a3a569e 100644 --- a/tests/wav.c +++ b/tests/wav.c @@ -1,6 +1,12 @@ +#define _DEFAULT_SOURCE + #include "../src/wav.h" +#include +#include +#include #include +#include #include #include @@ -14,7 +20,12 @@ Test(wav, read_file) { wav_master_chunk_t mchunk; wav_fmt_chunk_t fmtchunk; - wav_data_t data = load_wav_file(arena, "./tests/fixtures/sine.wav", &mchunk, &fmtchunk); + FILE *wav_file = fopen("./tests/fixtures/sine.wav", "rb"); + cr_assert_not_null(wav_file); + + wav_data_t data = load_wav_file(arena, wav_file, &mchunk, &fmtchunk); + + fclose(wav_file); u8 idcheck[4] = { 'f', 'm', 't', ' ' }; cr_expect(memcmp(fmtchunk.chunk_id, idcheck, 4) == 0); @@ -26,13 +37,18 @@ Test(wav, read_file) { smrt_arena_destroy(arena); } -Test(wav, sample_data_matches_source) { +Test(wav, read_sample_data_matches_source) { smrt_arena_t *arena = smrt_arena_create(MiB(4), KiB(4), false); wav_master_chunk_t mchunk; wav_fmt_chunk_t fmtchunk; - wav_data_t data = load_wav_file(arena, "./tests/fixtures/sine.wav", &mchunk, &fmtchunk); + FILE *wav_file = fopen("./tests/fixtures/sine.wav", "rb"); + cr_assert_not_null(wav_file); + + wav_data_t data = load_wav_file(arena, wav_file, &mchunk, &fmtchunk); + + fclose(wav_file); i16 expected[8] = { 0, 2147, 4242, 6291, 8185, 9935, 11580, 12979 }; i16 *samples = (i16*)((u8*)data.samples + WAV_DATA_BASE_POS); @@ -44,26 +60,34 @@ Test(wav, sample_data_matches_source) { smrt_arena_destroy(arena); } -Test(wav, missing_file_returns_null) { +Test(wav, read_missing_file_returns_null) { smrt_arena_t *arena = smrt_arena_create(MiB(4), KiB(4), false); wav_master_chunk_t mchunk; wav_fmt_chunk_t fmtchunk; - wav_data_t data = load_wav_file(arena, "./tests/fixtures/does_not_exist.wav", &mchunk, &fmtchunk); + FILE *wav_file = fopen("./tests/fixtures/does_not_exist.wav", "rb"); + cr_assert_null(wav_file); + + wav_data_t data = load_wav_file(arena, wav_file, &mchunk, &fmtchunk); cr_expect_eq(data.samples, NULL); smrt_arena_destroy(arena); } -Test(wav, stereo_16bit) { +Test(wav, read_stereo_16bit) { smrt_arena_t *arena = smrt_arena_create(MiB(4), KiB(4), false); wav_master_chunk_t mchunk; wav_fmt_chunk_t fmtchunk; - wav_data_t data = load_wav_file(arena, "./tests/fixtures/stereo16.wav", &mchunk, &fmtchunk); + FILE *wav_file = fopen("./tests/fixtures/stereo16.wav", "rb"); + cr_assert_not_null(wav_file); + + wav_data_t data = load_wav_file(arena, wav_file, &mchunk, &fmtchunk); + + fclose(wav_file); cr_expect(fmtchunk.num_channels == 2); cr_expect(fmtchunk.bits_per_sample == 16); @@ -78,13 +102,18 @@ Test(wav, stereo_16bit) { smrt_arena_destroy(arena); } -Test(wav, mono_8bit) { +Test(wav, read_mono_8bit) { smrt_arena_t *arena = smrt_arena_create(MiB(4), KiB(4), false); wav_master_chunk_t mchunk; wav_fmt_chunk_t fmtchunk; - wav_data_t data = load_wav_file(arena, "./tests/fixtures/mono8.wav", &mchunk, &fmtchunk); + FILE *wav_file = fopen("./tests/fixtures/mono8.wav", "rb"); + cr_assert_not_null(wav_file); + + wav_data_t data = load_wav_file(arena, wav_file, &mchunk, &fmtchunk); + + fclose(wav_file); cr_expect(fmtchunk.num_channels == 1); cr_expect(fmtchunk.bits_per_sample == 8); @@ -99,13 +128,18 @@ Test(wav, mono_8bit) { smrt_arena_destroy(arena); } -Test(wav, skips_unknown_chunk_before_fmt) { +Test(wav, read_skips_unknown_chunk_before_fmt) { smrt_arena_t *arena = smrt_arena_create(MiB(4), KiB(4), false); wav_master_chunk_t mchunk; wav_fmt_chunk_t fmtchunk; - wav_data_t data = load_wav_file(arena, "./tests/fixtures/extra_chunk.wav", &mchunk, &fmtchunk); + FILE *wav_file = fopen("./tests/fixtures/extra_chunk.wav", "rb"); + cr_assert_not_null(wav_file); + + wav_data_t data = load_wav_file(arena, wav_file, &mchunk, &fmtchunk); + + fclose(wav_file); u8 idcheck[4] = { 'f', 'm', 't', ' ' }; cr_expect(memcmp(fmtchunk.chunk_id, idcheck, 4) == 0); @@ -116,13 +150,18 @@ Test(wav, skips_unknown_chunk_before_fmt) { smrt_arena_destroy(arena); } -Test(wav, odd_sized_chunk_before_fmt, .timeout = 2) { +Test(wav, read_odd_sized_chunk_before_fmt, .timeout = 2) { smrt_arena_t *arena = smrt_arena_create(MiB(4), KiB(4), false); wav_master_chunk_t mchunk; wav_fmt_chunk_t fmtchunk; - wav_data_t data = load_wav_file(arena, "./tests/fixtures/odd_chunk.wav", &mchunk, &fmtchunk); + FILE *wav_file = fopen("./tests/fixtures/odd_chunk.wav", "rb"); + cr_assert_not_null(wav_file); + + wav_data_t data = load_wav_file(arena, wav_file, &mchunk, &fmtchunk); + + fclose(wav_file); u8 idcheck[4] = { 'f', 'm', 't', ' ' }; cr_expect(memcmp(fmtchunk.chunk_id, idcheck, 4) == 0); @@ -132,29 +171,125 @@ Test(wav, odd_sized_chunk_before_fmt, .timeout = 2) { smrt_arena_destroy(arena); } -Test(wav, missing_data_chunk, .timeout = 2) { +Test(wav, read_missing_data_chunk, .timeout = 2) { smrt_arena_t *arena = smrt_arena_create(MiB(4), KiB(4), false); wav_master_chunk_t mchunk; wav_fmt_chunk_t fmtchunk; - wav_data_t data = load_wav_file(arena, "./tests/fixtures/missing_data_chunk.wav", &mchunk, &fmtchunk); + FILE *wav_file = fopen("./tests/fixtures/missing_data_chunk.wav", "rb"); + cr_assert_not_null(wav_file); + + wav_data_t data = load_wav_file(arena, wav_file, &mchunk, &fmtchunk); + + fclose(wav_file); cr_expect_eq(data.samples, NULL); smrt_arena_destroy(arena); } -Test(wav, arena_too_small_for_data) { +Test(wav, read_arena_too_small_for_data) { smrt_arena_t *arena = smrt_arena_create(KiB(1), KiB(4), false); wav_master_chunk_t mchunk; wav_fmt_chunk_t fmtchunk; - wav_data_t data = load_wav_file(arena, "./tests/fixtures/sine.wav", &mchunk, &fmtchunk); + FILE *wav_file = fopen("./tests/fixtures/sine.wav", "rb"); + cr_assert_not_null(wav_file); + + wav_data_t data = load_wav_file(arena, wav_file, &mchunk, &fmtchunk); + + fclose(wav_file); cr_expect_eq(data.samples, NULL); cr_expect_eq(data.sample_count, 0); smrt_arena_destroy(arena); } + +Test(wav, generate_header) { + smrt_arena_t *arena = smrt_arena_create(KiB(64), KiB(4), false); + + wav_fmt_chunk_t fmtchunk = make_wav_fmt_chunk(1, 44100, 8); + + u8 idcheck[4] = { 'f', 'm', 't', ' ' }; + cr_expect(memcmp(fmtchunk.chunk_id, idcheck, 4) == 0); + cr_expect(fmtchunk.audio_format == 1); + cr_expect(fmtchunk.num_channels == 1); + cr_expect(fmtchunk.bits_per_sample == 8); + + smrt_arena_destroy(arena); +} + +static char write_sine_path[] = "/tmp/steez_testwav_XXXXXX"; +static int write_sine_fd; + +void write_sine_setup(void) { + write_sine_fd = mkstemp(write_sine_path); + cr_assert_geq(write_sine_fd, 0, "mkstemp failed"); +} + +void write_sine_teardown(void) { + unlink(write_sine_path); + close(write_sine_fd); +} + + +Test(wav, write_sine, .init = write_sine_setup, .fini = write_sine_teardown) { + FILE *wav_file = fdopen(write_sine_fd, "wb"); + cr_assert_not_null(wav_file); + + smrt_arena_t *arena = smrt_arena_create(KiB(256), KiB(4), false); + + u64 sample_count = 44100; + wav_data_t data; + data.samples = smrt_arena_push(arena, sizeof(u8) * sample_count, true); + data.sample_count = sample_count; + + const f64 freq = 220.0; + for (u64 i = 0; i < sample_count; i++) { + f64 progress = (f64)i / sample_count; + + data.samples[i] = (u8)(((sin(progress * freq) + 1.0 ) / 2.0 ) * UINT8_MAX); + } + + wav_fmt_chunk_t fmtchunk = make_wav_fmt_chunk(1, sample_count, 8); + + cr_assert(write_wav_file(wav_file, &fmtchunk, data)); + + fclose(wav_file); + + FILE *readback_file = fopen(write_sine_path, "rb"); + cr_assert_not_null(readback_file); + + wav_master_chunk_t mchunk; + wav_fmt_chunk_t read_fmtchunk; + + wav_data_t read_data = load_wav_file(arena, readback_file, &mchunk, &read_fmtchunk); + + fclose(readback_file); + + u8 riffcheck[4] = { 'R', 'I', 'F', 'F' }; + u8 wavecheck[4] = { 'W', 'A', 'V', 'E' }; + cr_expect(memcmp(mchunk.chunk_id, riffcheck, 4) == 0); + cr_expect(memcmp(mchunk.file_format_id, wavecheck, 4) == 0); + cr_expect_eq(mchunk.file_size, sizeof(wav_master_chunk_t) + sizeof(wav_fmt_chunk_t) + sample_count); + + u8 fmtcheck[4] = { 'f', 'm', 't', ' ' }; + cr_expect(memcmp(read_fmtchunk.chunk_id, fmtcheck, 4) == 0); + cr_expect_eq(read_fmtchunk.audio_format, 1); + cr_expect_eq(read_fmtchunk.num_channels, 1); + cr_expect_eq(read_fmtchunk.sample_rate, sample_count); + cr_expect_eq(read_fmtchunk.bits_per_sample, 8); + + cr_assert_not_null(read_data.samples); + cr_expect_eq(read_data.sample_count, sample_count); + + u8 *read_samples = (u8*)read_data.samples + WAV_DATA_BASE_POS; + for (u64 i = 0; i < sample_count; i++) { + cr_expect_eq(read_samples[i], data.samples[i]); + } + + smrt_arena_destroy(arena); +} -- cgit v1.2.3