summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tests/dft.c252
-rw-r--r--tests/ez_arena.c48
-rw-r--r--tests/string.c83
-rw-r--r--tests/vec2.c67
-rw-r--r--tests/wav.c45
5 files changed, 495 insertions, 0 deletions
diff --git a/tests/dft.c b/tests/dft.c
new file mode 100644
index 0000000..a6ee01d
--- /dev/null
+++ b/tests/dft.c
@@ -0,0 +1,252 @@
+#include "../src/dft.h"
+
+#include <math.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <criterion/criterion.h>
+#include <criterion/internal/assert.h>
+#include <criterion/internal/test.h>
+
+f64 *mock_amplitude_data(smrt_arena_t *arena, f64 *freqs, f64 *amps, u64 n, f64 duration, u64 sample_count) {
+ f64 *samples = SMRTA_ALLOC_ARRAY(arena, f64, sample_count);
+
+ for (u64 f = 0; f < n; f++) {
+ f64 freq = freqs[f];
+ f64 amp = amps[f];
+ for (u64 i = 0; i < sample_count; i++) {
+ f64 t = ((f64)i/sample_count) * duration;
+ samples[i] += cos(freq * t * 2.0 * PI) * amp;
+ }
+ }
+
+ return samples;
+}
+
+Test(dft, dc_component) {
+ smrt_arena_t *arena = smrt_arena_create(KiB(16), KiB(4), false);
+
+ u64 sample_count = 16, sample_rate = 16;
+ f64 freqs[1] = { 0.0 };
+ f64 amps[1] = { 3.0 };
+ f64 *samples = mock_amplitude_data(arena, freqs, amps, 1, (f64)sample_count/sample_rate, sample_count);
+
+ dft_data_t dft = discrete_fourier_transform(arena, samples, sample_count, sample_rate);
+
+ cr_expect_eq(dft.frequencies[0], 0.0);
+ cr_expect(F64_EQ(dft.amplitudes[0], 3.0, 1e-9));
+
+ for (u64 i = 1; i < dft.freq_count; i++) {
+ cr_expect(F64_EQ(dft.amplitudes[i], 0.0, 1e-9));
+ }
+
+ smrt_arena_destroy(arena);
+}
+
+Test(dft, single_tone_at_bin) {
+ smrt_arena_t *arena = smrt_arena_create(KiB(16), KiB(4), false);
+
+ u64 sample_count = 16, sample_rate = 16;
+ u64 k = 3;
+ f64 freqs[1] = { (f64)k * sample_rate / sample_count };
+ f64 amps[1] = { 2.0 };
+ f64 *samples = mock_amplitude_data(arena, freqs, amps, 1, (f64)sample_count/sample_rate, sample_count);
+
+ dft_data_t dft = discrete_fourier_transform(arena, samples, sample_count, sample_rate);
+
+ cr_expect(F64_EQ(dft.amplitudes[k], 2.0, 1e-9));
+ cr_expect(F64_EQ(dft.phases[k], 0.0, 1e-9));
+
+ for (u64 i = 0; i < dft.freq_count; i++) {
+ if (i == k) continue;
+ cr_expect(F64_EQ(dft.amplitudes[i], 0.0, 1e-9));
+ }
+
+ smrt_arena_destroy(arena);
+}
+
+Test(dft, separates_superposed_tones) {
+ smrt_arena_t *arena = smrt_arena_create(KiB(16), KiB(4), false);
+
+ u64 sample_count = 16, sample_rate = 16;
+ u64 k1 = 2, k2 = 5;
+ f64 freqs[2] = { (f64)k1 * sample_rate / sample_count, (f64)k2 * sample_rate / sample_count };
+ f64 amps[2] = { 1.5, 0.5 };
+ f64 *samples = mock_amplitude_data(arena, freqs, amps, 2, (f64)sample_count/sample_rate, sample_count);
+
+ dft_data_t dft = discrete_fourier_transform(arena, samples, sample_count, sample_rate);
+
+ cr_expect(F64_EQ(dft.amplitudes[k1], 1.5, 1e-9));
+ cr_expect(F64_EQ(dft.amplitudes[k2], 0.5, 1e-9));
+
+ for (u64 i = 0; i < dft.freq_count; i++) {
+ if (i == k1 || i == k2) continue;
+ cr_expect(F64_EQ(dft.amplitudes[i], 0.0, 1e-9));
+ }
+
+ smrt_arena_destroy(arena);
+}
+
+Test(dft, nyquist_bin_uses_unit_coefficient) {
+ smrt_arena_t *arena = smrt_arena_create(KiB(16), KiB(4), false);
+
+ u64 sample_count = 16, sample_rate = 16;
+ f64 freqs[1] = { (f64)sample_rate / 2.0 };
+ f64 amps[1] = { 1.0 };
+ f64 *samples = mock_amplitude_data(arena, freqs, amps, 1, (f64)sample_count/sample_rate, sample_count);
+
+ dft_data_t dft = discrete_fourier_transform(arena, samples, sample_count, sample_rate);
+
+ cr_expect_eq(dft.freq_count, sample_count/2 + 1);
+ cr_expect(F64_EQ(dft.amplitudes[dft.freq_count-1], 1.0, 1e-9));
+
+ smrt_arena_destroy(arena);
+}
+
+Test(dft, odd_sample_count_has_no_nyquist_bin) {
+ smrt_arena_t *arena = smrt_arena_create(KiB(16), KiB(4), false);
+
+ u64 sample_count = 15, sample_rate = 15;
+ u64 last_bin = sample_count/2;
+ f64 freqs[1] = { (f64)last_bin };
+ f64 amps[1] = { 1.0 };
+ f64 *samples = mock_amplitude_data(arena, freqs, amps, 1, (f64)sample_count/sample_rate, sample_count);
+
+ dft_data_t dft = discrete_fourier_transform(arena, samples, sample_count, sample_rate);
+
+ cr_expect_eq(dft.freq_count, last_bin + 1);
+ cr_expect(F64_EQ(dft.amplitudes[last_bin], 1.0, 1e-9));
+
+ smrt_arena_destroy(arena);
+}
+
+Test(dft, data_to_wav_dc_amplitude) {
+ smrt_arena_t *arena = smrt_arena_create(KiB(16), KiB(4), false);
+
+ dft_data_t dft = { .freq_count = 1 };
+ dft.frequencies = SMRTA_ALLOC_ARRAY(arena, f64, 1);
+ dft.amplitudes = SMRTA_ALLOC_ARRAY(arena, f64, 1);
+ dft.phases = SMRTA_ALLOC_ARRAY(arena, f64, 1);
+ dft.frequencies[0] = 0.0;
+ dft.amplitudes[0] = 0.5;
+ dft.phases[0] = 0.0;
+
+ wav_data_t wav = dft_data_to_wav(arena, dft, 8, 1.0);
+
+ cr_assert_eq(wav.sample_count, 8);
+ for (u64 i = 0; i < wav.sample_count; i++) {
+ cr_expect_eq(wav.samples[i], 190);
+ }
+
+ smrt_arena_destroy(arena);
+}
+
+Test(dft, data_to_wav_respects_phase) {
+ smrt_arena_t *arena = smrt_arena_create(KiB(16), KiB(4), false);
+
+ dft_data_t dft = { .freq_count = 1 };
+ dft.frequencies = SMRTA_ALLOC_ARRAY(arena, f64, 1);
+ dft.amplitudes = SMRTA_ALLOC_ARRAY(arena, f64, 1);
+ dft.phases = SMRTA_ALLOC_ARRAY(arena, f64, 1);
+ dft.frequencies[0] = 0.0;
+ dft.amplitudes[0] = 0.5;
+ dft.phases[0] = PI;
+
+ wav_data_t wav = dft_data_to_wav(arena, dft, 8, 1.0);
+
+ cr_assert_eq(wav.sample_count, 8);
+ for (u64 i = 0; i < wav.sample_count; i++) {
+ cr_expect_eq(wav.samples[i], 63);
+ }
+
+ smrt_arena_destroy(arena);
+}
+
+Test(dft, data_to_wav_varies_with_time) {
+ smrt_arena_t *arena = smrt_arena_create(KiB(16), KiB(4), false);
+
+ dft_data_t dft = { .freq_count = 1 };
+ dft.frequencies = SMRTA_ALLOC_ARRAY(arena, f64, 1);
+ dft.amplitudes = SMRTA_ALLOC_ARRAY(arena, f64, 1);
+ dft.phases = SMRTA_ALLOC_ARRAY(arena, f64, 1);
+ dft.frequencies[0] = 1.0;
+ dft.amplitudes[0] = 1.0;
+ dft.phases[0] = 0.0;
+
+ wav_data_t wav = dft_data_to_wav(arena, dft, 8, 1.0);
+
+ cr_assert_eq(wav.sample_count, 8);
+ cr_expect_eq(wav.samples[0], 254);
+ cr_expect_eq(wav.samples[4], 0);
+
+ smrt_arena_destroy(arena);
+}
+
+Test(dft, stft_segments_match_direct_transform) {
+ smrt_arena_t *arena = smrt_arena_create(KiB(64), KiB(4), false);
+
+ u64 sample_count = 12, sample_rate = 12, samples_per_segment = 5;
+ f64 freqs[1] = { 2.0 };
+ f64 amps[1] = { 1.0 };
+ f64 *samples = mock_amplitude_data(arena, freqs, amps, 1, (f64)sample_count/sample_rate, sample_count);
+
+ stft_data_t stft = short_time_fourier_transform(arena, samples_per_segment, samples, sample_count, sample_rate);
+
+ cr_assert_eq(stft.segment_count, 3);
+
+ u64 expected_starts[3] = { 0, 5, 10 };
+ u64 expected_counts[3] = { 5, 5, 2 };
+
+ for (u64 s = 0; s < stft.segment_count; s++) {
+ stft_segment_t seg = stft.segments[s];
+ cr_expect_eq(seg.start_index, expected_starts[s]);
+ cr_expect_eq(seg.sample_count, expected_counts[s]);
+
+ dft_data_t direct = discrete_fourier_transform(arena, samples + seg.start_index, seg.sample_count, sample_rate);
+
+ cr_assert_eq(seg.data.freq_count, direct.freq_count);
+ for (u64 i = 0; i < direct.freq_count; i++) {
+ cr_expect(F64_EQ(seg.data.frequencies[i], direct.frequencies[i], 1e-9));
+ cr_expect(F64_EQ(seg.data.amplitudes[i], direct.amplitudes[i], 1e-9));
+ cr_expect(F64_EQ(seg.data.phases[i], direct.phases[i], 1e-9));
+ }
+ }
+
+ smrt_arena_destroy(arena);
+}
+
+Test(dft, stft_data_to_wav_concatenates_segments) {
+ smrt_arena_t *arena = smrt_arena_create(KiB(16), KiB(4), false);
+
+ stft_segment_t segs[2] = { 0 };
+
+ segs[0].start_index = 0;
+ segs[0].sample_count = 4;
+ segs[0].data.freq_count = 1;
+ segs[0].data.frequencies = SMRTA_ALLOC_ARRAY(arena, f64, 1);
+ segs[0].data.amplitudes = SMRTA_ALLOC_ARRAY(arena, f64, 1);
+ segs[0].data.phases = SMRTA_ALLOC_ARRAY(arena, f64, 1);
+ segs[0].data.amplitudes[0] = 0.5;
+
+ segs[1].start_index = 4;
+ segs[1].sample_count = 6;
+ segs[1].data.freq_count = 1;
+ segs[1].data.frequencies = SMRTA_ALLOC_ARRAY(arena, f64, 1);
+ segs[1].data.amplitudes = SMRTA_ALLOC_ARRAY(arena, f64, 1);
+ segs[1].data.phases = SMRTA_ALLOC_ARRAY(arena, f64, 1);
+ segs[1].data.amplitudes[0] = -0.5;
+
+ stft_data_t stft = {
+ .segments = segs,
+ .segment_count = 2,
+ .sample_rate = 10,
+ };
+
+ wav_data_t wav = stft_data_to_wav(arena, stft, 10);
+
+ cr_assert_eq(wav.sample_count, 10);
+ for (u64 i = 0; i < 4; i++) cr_expect_eq(wav.samples[i], 190);
+ for (u64 i = 4; i < 10; i++) cr_expect_eq(wav.samples[i], 63);
+
+ smrt_arena_destroy(arena);
+}
diff --git a/tests/ez_arena.c b/tests/ez_arena.c
index d5bcced..8a8b959 100644
--- a/tests/ez_arena.c
+++ b/tests/ez_arena.c
@@ -107,3 +107,51 @@ Test(ez_arena, alloc_clear_dont_zero_arena) {
ez_arena_destroy(arena);
}
+
+Test(ez_arena, create_without_zeroing_is_usable) {
+ ez_arena_t *arena = ez_arena_create(sizeof(uint32_t), false);
+
+ uint32_t *i = ez_arena_alloc(arena, sizeof(uint32_t));
+ *i = UINT32_MAX;
+
+ cr_assert_eq(*i, UINT32_MAX);
+
+ ez_arena_destroy(arena);
+}
+
+Test(ez_arena, create_fails_when_malloc_cannot_satisfy_size) {
+ ez_arena_t *arena = ez_arena_create(UINT64_MAX - sizeof(ez_arena_t), true);
+
+ cr_expect_eq(arena, NULL);
+}
+
+Test(ez_arena, alloc_nz_arena) {
+ ez_arena_t *arena = ez_arena_create(sizeof(uint32_t), true);
+
+ uint32_t *i = ez_arena_alloc_nz(arena, sizeof(uint32_t));
+ *i = UINT32_MAX;
+
+ cr_assert_eq(*i, UINT32_MAX);
+
+ ez_arena_destroy(arena);
+}
+
+Test(ez_arena, alloc_exceeds_size_returns_null) {
+ ez_arena_t *arena = ez_arena_create(sizeof(uint32_t), true);
+
+ void *p = ez_arena_alloc(arena, KiB(1));
+
+ cr_expect_eq(p, NULL);
+
+ ez_arena_destroy(arena);
+}
+
+Test(ez_arena, alloc_nz_exceeds_size_returns_null) {
+ ez_arena_t *arena = ez_arena_create(sizeof(uint32_t), true);
+
+ void *p = ez_arena_alloc_nz(arena, KiB(1));
+
+ cr_expect_eq(p, NULL);
+
+ ez_arena_destroy(arena);
+}
diff --git a/tests/string.c b/tests/string.c
index 794bcfb..d106380 100644
--- a/tests/string.c
+++ b/tests/string.c
@@ -86,3 +86,86 @@ Test(string, format_and_copy_string) {
smrt_arena_destroy(arena);
}
+
+Test(string, clear_string) {
+ smrt_arena_t *arena = smrt_arena_create(KiB(4), KiB(4), false);
+ strng_t *s = strng_from(arena, "Hello, world.");
+
+ strng_clear(s);
+
+ cr_expect_eq(s->len, 0);
+
+ smrt_arena_destroy(arena);
+}
+
+Test(string, set_string_too_large_fails) {
+ smrt_arena_t *arena = smrt_arena_create(KiB(4), KiB(4), false);
+ strng_t *s = strng_new(arena, 4);
+
+ b32 ok = strng_set(s, "way too long for a 4 byte buffer");
+
+ cr_expect_eq(ok, false);
+ cr_expect_eq(s->len, 0);
+
+ smrt_arena_destroy(arena);
+}
+
+Test(string, to_cstring_of_empty_string) {
+ smrt_arena_t *arena = smrt_arena_create(KiB(4), KiB(4), false);
+ strng_t *s = strng_new(arena, 10);
+
+ char *c = strng_str(arena, s);
+
+ cr_assert_not_null(c);
+ cr_expect(strlen(c) == 0);
+
+ smrt_arena_destroy(arena);
+}
+
+Test(string, new_string_returns_null_when_arena_exhausted) {
+ smrt_arena_t *arena = smrt_arena_create(1, 1, false);
+
+ strng_t *s = strng_new(arena, MiB(1));
+
+ cr_expect_eq(s, NULL);
+
+ smrt_arena_destroy(arena);
+}
+
+Test(string, from_cstring_returns_null_when_arena_exhausted) {
+ smrt_arena_t *arena = smrt_arena_create(1, 1, false);
+
+ char big[KiB(8)+1];
+ memset(big, 'a', sizeof(big)-1);
+ big[sizeof(big)-1] = '\0';
+
+ strng_t *s = strng_from(arena, big);
+
+ cr_expect_eq(s, NULL);
+
+ smrt_arena_destroy(arena);
+}
+
+Test(string, duplicate_returns_null_when_arena_exhausted) {
+ smrt_arena_t *arena = smrt_arena_create(1, 1, false);
+
+ strng_t fake_src = { .alloc_size = KiB(8), .len = KiB(8) };
+
+ strng_t *s = strng_dup(arena, &fake_src);
+
+ cr_expect_eq(s, NULL);
+
+ smrt_arena_destroy(arena);
+}
+
+Test(string, to_cstring_returns_null_when_arena_exhausted) {
+ smrt_arena_t *arena = smrt_arena_create(1, 1, false);
+
+ strng_t fake_str = { .alloc_size = KiB(8), .len = KiB(8) };
+
+ char *c = strng_str(arena, &fake_str);
+
+ cr_expect_eq(c, NULL);
+
+ smrt_arena_destroy(arena);
+}
diff --git a/tests/vec2.c b/tests/vec2.c
index a446557..f0b2d9b 100644
--- a/tests/vec2.c
+++ b/tests/vec2.c
@@ -128,3 +128,70 @@ Test(vec2d, vec_dot) {
cr_expect(F64_EQ(opd, expected, 1e-9));
}
+
+Test(vec2d_soa, add_n) {
+ vec2d_soa_t a = { .xs = (f64[]){1.0, 2.0, 3.0}, .ys = (f64[]){4.0, 5.0, 6.0}, .size = 3 };
+ vec2d_soa_t b = { .xs = (f64[]){10.0, 20.0, 30.0}, .ys = (f64[]){40.0, 50.0, 60.0}, .size = 3 };
+
+ f64 out_x[3], out_y[3];
+ vec2d_soa_t out = { .xs = out_x, .ys = out_y, .size = 3 };
+
+ vec2d_soa_add_n(&out, &a, &b);
+
+ for (size_t i = 0; i < 3; i++) {
+ cr_expect(F64_EQ(out.xs[i], a.xs[i] + b.xs[i], 1e-9));
+ cr_expect(F64_EQ(out.ys[i], a.ys[i] + b.ys[i], 1e-9));
+ }
+}
+
+Test(vec2d_soa, sub_n) {
+ vec2d_soa_t lhs = { .xs = (f64[]){10.0, 20.0, 30.0}, .ys = (f64[]){40.0, 50.0, 60.0}, .size = 3 };
+ vec2d_soa_t rhs = { .xs = (f64[]){1.0, 2.0, 3.0}, .ys = (f64[]){4.0, 5.0, 6.0}, .size = 3 };
+
+ f64 out_x[3], out_y[3];
+ vec2d_soa_t out = { .xs = out_x, .ys = out_y, .size = 3 };
+
+ vec2d_soa_sub_n(&out, &lhs, &rhs);
+
+ for (size_t i = 0; i < 3; i++) {
+ cr_expect(F64_EQ(out.xs[i], lhs.xs[i] - rhs.xs[i], 1e-9));
+ cr_expect(F64_EQ(out.ys[i], lhs.ys[i] - rhs.ys[i], 1e-9));
+ }
+}
+
+Test(vec2d_soa, scale_n) {
+ vec2d_soa_t in = { .xs = (f64[]){1.0, 2.0, 3.0}, .ys = (f64[]){4.0, 5.0, 6.0}, .size = 3 };
+
+ f64 out_x[3], out_y[3];
+ vec2d_soa_t out = { .xs = out_x, .ys = out_y, .size = 3 };
+
+ vec2d_soa_scale_n(&out, &in, 10.0);
+
+ for (size_t i = 0; i < 3; i++) {
+ cr_expect(F64_EQ(out.xs[i], in.xs[i] * 10.0, 1e-9));
+ cr_expect(F64_EQ(out.ys[i], in.ys[i] * 10.0, 1e-9));
+ }
+}
+
+Test(vec2d_soa, norm_n_handles_zero_and_nonzero) {
+ vec2d_soa_t in = { .xs = (f64[]){3.0, 0.0}, .ys = (f64[]){4.0, 0.0}, .size = 2 };
+
+ f64 out_x[2], out_y[2];
+ vec2d_soa_t out = { .xs = out_x, .ys = out_y, .size = 2 };
+
+ vec2d_soa_norm_n(&out, &in);
+
+ cr_expect(F64_EQ(out.xs[0], 0.6, 1e-9));
+ cr_expect(F64_EQ(out.ys[0], 0.8, 1e-9));
+ cr_expect(F64_EQ(out.xs[1], 0.0, 1e-9));
+ cr_expect(F64_EQ(out.ys[1], 0.0, 1e-9));
+}
+
+Test(vec2d_soa, average) {
+ vec2d_soa_t vs = { .xs = (f64[]){2.0, 4.0, 6.0}, .ys = (f64[]){10.0, 20.0, 30.0}, .size = 3 };
+
+ vec2d_t avg = vec2d_soa_average(&vs);
+
+ cr_expect(F64_EQ(avg.x, 4.0, 1e-9));
+ cr_expect(F64_EQ(avg.y, 20.0, 1e-9));
+}
diff --git a/tests/wav.c b/tests/wav.c
index 29b9daf..6a29197 100644
--- a/tests/wav.c
+++ b/tests/wav.c
@@ -235,6 +235,51 @@ void write_sine_teardown(void) {
close(write_sine_fd);
}
+static char non_pcm_path[] = "/tmp/steez_testwav_nonpcm_XXXXXX";
+static int non_pcm_fd;
+
+void non_pcm_setup(void) {
+ non_pcm_fd = mkstemp(non_pcm_path);
+ cr_assert_geq(non_pcm_fd, 0, "mkstemp failed");
+}
+
+void non_pcm_teardown(void) {
+ unlink(non_pcm_path);
+ close(non_pcm_fd);
+}
+
+Test(wav, read_rejects_non_pcm_format, .init = non_pcm_setup, .fini = non_pcm_teardown) {
+ FILE *wav_file = fdopen(non_pcm_fd, "wb");
+ cr_assert_not_null(wav_file);
+
+ smrt_arena_t *arena = smrt_arena_create(KiB(64), KiB(4), false);
+
+ u8 sample = 128;
+ wav_data_t data = { .samples = &sample, .sample_count = 1 };
+
+ wav_fmt_chunk_t fmtchunk = make_wav_fmt_chunk(1, 8000, 8);
+ fmtchunk.audio_format = 3; // IEEE 754 float, not PCM
+
+ cr_assert(write_wav_file(wav_file, &fmtchunk, data));
+
+ fclose(wav_file);
+
+ FILE *readback_file = fopen(non_pcm_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);
+
+ cr_expect_eq(read_fmtchunk.audio_format, 3);
+ cr_expect_eq(read_data.samples, NULL);
+
+ smrt_arena_destroy(arena);
+}
+
Test(wav, write_sine, .init = write_sine_setup, .fini = write_sine_teardown) {
FILE *wav_file = fdopen(write_sine_fd, "wb");