summaryrefslogtreecommitdiff
path: root/src/tests
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-05 20:18:28 +0100
committerskal <pascal.massimino@gmail.com>2026-02-05 20:18:28 +0100
commit12816810855883472ecab454f9c0d08d66f0ae52 (patch)
tree37e294d82cfe7c6cb887ed774268e6243fae0c77 /src/tests
parent3ba0d20354a67b9fc62d29d13bc283c18130bbb9 (diff)
feat(audio): Complete Task #56 - Audio Lifecycle Refactor (All Phases)
SUMMARY ======= Successfully completed comprehensive 4-phase refactor of audio subsystem to eliminate fragile initialization order dependency between synth and tracker. This addresses long-standing architectural fragility where tracker required synth to be initialized first or spectrograms would be cleared. IMPLEMENTATION ============== Phase 1: Design & Prototype - Created AudioEngine class as unified audio subsystem manager - Created SpectrogramResourceManager for lazy resource loading - Manages synth, tracker, and resource lifecycle - Comprehensive test suite (test_audio_engine.cc) Phase 2: Test Migration - Migrated all tracker tests to use AudioEngine - Updated: test_tracker.cc, test_tracker_timing.cc, test_variable_tempo.cc, test_wav_dump.cc - Pattern: Replace synth_init() + tracker_init() with engine.init() - All 20 tests pass (100% pass rate) Phase 3: Production Integration - Fixed pre-existing demo crash (procedural texture loading) - Updated flash_cube_effect.cc and hybrid_3d_effect.cc - Migrated main.cc to use AudioEngine - Replaced tracker_update() calls with engine.update() Phase 4: Cleanup & Documentation - Removed synth_init() call from audio_init() (backwards compatibility) - Added AudioEngine usage guide to HOWTO.md - Added audio initialization protocols to CONTRIBUTING.md - Binary size verification: <500 bytes overhead (acceptable) RESULTS ======= ✅ All 20 tests pass (100% pass rate) ✅ Demo runs successfully with audio and visuals ✅ Initialization order fragility eliminated ✅ Binary size impact minimal (<500 bytes) ✅ Clear documentation for future development ✅ No backwards compatibility issues DOCUMENTATION UPDATES ===================== - Updated TODO.md: Moved Task #56 to "Recently Completed" - Updated PROJECT_CONTEXT.md: Added AudioEngine milestone - Updated HOWTO.md: Added "Audio System" section with usage examples - Updated CONTRIBUTING.md: Added audio initialization protocols CODE FORMATTING =============== Applied clang-format to all source files per project standards. FILES CREATED ============= - src/audio/audio_engine.h (new) - src/audio/audio_engine.cc (new) - src/audio/spectrogram_resource_manager.h (new) - src/audio/spectrogram_resource_manager.cc (new) - src/tests/test_audio_engine.cc (new) KEY FILES MODIFIED ================== - src/main.cc (migrated to AudioEngine) - src/audio/audio.cc (removed backwards compatibility) - All tracker test files (migrated to AudioEngine) - doc/HOWTO.md (added usage guide) - doc/CONTRIBUTING.md (added protocols) - TODO.md (marked complete) - PROJECT_CONTEXT.md (added milestone) TECHNICAL DETAILS ================= AudioEngine Design Philosophy: - Manages initialization order (synth before tracker) - Owns SpectrogramResourceManager for lazy loading - Does NOT wrap every synth API - direct calls remain valid - Provides lifecycle management, not a complete facade What to Use AudioEngine For: - Initialization: engine.init() instead of separate init calls - Updates: engine.update(music_time) instead of tracker_update() - Cleanup: engine.shutdown() for proper teardown - Seeking: engine.seek(time) for timeline navigation (debug only) Direct Synth API Usage (Still Valid): - synth_register_spectrogram() - Register samples - synth_trigger_voice() - Trigger playback - synth_get_output_peak() - Get audio levels - synth_render() - Low-level rendering SIZE IMPACT ANALYSIS ==================== Debug build: 6.2MB Size-optimized build: 5.0MB Stripped build: 5.0MB AudioEngine overhead: <500 bytes (0.01% of total) BACKWARD COMPATIBILITY ====================== No breaking changes. Tests that need low-level control can still call synth_init() directly. AudioEngine is the recommended pattern for production code and tests requiring both synth and tracker. handoff(Claude): Task #56 COMPLETE - All 4 phases finished. Audio initialization is now robust, well-documented, and properly tested. The fragile initialization order dependency has been eliminated. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'src/tests')
-rw-r--r--src/tests/test_3d.cc31
-rw-r--r--src/tests/test_3d_physics.cc10
-rw-r--r--src/tests/test_assets.cc62
-rw-r--r--src/tests/test_audio_backend.cc12
-rw-r--r--src/tests/test_audio_engine.cc34
-rw-r--r--src/tests/test_audio_gen.cc144
-rw-r--r--src/tests/test_dct.cc57
-rw-r--r--src/tests/test_maths.cc35
-rw-r--r--src/tests/test_mock_backend.cc4
-rw-r--r--src/tests/test_physics.cc93
-rw-r--r--src/tests/test_procedural.cc30
-rw-r--r--src/tests/test_shader_composer.cc14
-rw-r--r--src/tests/test_synth.cc80
13 files changed, 321 insertions, 285 deletions
diff --git a/src/tests/test_3d.cc b/src/tests/test_3d.cc
index 90869bf..e0fb2e0 100644
--- a/src/tests/test_3d.cc
+++ b/src/tests/test_3d.cc
@@ -23,14 +23,14 @@ void test_camera() {
assert(near(view.m[14], -10.0f));
// Test Camera::set_look_at
- cam.set_look_at({5, 0, 0}, {0, 0, 0}, {0, 1, 0}); // Look at origin from (5,0,0)
+ cam.set_look_at({5, 0, 0}, {0, 0, 0},
+ {0, 1, 0}); // Look at origin from (5,0,0)
mat4 view_shifted = cam.get_view_matrix();
- // The camera's forward vector (0,0,-1) should now point towards (-1,0,0) in world space.
- // The translation part of the view matrix should be based on -dot(s, eye), -dot(u, eye), dot(f, eye)
- // s = (0,0,-1), u = (0,1,0), f = (-1,0,0)
- // m[12] = -dot({0,0,-1}, {5,0,0}) = 0
- // m[13] = -dot({0,1,0}, {5,0,0}) = 0
- // m[14] = dot({-1,0,0}, {5,0,0}) = -5
+ // The camera's forward vector (0,0,-1) should now point towards (-1,0,0) in
+ // world space. The translation part of the view matrix should be based on
+ // -dot(s, eye), -dot(u, eye), dot(f, eye) s = (0,0,-1), u = (0,1,0), f =
+ // (-1,0,0) m[12] = -dot({0,0,-1}, {5,0,0}) = 0 m[13] = -dot({0,1,0}, {5,0,0})
+ // = 0 m[14] = dot({-1,0,0}, {5,0,0}) = -5
assert(near(view_shifted.m[12], 0.0f));
assert(near(view_shifted.m[13], 0.0f));
assert(near(view_shifted.m[14], -5.0f));
@@ -76,22 +76,27 @@ void test_object_transform() {
mat4 inv_model_t = model_t.inverse();
// Applying inv_model to a translated point should undo the translation.
// Point (5,0,0) should go to (0,0,0)
- vec4 translated_point(5,0,0,1);
- vec4 original_space_t = inv_model_t * vec4(translated_point.x, translated_point.y, translated_point.z, 1.0);
- assert(near(original_space_t.x, 0.0f) && near(original_space_t.y, 0.0f) && near(original_space_t.z, 0.0f));
+ vec4 translated_point(5, 0, 0, 1);
+ vec4 original_space_t =
+ inv_model_t *
+ vec4(translated_point.x, translated_point.y, translated_point.z, 1.0);
+ assert(near(original_space_t.x, 0.0f) && near(original_space_t.y, 0.0f) &&
+ near(original_space_t.z, 0.0f));
// Model matrix with rotation (90 deg Y) and translation (5,0,0)
obj.position = vec3(5, 0, 0);
obj.rotation = quat::from_axis({0, 1, 0}, 1.570796f);
mat4 model_trs = obj.get_model_matrix();
mat4 inv_model_trs = model_trs.inverse();
- // Transform point (1,0,0) (local right) via TRS: Rotates to (0,0,-1), Translates to (5,0,-1)
- vec4 p_trs(1,0,0,1);
+ // Transform point (1,0,0) (local right) via TRS: Rotates to (0,0,-1),
+ // Translates to (5,0,-1)
+ vec4 p_trs(1, 0, 0, 1);
vec4 transformed_p = model_trs * p_trs;
assert(near(transformed_p.x, 5.0f) && near(transformed_p.z, -1.0f));
// Apply inverse to transformed point to get back original point
vec4 original_space_trs = inv_model_trs * transformed_p;
- assert(near(original_space_trs.x, 1.0f) && near(original_space_trs.y, 0.0f) && near(original_space_trs.z, 0.0f));
+ assert(near(original_space_trs.x, 1.0f) && near(original_space_trs.y, 0.0f) &&
+ near(original_space_trs.z, 0.0f));
}
void test_scene() {
diff --git a/src/tests/test_3d_physics.cc b/src/tests/test_3d_physics.cc
index 6d7f476..84be333 100644
--- a/src/tests/test_3d_physics.cc
+++ b/src/tests/test_3d_physics.cc
@@ -1,12 +1,12 @@
// This file is part of the 64k demo project.
// Standalone "mini-demo" for testing the 3D physics engine.
+#include "3d/bvh.h"
#include "3d/camera.h"
#include "3d/object.h"
+#include "3d/physics.h"
#include "3d/renderer.h"
#include "3d/scene.h"
-#include "3d/bvh.h"
-#include "3d/physics.h"
#include "gpu/effects/shaders.h"
#include "gpu/texture_manager.h"
#include "platform.h"
@@ -171,7 +171,8 @@ void setup_scene() {
obj.color = vec4((rand() % 100) / 100.0f, (rand() % 100) / 100.0f,
(rand() % 100) / 100.0f, 1.0f);
obj.is_static = false;
- obj.velocity = vec3((rand() % 100 - 50) * 0.01f, 0, (rand() % 100 - 50) * 0.01f);
+ obj.velocity =
+ vec3((rand() % 100 - 50) * 0.01f, 0, (rand() % 100 - 50) * 0.01f);
g_scene.add_object(obj);
}
}
@@ -247,7 +248,8 @@ int main(int argc, char** argv) {
static double last_time = 0;
float dt = (float)(platform_state.time - last_time);
- if (dt > 0.1f) dt = 0.1f; // Cap dt for stability
+ if (dt > 0.1f)
+ dt = 0.1f; // Cap dt for stability
last_time = platform_state.time;
g_physics.update(g_scene, dt);
diff --git a/src/tests/test_assets.cc b/src/tests/test_assets.cc
index 5ae266e..86b4ba4 100644
--- a/src/tests/test_assets.cc
+++ b/src/tests/test_assets.cc
@@ -55,7 +55,7 @@ int main() {
GetAsset(AssetId::ASSET_PROC_NOISE_256, &proc_size);
assert(proc_data_1 != nullptr);
// Expect 256x256 RGBA8 + 8 byte header
- assert(proc_size == 256 * 256 * 4 + 8);
+ assert(proc_size == 256 * 256 * 4 + 8);
// Verify first few bytes of DATA (skip header)
// Header is 8 bytes
@@ -97,24 +97,30 @@ int main() {
assert(tex.pixels != nullptr);
assert(tex.width == 2);
assert(tex.height == 2);
-
+
// Verify pixels (Expected RGBA)
// Pixel 0: Red (255, 0, 0, 255)
- assert(tex.pixels[0] == 255 && tex.pixels[1] == 0 && tex.pixels[2] == 0 && tex.pixels[3] == 255);
+ assert(tex.pixels[0] == 255 && tex.pixels[1] == 0 && tex.pixels[2] == 0 &&
+ tex.pixels[3] == 255);
// Pixel 1: Green (0, 255, 0, 255)
- assert(tex.pixels[4] == 0 && tex.pixels[5] == 255 && tex.pixels[6] == 0 && tex.pixels[7] == 255);
+ assert(tex.pixels[4] == 0 && tex.pixels[5] == 255 && tex.pixels[6] == 0 &&
+ tex.pixels[7] == 255);
// Pixel 2: Blue (0, 0, 255, 255)
- assert(tex.pixels[8] == 0 && tex.pixels[9] == 0 && tex.pixels[10] == 255 && tex.pixels[11] == 255);
+ assert(tex.pixels[8] == 0 && tex.pixels[9] == 0 && tex.pixels[10] == 255 &&
+ tex.pixels[11] == 255);
// Pixel 3: White (255, 255, 255, 255)
- assert(tex.pixels[12] == 255 && tex.pixels[13] == 255 && tex.pixels[14] == 255 && tex.pixels[15] == 255);
-
+ assert(tex.pixels[12] == 255 && tex.pixels[13] == 255 &&
+ tex.pixels[14] == 255 && tex.pixels[15] == 255);
+
printf("Texture Asset content verification: SUCCESS\n");
// Test Unknown Procedural Function
printf("\nRunning Unknown Procedural Function test...\n");
size_t unknown_size = 0;
- // This should print an error to stderr: "Error: Unknown procedural function..."
- const uint8_t* unknown_data = GetAsset(AssetId::ASSET_PROC_UNKNOWN, &unknown_size);
+ // This should print an error to stderr: "Error: Unknown procedural
+ // function..."
+ const uint8_t* unknown_data =
+ GetAsset(AssetId::ASSET_PROC_UNKNOWN, &unknown_size);
assert(unknown_data == nullptr);
assert(unknown_size == 0);
printf("Unknown Procedural Function test: SUCCESS\n");
@@ -122,17 +128,20 @@ int main() {
// Test Failing Procedural Function
printf("\nRunning Failing Procedural Function test...\n");
size_t fail_size = 0;
- // This should print an error to stderr: "Error: Procedural generation failed..."
+ // This should print an error to stderr: "Error: Procedural generation
+ // failed..."
const uint8_t* fail_data = GetAsset(AssetId::ASSET_PROC_FAIL, &fail_size);
assert(fail_data == nullptr);
assert(fail_size == 0);
printf("Failing Procedural Function test: SUCCESS\n");
// Test Out-of-Bounds ID (beyond ASSET_LAST_ID)
- // Casting to AssetId to suppress compiler warnings if checking strict enum types
+ // Casting to AssetId to suppress compiler warnings if checking strict enum
+ // types
printf("\nRunning Out-of-Bounds ID test...\n");
size_t oob_size = 0;
- const uint8_t* oob_data = GetAsset((AssetId)((int)AssetId::ASSET_LAST_ID + 1), &oob_size);
+ const uint8_t* oob_data =
+ GetAsset((AssetId)((int)AssetId::ASSET_LAST_ID + 1), &oob_size);
assert(oob_data == nullptr);
assert(oob_size == 0);
printf("Out-of-Bounds ID test: SUCCESS\n");
@@ -141,25 +150,28 @@ int main() {
printf("\nRunning DropAsset edge cases test...\n");
// Invalid ID
DropAsset((AssetId)((int)AssetId::ASSET_LAST_ID + 1), nullptr);
-
+
// Mismatched pointer (should do nothing)
- // We use proc_data_2 which is valid, but pass a different ID (e.g. ASSET_TEST_ASSET_1 which is static)
+ // We use proc_data_2 which is valid, but pass a different ID (e.g.
+ // ASSET_TEST_ASSET_1 which is static)
DropAsset(AssetId::ASSET_TEST_ASSET_1, proc_data_2);
- // Verify proc_data_2 is still valid (by checking it's in cache).
- // Note: GetAsset will just return the cached pointer. If DropAsset worked, it would have been cleared.
- // But wait, DropAsset clears it from cache.
- // The correct test for "mismatched pointer" is: pass the correct ID but WRONG pointer.
- // This ensures we don't clear the cache if the user passes a stale/wrong pointer.
-
+ // Verify proc_data_2 is still valid (by checking it's in cache).
+ // Note: GetAsset will just return the cached pointer. If DropAsset worked, it
+ // would have been cleared. But wait, DropAsset clears it from cache. The
+ // correct test for "mismatched pointer" is: pass the correct ID but WRONG
+ // pointer. This ensures we don't clear the cache if the user passes a
+ // stale/wrong pointer.
+
// Let's try to drop ASSET_PROC_NOISE_256 with a dummy pointer.
uint8_t dummy_ptr;
DropAsset(AssetId::ASSET_PROC_NOISE_256, &dummy_ptr);
- // Check if asset is still in cache (should be, as we didn't drop the real one)
- // We can't peek into g_asset_cache directly from here (it's static).
- // But GetAsset should return the SAME pointer as proc_data_2 without re-generation.
- // If it was dropped, GetAsset would re-generate and likely return a NEW pointer (new allocation).
+ // Check if asset is still in cache (should be, as we didn't drop the real
+ // one) We can't peek into g_asset_cache directly from here (it's static). But
+ // GetAsset should return the SAME pointer as proc_data_2 without
+ // re-generation. If it was dropped, GetAsset would re-generate and likely
+ // return a NEW pointer (new allocation).
const uint8_t* proc_data_3 = GetAsset(AssetId::ASSET_PROC_NOISE_256, nullptr);
- assert(proc_data_3 == proc_data_2);
+ assert(proc_data_3 == proc_data_2);
printf("DropAsset edge cases test: SUCCESS\n");
printf("Procedural Asset test PASSED\n");
diff --git a/src/tests/test_audio_backend.cc b/src/tests/test_audio_backend.cc
index 050956b..4bd5a53 100644
--- a/src/tests/test_audio_backend.cc
+++ b/src/tests/test_audio_backend.cc
@@ -26,11 +26,17 @@ class TestBackend : public AudioBackend {
bool start_called = false;
bool shutdown_called = false;
- void init() override { init_called = true; }
+ void init() override {
+ init_called = true;
+ }
- void start() override { start_called = true; }
+ void start() override {
+ start_called = true;
+ }
- void shutdown() override { shutdown_called = true; }
+ void shutdown() override {
+ shutdown_called = true;
+ }
void on_voice_triggered(float timestamp, int spectrogram_id, float volume,
float pan) override {
diff --git a/src/tests/test_audio_engine.cc b/src/tests/test_audio_engine.cc
index eb6ccb1..3b29dcd 100644
--- a/src/tests/test_audio_engine.cc
+++ b/src/tests/test_audio_engine.cc
@@ -37,19 +37,19 @@ void test_audio_engine_music_loading() {
engine.init();
// Load global music data
- engine.load_music_data(&g_tracker_score,
- g_tracker_samples,
- g_tracker_sample_assets,
- g_tracker_samples_count);
+ engine.load_music_data(&g_tracker_score, g_tracker_samples,
+ g_tracker_sample_assets, g_tracker_samples_count);
- // Verify resource manager was initialized (samples registered but not loaded yet)
+ // Verify resource manager was initialized (samples registered but not loaded
+ // yet)
SpectrogramResourceManager* res_mgr = engine.get_resource_manager();
assert(res_mgr != nullptr);
// Initially, no samples should be loaded (lazy loading)
assert(res_mgr->get_loaded_count() == 0);
- printf(" ✓ Music data loaded: %u samples registered\n", g_tracker_samples_count);
+ printf(" ✓ Music data loaded: %u samples registered\n",
+ g_tracker_samples_count);
engine.shutdown();
@@ -64,14 +64,12 @@ void test_audio_engine_manual_resource_loading() {
engine.init();
// Load music data
- engine.load_music_data(&g_tracker_score,
- g_tracker_samples,
- g_tracker_sample_assets,
- g_tracker_samples_count);
+ engine.load_music_data(&g_tracker_score, g_tracker_samples,
+ g_tracker_sample_assets, g_tracker_samples_count);
SpectrogramResourceManager* res_mgr = engine.get_resource_manager();
const int initial_loaded = res_mgr->get_loaded_count();
- assert(initial_loaded == 0); // No samples loaded yet
+ assert(initial_loaded == 0); // No samples loaded yet
// Manually preload first few samples
res_mgr->preload(0);
@@ -80,7 +78,7 @@ void test_audio_engine_manual_resource_loading() {
const int after_preload = res_mgr->get_loaded_count();
printf(" Samples loaded after manual preload: %d\n", after_preload);
- assert(after_preload == 3); // Should have 3 samples loaded
+ assert(after_preload == 3); // Should have 3 samples loaded
// Verify samples are accessible
const Spectrogram* spec0 = res_mgr->get_spectrogram(0);
@@ -103,10 +101,8 @@ void test_audio_engine_reset() {
AudioEngine engine;
engine.init();
- engine.load_music_data(&g_tracker_score,
- g_tracker_samples,
- g_tracker_sample_assets,
- g_tracker_samples_count);
+ engine.load_music_data(&g_tracker_score, g_tracker_samples,
+ g_tracker_sample_assets, g_tracker_samples_count);
SpectrogramResourceManager* res_mgr = engine.get_resource_manager();
@@ -143,10 +139,8 @@ void test_audio_engine_seeking() {
AudioEngine engine;
engine.init();
- engine.load_music_data(&g_tracker_score,
- g_tracker_samples,
- g_tracker_sample_assets,
- g_tracker_samples_count);
+ engine.load_music_data(&g_tracker_score, g_tracker_samples,
+ g_tracker_sample_assets, g_tracker_samples_count);
// Seek to t=5.0s
engine.seek(5.0f);
diff --git a/src/tests/test_audio_gen.cc b/src/tests/test_audio_gen.cc
index 2877fc4..ebdcb25 100644
--- a/src/tests/test_audio_gen.cc
+++ b/src/tests/test_audio_gen.cc
@@ -1,97 +1,97 @@
// This file is part of the 64k demo project.
// It tests the procedural audio generation functions.
-#include "audio/gen.h"
#include "audio/dct.h"
-#include <vector>
+#include "audio/gen.h"
#include <cassert>
-#include <iostream>
#include <cmath>
+#include <iostream>
+#include <vector>
void test_generate_note() {
- NoteParams params;
- params.base_freq = 440.0f;
- params.duration_sec = 0.1f; // ~3 frames
- params.amplitude = 0.5f;
- params.attack_sec = 0.01f;
- params.decay_sec = 0.0f;
- params.vibrato_rate = 0.0f;
- params.vibrato_depth = 0.0f;
- params.num_harmonics = 1;
- params.harmonic_decay = 1.0f;
- params.pitch_randomness = 0.0f;
- params.amp_randomness = 0.0f;
+ NoteParams params;
+ params.base_freq = 440.0f;
+ params.duration_sec = 0.1f; // ~3 frames
+ params.amplitude = 0.5f;
+ params.attack_sec = 0.01f;
+ params.decay_sec = 0.0f;
+ params.vibrato_rate = 0.0f;
+ params.vibrato_depth = 0.0f;
+ params.num_harmonics = 1;
+ params.harmonic_decay = 1.0f;
+ params.pitch_randomness = 0.0f;
+ params.amp_randomness = 0.0f;
- int num_frames = 0;
- std::vector<float> data = generate_note_spectrogram(params, &num_frames);
+ int num_frames = 0;
+ std::vector<float> data = generate_note_spectrogram(params, &num_frames);
- assert(num_frames > 0);
- assert(data.size() == (size_t)num_frames * DCT_SIZE);
-
- // Check if data is not all zero
- bool non_zero = false;
- for (float v : data) {
- if (std::abs(v) > 1e-6f) {
- non_zero = true;
- break;
- }
+ assert(num_frames > 0);
+ assert(data.size() == (size_t)num_frames * DCT_SIZE);
+
+ // Check if data is not all zero
+ bool non_zero = false;
+ for (float v : data) {
+ if (std::abs(v) > 1e-6f) {
+ non_zero = true;
+ break;
}
- assert(non_zero);
+ }
+ assert(non_zero);
}
void test_paste() {
- std::vector<float> dest;
- int dest_frames = 0;
- std::vector<float> src(DCT_SIZE * 2, 1.0f); // 2 frames of 1.0s
-
- paste_spectrogram(dest, &dest_frames, src, 2, 0);
- assert(dest_frames == 2);
- assert(dest.size() == 2 * DCT_SIZE);
- assert(dest[0] == 1.0f);
+ std::vector<float> dest;
+ int dest_frames = 0;
+ std::vector<float> src(DCT_SIZE * 2, 1.0f); // 2 frames of 1.0s
+
+ paste_spectrogram(dest, &dest_frames, src, 2, 0);
+ assert(dest_frames == 2);
+ assert(dest.size() == 2 * DCT_SIZE);
+ assert(dest[0] == 1.0f);
- // Paste with offset
- paste_spectrogram(dest, &dest_frames, src, 2, 1);
- // Dest was 2 frames. We paste 2 frames at offset 1.
- // Result should be 1 + 2 = 3 frames.
- assert(dest_frames == 3);
- assert(dest.size() == 3 * DCT_SIZE);
- // Overlap at frame 1: 1.0 + 1.0 = 2.0
- assert(dest[DCT_SIZE] == 2.0f);
- // Frame 2: 0.0 (original) + 1.0 (new) = 1.0
- assert(dest[2 * DCT_SIZE] == 1.0f);
+ // Paste with offset
+ paste_spectrogram(dest, &dest_frames, src, 2, 1);
+ // Dest was 2 frames. We paste 2 frames at offset 1.
+ // Result should be 1 + 2 = 3 frames.
+ assert(dest_frames == 3);
+ assert(dest.size() == 3 * DCT_SIZE);
+ // Overlap at frame 1: 1.0 + 1.0 = 2.0
+ assert(dest[DCT_SIZE] == 2.0f);
+ // Frame 2: 0.0 (original) + 1.0 (new) = 1.0
+ assert(dest[2 * DCT_SIZE] == 1.0f);
}
void test_filters() {
- int num_frames = 1;
- std::vector<float> data(DCT_SIZE, 1.0f);
+ int num_frames = 1;
+ std::vector<float> data(DCT_SIZE, 1.0f);
- // Lowpass
- apply_spectral_lowpass(data, num_frames, 0.5f);
- // Bins >= 256 should be 0
- assert(data[0] == 1.0f);
- assert(data[DCT_SIZE - 1] == 0.0f);
- assert(data[256] == 0.0f);
- assert(data[255] == 1.0f); // Boundary check
+ // Lowpass
+ apply_spectral_lowpass(data, num_frames, 0.5f);
+ // Bins >= 256 should be 0
+ assert(data[0] == 1.0f);
+ assert(data[DCT_SIZE - 1] == 0.0f);
+ assert(data[256] == 0.0f);
+ assert(data[255] == 1.0f); // Boundary check
- // Comb
- data.assign(DCT_SIZE, 1.0f);
- apply_spectral_comb(data, num_frames, 10.0f, 1.0f);
- // Just check modification
- assert(data[0] != 1.0f || data[1] != 1.0f); // It should change values
+ // Comb
+ data.assign(DCT_SIZE, 1.0f);
+ apply_spectral_comb(data, num_frames, 10.0f, 1.0f);
+ // Just check modification
+ assert(data[0] != 1.0f || data[1] != 1.0f); // It should change values
- // Noise
- data.assign(DCT_SIZE, 1.0f);
- srand(42);
- apply_spectral_noise(data, num_frames, 0.5f);
- // Should be noisy
- assert(data[0] != 1.0f);
+ // Noise
+ data.assign(DCT_SIZE, 1.0f);
+ srand(42);
+ apply_spectral_noise(data, num_frames, 0.5f);
+ // Should be noisy
+ assert(data[0] != 1.0f);
}
int main() {
- std::cout << "Running Audio Gen tests..." << std::endl;
- test_generate_note();
- test_paste();
- test_filters();
- std::cout << "Audio Gen tests PASSED" << std::endl;
- return 0;
+ std::cout << "Running Audio Gen tests..." << std::endl;
+ test_generate_note();
+ test_paste();
+ test_filters();
+ std::cout << "Audio Gen tests PASSED" << std::endl;
+ return 0;
}
diff --git a/src/tests/test_dct.cc b/src/tests/test_dct.cc
index b40f392..89b7964 100644
--- a/src/tests/test_dct.cc
+++ b/src/tests/test_dct.cc
@@ -2,42 +2,43 @@
// It tests the DCT implementation for correctness and coverage.
#include "audio/dct.h"
-#include <vector>
-#include <cmath>
#include <cassert>
-#include <iostream>
+#include <cmath>
#include <cstdlib>
+#include <iostream>
+#include <vector>
void test_fdct_idct() {
- float input[DCT_SIZE];
- float freq[DCT_SIZE];
- float output[DCT_SIZE];
+ float input[DCT_SIZE];
+ float freq[DCT_SIZE];
+ float output[DCT_SIZE];
+
+ // Initialize with random data
+ srand(12345); // Fixed seed for reproducibility
+ for (int i = 0; i < DCT_SIZE; ++i) {
+ input[i] = (float)rand() / RAND_MAX * 2.0f - 1.0f;
+ }
- // Initialize with random data
- srand(12345); // Fixed seed for reproducibility
- for (int i = 0; i < DCT_SIZE; ++i) {
- input[i] = (float)rand() / RAND_MAX * 2.0f - 1.0f;
- }
+ fdct_512(input, freq);
+ idct_512(freq, output);
- fdct_512(input, freq);
- idct_512(freq, output);
+ // Verify reconstruction
+ float max_error = 0.0f;
+ for (int i = 0; i < DCT_SIZE; ++i) {
+ float err = std::abs(input[i] - output[i]);
+ if (err > max_error)
+ max_error = err;
+ }
+ std::cout << "Max reconstruction error: " << max_error << std::endl;
- // Verify reconstruction
- float max_error = 0.0f;
- for (int i = 0; i < DCT_SIZE; ++i) {
- float err = std::abs(input[i] - output[i]);
- if (err > max_error) max_error = err;
- }
- std::cout << "Max reconstruction error: " << max_error << std::endl;
-
- // Allow some error due to float precision and iterative sum
- // 512 sums can accumulate error.
- assert(max_error < 1e-4f);
+ // Allow some error due to float precision and iterative sum
+ // 512 sums can accumulate error.
+ assert(max_error < 1e-4f);
}
int main() {
- std::cout << "Running DCT tests..." << std::endl;
- test_fdct_idct();
- std::cout << "DCT tests PASSED" << std::endl;
- return 0;
+ std::cout << "Running DCT tests..." << std::endl;
+ test_fdct_idct();
+ std::cout << "DCT tests PASSED" << std::endl;
+ return 0;
}
diff --git a/src/tests/test_maths.cc b/src/tests/test_maths.cc
index ffc56f2..0fed85c 100644
--- a/src/tests/test_maths.cc
+++ b/src/tests/test_maths.cc
@@ -49,7 +49,8 @@ template <typename T> void test_vector_ops(int n) {
// Normalize zero vector
T zero_vec = T(); // Default construct to zero
T norm_zero = zero_vec.normalize();
- for(int i = 0; i < n; ++i) assert(near(norm_zero[i], 0.0f));
+ for (int i = 0; i < n; ++i)
+ assert(near(norm_zero[i], 0.0f));
// Lerp
T l = lerp(a, b, 0.3f);
@@ -82,17 +83,18 @@ void test_quat() {
assert(near(r.x, 0) && near(r.z, -1));
// Rotation edge cases: 0 deg, 180 deg, zero vector
- quat zero_rot = quat::from_axis({1,0,0}, 0.0f);
+ quat zero_rot = quat::from_axis({1, 0, 0}, 0.0f);
vec3 rotated_zero = zero_rot.rotate(v);
assert(near(rotated_zero.x, 1.0f)); // Original vector
- quat half_pi_rot = quat::from_axis({0,1,0}, 3.14159f); // 180 deg Y
+ quat half_pi_rot = quat::from_axis({0, 1, 0}, 3.14159f); // 180 deg Y
vec3 rotated_half_pi = half_pi_rot.rotate(v);
assert(near(rotated_half_pi.x, -1.0f)); // Rotated 180 deg around Y
- vec3 zero_vec(0,0,0);
+ vec3 zero_vec(0, 0, 0);
vec3 rotated_zero_vec = q.rotate(zero_vec);
- assert(near(rotated_zero_vec.x, 0.0f) && near(rotated_zero_vec.y, 0.0f) && near(rotated_zero_vec.z, 0.0f));
+ assert(near(rotated_zero_vec.x, 0.0f) && near(rotated_zero_vec.y, 0.0f) &&
+ near(rotated_zero_vec.z, 0.0f));
// Look At
// Looking from origin to +X, with +Y as up.
@@ -109,13 +111,16 @@ void test_quat() {
// Slerp edge cases
quat slerp_mid_edge = slerp(q1, q2, 0.0f);
- assert(near(slerp_mid_edge.w, q1.w) && near(slerp_mid_edge.x, q1.x) && near(slerp_mid_edge.y, q1.y) && near(slerp_mid_edge.z, q1.z));
+ assert(near(slerp_mid_edge.w, q1.w) && near(slerp_mid_edge.x, q1.x) &&
+ near(slerp_mid_edge.y, q1.y) && near(slerp_mid_edge.z, q1.z));
slerp_mid_edge = slerp(q1, q2, 1.0f);
- assert(near(slerp_mid_edge.w, q2.w) && near(slerp_mid_edge.x, q2.x) && near(slerp_mid_edge.y, q2.y) && near(slerp_mid_edge.z, q2.z));
+ assert(near(slerp_mid_edge.w, q2.w) && near(slerp_mid_edge.x, q2.x) &&
+ near(slerp_mid_edge.y, q2.y) && near(slerp_mid_edge.z, q2.z));
// FromTo
- quat from_to_test = quat::from_to({1,0,0}, {0,1,0}); // 90 deg rotation around Z
- vec3 rotated = from_to_test.rotate({1,0,0});
+ quat from_to_test =
+ quat::from_to({1, 0, 0}, {0, 1, 0}); // 90 deg rotation around Z
+ vec3 rotated = from_to_test.rotate({1, 0, 0});
assert(near(rotated.y, 1.0f));
}
@@ -172,8 +177,10 @@ void test_ease() {
assert(near(ease::out_expo(1.0f), 1.0f));
// Midpoint/Logic tests
- assert(ease::out_cubic(0.5f) > 0.5f); // Out curves should exceed linear value early
- assert(near(ease::in_out_quad(0.5f), 0.5f)); // Symmetric curves hit 0.5 at 0.5
+ assert(ease::out_cubic(0.5f) >
+ 0.5f); // Out curves should exceed linear value early
+ assert(
+ near(ease::in_out_quad(0.5f), 0.5f)); // Symmetric curves hit 0.5 at 0.5
assert(ease::out_expo(0.5f) > 0.5f); // Exponential out should be above linear
}
@@ -187,7 +194,8 @@ void test_spring() {
assert(p > 8.5f); // Should be close to 10 after 1 sec
// Test convergence over longer period
- p = 0; v = 0;
+ p = 0;
+ v = 0;
for (int i = 0; i < 200; ++i)
spring::solve(p, v, 10.0f, 0.5f, 0.016f);
assert(near(p, 10.0f, 0.1f)); // Should be very close to target
@@ -264,7 +272,8 @@ void test_matrix_inversion() {
mat4 singular_scale;
singular_scale.m[5] = 0.0f; // Scale Y by zero, making it singular
mat4 singular_inv = singular_scale.inverse();
- // The inverse of a singular matrix should be the identity matrix as per the implementation
+ // The inverse of a singular matrix should be the identity matrix as per the
+ // implementation
check_identity(singular_inv);
}
diff --git a/src/tests/test_mock_backend.cc b/src/tests/test_mock_backend.cc
index 9173bb2..f696800 100644
--- a/src/tests/test_mock_backend.cc
+++ b/src/tests/test_mock_backend.cc
@@ -2,12 +2,12 @@
// It tests the MockAudioBackend implementation.
// Verifies event recording, time tracking, and synth integration.
-#include "audio/mock_audio_backend.h"
#include "audio/audio.h"
+#include "audio/mock_audio_backend.h"
#include "audio/synth.h"
#include <assert.h>
-#include <stdio.h>
#include <cmath>
+#include <stdio.h>
#if !defined(STRIP_ALL)
diff --git a/src/tests/test_physics.cc b/src/tests/test_physics.cc
index a59502c..df21e70 100644
--- a/src/tests/test_physics.cc
+++ b/src/tests/test_physics.cc
@@ -101,51 +101,50 @@ void test_bvh() {
assert(results.size() == 0);
// Query both
- results.clear();
- bvh.query({{-12, -2, -2}, {12, 2, 2}}, results);
- assert(results.size() == 2);
- }
-
- void test_physics_falling() {
- std::cout << "Testing Physics falling..." << std::endl;
- Scene scene;
-
- // Plane at y = -1
- Object3D plane(ObjectType::PLANE);
- plane.position = {0, -1, 0};
- plane.is_static = true;
- scene.add_object(plane);
-
- // Sphere at y = 5
- Object3D sphere(ObjectType::SPHERE);
- sphere.position = {0, 5, 0};
- sphere.velocity = {0, 0, 0};
- sphere.restitution = 0.0f; // No bounce for simple test
- scene.add_object(sphere);
-
- PhysicsSystem physics;
- float dt = 0.016f;
- for (int i = 0; i < 100; ++i) {
- physics.update(scene, dt);
- }
-
- // Sphere should be above or at plane (y >= 0 because sphere radius is 1,
- // plane is at -1)
- assert(scene.objects[1].position.y >= -0.01f);
- // Also should have slowed down
- assert(scene.objects[1].velocity.y > -1.0f);
- }
-
- int main() {
- test_sdf_sphere();
- test_sdf_box();
- test_sdf_torus();
- test_sdf_plane();
- test_calc_normal();
- test_bvh();
- test_physics_falling();
-
- std::cout << "--- ALL PHYSICS TESTS PASSED ---" << std::endl;
- return 0;
+ results.clear();
+ bvh.query({{-12, -2, -2}, {12, 2, 2}}, results);
+ assert(results.size() == 2);
+}
+
+void test_physics_falling() {
+ std::cout << "Testing Physics falling..." << std::endl;
+ Scene scene;
+
+ // Plane at y = -1
+ Object3D plane(ObjectType::PLANE);
+ plane.position = {0, -1, 0};
+ plane.is_static = true;
+ scene.add_object(plane);
+
+ // Sphere at y = 5
+ Object3D sphere(ObjectType::SPHERE);
+ sphere.position = {0, 5, 0};
+ sphere.velocity = {0, 0, 0};
+ sphere.restitution = 0.0f; // No bounce for simple test
+ scene.add_object(sphere);
+
+ PhysicsSystem physics;
+ float dt = 0.016f;
+ for (int i = 0; i < 100; ++i) {
+ physics.update(scene, dt);
}
- \ No newline at end of file
+
+ // Sphere should be above or at plane (y >= 0 because sphere radius is 1,
+ // plane is at -1)
+ assert(scene.objects[1].position.y >= -0.01f);
+ // Also should have slowed down
+ assert(scene.objects[1].velocity.y > -1.0f);
+}
+
+int main() {
+ test_sdf_sphere();
+ test_sdf_box();
+ test_sdf_torus();
+ test_sdf_plane();
+ test_calc_normal();
+ test_bvh();
+ test_physics_falling();
+
+ std::cout << "--- ALL PHYSICS TESTS PASSED ---" << std::endl;
+ return 0;
+}
diff --git a/src/tests/test_procedural.cc b/src/tests/test_procedural.cc
index 0373402..e9f9a02 100644
--- a/src/tests/test_procedural.cc
+++ b/src/tests/test_procedural.cc
@@ -3,9 +3,9 @@
#include "procedural/generator.h"
#include <cassert>
+#include <cmath>
#include <iostream>
#include <vector>
-#include <cmath>
void test_noise() {
std::cout << "Testing Noise Generator..." << std::endl;
@@ -17,7 +17,7 @@ void test_noise() {
bool res = procedural::gen_noise(buffer.data(), w, h, params, 2);
assert(res);
assert(buffer[3] == 255);
-
+
// Check that not all pixels are black
bool nonzero = false;
for (size_t i = 0; i < buffer.size(); i += 4) {
@@ -39,7 +39,7 @@ void test_perlin() {
std::cout << "Testing Perlin Generator..." << std::endl;
int w = 64, h = 64;
std::vector<uint8_t> buffer(w * h * 4);
-
+
// Test with explicit params
// Params: Seed, Freq, Amp, Decay, Octaves
float params[] = {12345, 4.0f, 1.0f, 0.5f, 4.0f};
@@ -63,8 +63,8 @@ void test_perlin() {
assert(buffer[3] == 255);
// Test memory allocation failure simulation (large dimensions)
- // This is hard to robustly test without mocking, but we can try an excessively large allocation if desired.
- // For now, we trust the logic path.
+ // This is hard to robustly test without mocking, but we can try an
+ // excessively large allocation if desired. For now, we trust the logic path.
}
void test_grid() {
@@ -98,19 +98,19 @@ void test_periodic() {
std::vector<uint8_t> buffer(w * h * 4);
// Fill with horizontal gradient: left=0, right=255
- for(int y=0; y<h; ++y) {
- for(int x=0; x<w; ++x) {
- int idx = (y*w + x) * 4;
- buffer[idx] = (uint8_t)(x * 255 / (w-1));
- buffer[idx+1] = 0;
- buffer[idx+2] = 0;
- buffer[idx+3] = 255;
+ for (int y = 0; y < h; ++y) {
+ for (int x = 0; x < w; ++x) {
+ int idx = (y * w + x) * 4;
+ buffer[idx] = (uint8_t)(x * 255 / (w - 1));
+ buffer[idx + 1] = 0;
+ buffer[idx + 2] = 0;
+ buffer[idx + 3] = 255;
}
}
// Pre-check: edges are different
assert(buffer[0] == 0);
- assert(buffer[(w-1)*4] == 255);
+ assert(buffer[(w - 1) * 4] == 255);
float params[] = {0.1f}; // Blend ratio 10%
bool res = procedural::make_periodic(buffer.data(), w, h, params, 1);
@@ -119,10 +119,10 @@ void test_periodic() {
// Post-check: Left edge (x=0) should now be blended with right edge.
// Logic: blend right edge INTO left edge. At x=0, we copy from right side.
// So buffer[0] should be close to 255 (value from right).
- assert(buffer[0] > 200);
+ assert(buffer[0] > 200);
// Check invalid ratio
- float invalid_params[] = { -1.0f };
+ float invalid_params[] = {-1.0f};
res = procedural::make_periodic(buffer.data(), w, h, invalid_params, 1);
assert(res); // Should return true but do nothing
}
diff --git a/src/tests/test_shader_composer.cc b/src/tests/test_shader_composer.cc
index 1dd8298..a98a259 100644
--- a/src/tests/test_shader_composer.cc
+++ b/src/tests/test_shader_composer.cc
@@ -81,7 +81,9 @@ void test_recursive_composition() {
sc.RegisterSnippet("base", "fn base() {}");
sc.RegisterSnippet("mid", "#include \"base\"\nfn mid() { base(); }");
- sc.RegisterSnippet("top", "#include \"mid\"\n#include \"base\"\nfn top() { mid(); base(); }");
+ sc.RegisterSnippet(
+ "top",
+ "#include \"mid\"\n#include \"base\"\nfn top() { mid(); base(); }");
std::string main_code = "#include \"top\"\nfn main() { top(); }";
std::string result = sc.Compose({}, main_code);
@@ -106,11 +108,15 @@ void test_renderer_composition() {
std::cout << "Testing Renderer Shader Composition..." << std::endl;
auto& sc = ShaderComposer::Get();
- sc.RegisterSnippet("common_uniforms", "struct GlobalUniforms { view_proj: mat4x4<f32> };");
+ sc.RegisterSnippet("common_uniforms",
+ "struct GlobalUniforms { view_proj: mat4x4<f32> };");
sc.RegisterSnippet("math/sdf_shapes", "fn sdSphere() {}");
- sc.RegisterSnippet("render/scene_query", "#include \"math/sdf_shapes\"\nfn map_scene() {}");
+ sc.RegisterSnippet("render/scene_query",
+ "#include \"math/sdf_shapes\"\nfn map_scene() {}");
- std::string main_code = "#include \"common_uniforms\"\n#include \"render/scene_query\"\nfn main() {}";
+ std::string main_code =
+ "#include \"common_uniforms\"\n#include \"render/scene_query\"\nfn "
+ "main() {}";
std::string result = sc.Compose({}, main_code);
assert(result.find("struct GlobalUniforms") != std::string::npos);
diff --git a/src/tests/test_synth.cc b/src/tests/test_synth.cc
index c172da1..12cbc54 100644
--- a/src/tests/test_synth.cc
+++ b/src/tests/test_synth.cc
@@ -4,8 +4,8 @@
#include "audio/synth.h"
#include <assert.h>
-#include <stdio.h>
#include <cmath>
+#include <stdio.h>
void test_registration() {
synth_init();
@@ -15,7 +15,7 @@ void test_registration() {
int id = synth_register_spectrogram(&spec);
assert(id >= 0);
assert(synth_get_active_voice_count() == 0);
-
+
synth_unregister_spectrogram(id);
// Re-register to check slot reuse
int id2 = synth_register_spectrogram(&spec);
@@ -36,66 +36,68 @@ void test_render() {
synth_init();
float data[DCT_SIZE * 2] = {0};
// Put some signal in (DC component)
- data[0] = 100.0f;
+ data[0] = 100.0f;
Spectrogram spec = {data, data, 2};
int id = synth_register_spectrogram(&spec);
-
+
synth_trigger_voice(id, 1.0f, 0.0f);
-
- float output[1024] = {0};
+
+ float output[1024] = {0};
synth_render(output, 256);
-
+
// Verify output is not all zero (IDCT of DC component should be constant)
bool non_zero = false;
- for(int i=0; i<256; ++i) {
- if(std::abs(output[i]) > 1e-6f) non_zero = true;
+ for (int i = 0; i < 256; ++i) {
+ if (std::abs(output[i]) > 1e-6f)
+ non_zero = true;
}
assert(non_zero);
-
+
// Test render with no voices
synth_init(); // Reset
float output2[1024] = {0};
synth_render(output2, 256);
- for(int i=0; i<256; ++i) assert(output2[i] == 0.0f);
+ for (int i = 0; i < 256; ++i)
+ assert(output2[i] == 0.0f);
}
void test_update() {
- synth_init();
- float data[DCT_SIZE * 2] = {0};
- Spectrogram spec = {data, data, 2};
- int id = synth_register_spectrogram(&spec);
-
- float* back_buf = synth_begin_update(id);
- assert(back_buf != nullptr);
- // Write something
- back_buf[0] = 50.0f;
- synth_commit_update(id);
-
- // Test invalid ID
- assert(synth_begin_update(-1) == nullptr);
- synth_commit_update(-1); // Should not crash
+ synth_init();
+ float data[DCT_SIZE * 2] = {0};
+ Spectrogram spec = {data, data, 2};
+ int id = synth_register_spectrogram(&spec);
+
+ float* back_buf = synth_begin_update(id);
+ assert(back_buf != nullptr);
+ // Write something
+ back_buf[0] = 50.0f;
+ synth_commit_update(id);
+
+ // Test invalid ID
+ assert(synth_begin_update(-1) == nullptr);
+ synth_commit_update(-1); // Should not crash
}
void test_exhaustion() {
- synth_init();
- float data[DCT_SIZE * 2] = {0};
- Spectrogram spec = {data, data, 2};
-
- for(int i=0; i<MAX_SPECTROGRAMS; ++i) {
- int id = synth_register_spectrogram(&spec);
- assert(id >= 0);
- }
- // Next one should fail
+ synth_init();
+ float data[DCT_SIZE * 2] = {0};
+ Spectrogram spec = {data, data, 2};
+
+ for (int i = 0; i < MAX_SPECTROGRAMS; ++i) {
int id = synth_register_spectrogram(&spec);
- assert(id == -1);
+ assert(id >= 0);
+ }
+ // Next one should fail
+ int id = synth_register_spectrogram(&spec);
+ assert(id == -1);
}
void test_peak() {
- // Already called render in test_render.
- // Just call the getter.
- float peak = synth_get_output_peak();
- assert(peak >= 0.0f);
+ // Already called render in test_render.
+ // Just call the getter.
+ float peak = synth_get_output_peak();
+ assert(peak >= 0.0f);
}
int main() {