// This file is part of the 64k demo project. // It tests tracker timing and synchronization using MockAudioBackend. // Verifies pattern triggers occur at correct times with proper BPM scaling. #include "audio/mock_audio_backend.h" #include "audio/audio.h" #include "audio/synth.h" #include "audio/tracker.h" #include #include #include #if !defined(STRIP_ALL) // Helper: Check if a timestamp exists in events within tolerance static bool has_event_at_time(const std::vector& events, float expected_time, float tolerance = 0.001f) { for (const auto& evt : events) { if (std::abs(evt.timestamp_sec - expected_time) < tolerance) { return true; } } return false; } // Helper: Count events at a specific time static int count_events_at_time(const std::vector& events, float expected_time, float tolerance = 0.001f) { int count = 0; for (const auto& evt : events) { if (std::abs(evt.timestamp_sec - expected_time) < tolerance) { count++; } } return count; } // Helper: Get all unique timestamps in events static std::vector get_unique_timestamps( const std::vector& events, float tolerance = 0.001f) { std::vector timestamps; for (const auto& evt : events) { bool found = false; for (float ts : timestamps) { if (std::abs(evt.timestamp_sec - ts) < tolerance) { found = true; break; } } if (!found) { timestamps.push_back(evt.timestamp_sec); } } return timestamps; } void test_basic_event_recording() { printf("Test: Basic event recording with mock backend...\n"); MockAudioBackend backend; audio_set_backend(&backend); tracker_init(); synth_init(); // Trigger at t=0.0 (should trigger initial patterns) tracker_update(0.0f); const auto& events = backend.get_events(); printf(" Events triggered at t=0.0: %zu\n", events.size()); // Verify we got some events assert(events.size() > 0); // All events at t=0 should have timestamp near 0 for (const auto& evt : events) { assert(evt.timestamp_sec < 0.1f); // Within 100ms of start } printf(" ✓ Basic event recording works\n"); } void test_progressive_triggering() { printf("Test: Progressive pattern triggering...\n"); MockAudioBackend backend; audio_set_backend(&backend); tracker_init(); synth_init(); // Update at t=0 tracker_update(0.0f); const size_t events_at_0 = backend.get_events().size(); printf(" Events at t=0.0: %zu\n", events_at_0); // Update at t=1.0 tracker_update(1.0f); const size_t events_at_1 = backend.get_events().size(); printf(" Events at t=1.0: %zu\n", events_at_1); // Update at t=2.0 tracker_update(2.0f); const size_t events_at_2 = backend.get_events().size(); printf(" Events at t=2.0: %zu\n", events_at_2); // Events should accumulate (or at least not decrease) assert(events_at_1 >= events_at_0); assert(events_at_2 >= events_at_1); printf(" ✓ Events accumulate over time\n"); } void test_simultaneous_triggers() { printf("Test: SIMULTANEOUS pattern triggers at same time...\n"); MockAudioBackend backend; audio_set_backend(&backend); tracker_init(); synth_init(); // Clear and update to first trigger point backend.clear_events(); tracker_update(0.0f); const auto& events = backend.get_events(); if (events.size() == 0) { printf(" No events at t=0.0, skipping test\n"); return; } // Check if we have multiple events at t=0 const int simultaneous_count = count_events_at_time(events, 0.0f, 0.001f); printf(" Simultaneous events at t=0.0: %d out of %zu total\n", simultaneous_count, events.size()); if (simultaneous_count > 1) { // Verify all simultaneous events have EXACTLY the same timestamp const float first_timestamp = events[0].timestamp_sec; float max_delta = 0.0f; for (size_t i = 1; i < events.size(); ++i) { const float delta = std::abs(events[i].timestamp_sec - first_timestamp); max_delta = std::fmaxf(max_delta, delta); } printf(" Maximum timestamp delta: %.6f seconds (%.3f ms)\n", max_delta, max_delta * 1000.0f); // Simultaneous events should have sub-millisecond timing assert(max_delta < 0.001f); // Less than 1ms difference printf(" ✓ All simultaneous events within 1ms of each other\n"); } else { printf(" ℹ Only one event at t=0.0, cannot verify simultaneity\n"); } } void test_timing_monotonicity() { printf("Test: Event timestamps are monotonically increasing...\n"); MockAudioBackend backend; audio_set_backend(&backend); tracker_init(); synth_init(); // Update through several time points for (float t = 0.0f; t <= 5.0f; t += 0.5f) { tracker_update(t); } const auto& events = backend.get_events(); printf(" Total events recorded: %zu\n", events.size()); // Verify timestamps are monotonically increasing (non-decreasing) for (size_t i = 1; i < events.size(); ++i) { assert(events[i].timestamp_sec >= events[i - 1].timestamp_sec); } printf(" ✓ All timestamps monotonically increasing\n"); } void test_seek_simulation() { printf("Test: Seek/fast-forward simulation...\n"); MockAudioBackend backend; audio_set_backend(&backend); audio_init(); tracker_init(); synth_init(); // Simulate seeking to t=3.0s by rendering silent audio // This should trigger all patterns in range [0, 3.0] const float seek_target = 3.0f; // Update tracker progressively (simulating real playback) float t = 0.0f; const float step = 0.1f; while (t <= seek_target) { tracker_update(t); // Simulate audio rendering float dummy_buffer[512 * 2]; synth_render(dummy_buffer, 512); t += step; } const auto& events = backend.get_events(); printf(" Events triggered during seek to %.1fs: %zu\n", seek_target, events.size()); // Should have triggered multiple patterns assert(events.size() > 0); // All events should be before seek target time for (const auto& evt : events) { // Events can be slightly after due to synth processing assert(evt.timestamp_sec <= seek_target + 0.5f); } audio_shutdown(); printf(" ✓ Seek simulation works correctly\n"); } void test_timestamp_clustering() { printf("Test: Analyzing timestamp clustering...\n"); MockAudioBackend backend; audio_set_backend(&backend); tracker_init(); synth_init(); // Update through the first 4 seconds for (float t = 0.0f; t <= 4.0f; t += 0.1f) { tracker_update(t); } const auto& events = backend.get_events(); printf(" Total events: %zu\n", events.size()); // Get unique timestamps auto unique_timestamps = get_unique_timestamps(events, 0.001f); printf(" Unique trigger times: %zu\n", unique_timestamps.size()); // For each unique timestamp, count how many events occurred for (float ts : unique_timestamps) { const int count = count_events_at_time(events, ts, 0.001f); if (count > 1) { printf(" %.3fs: %d simultaneous events\n", ts, count); } } printf(" ✓ Timestamp clustering analyzed\n"); } void test_render_integration() { printf("Test: Integration with audio_render_silent...\n"); MockAudioBackend backend; audio_set_backend(&backend); audio_init(); tracker_init(); synth_init(); // Trigger some patterns tracker_update(0.0f); const size_t events_before = backend.get_events().size(); // Render 1 second of silent audio audio_render_silent(1.0f); // Check that backend time advanced const float backend_time = backend.get_current_time(); printf(" Backend time after 1s render: %.3fs\n", backend_time); assert(backend_time >= 0.9f && backend_time <= 1.1f); // Trigger more patterns after time advance tracker_update(1.0f); const size_t events_after = backend.get_events().size(); printf(" Events before: %zu, after: %zu\n", events_before, events_after); assert(events_after >= events_before); audio_shutdown(); printf(" ✓ audio_render_silent integration works\n"); } #endif /* !defined(STRIP_ALL) */ int main() { #if !defined(STRIP_ALL) printf("Running Tracker Timing tests...\n\n"); test_basic_event_recording(); test_progressive_triggering(); test_simultaneous_triggers(); test_timing_monotonicity(); test_seek_simulation(); test_timestamp_clustering(); test_render_integration(); printf("\n✅ All Tracker Timing tests PASSED\n"); return 0; #else printf("Tracker Timing tests skipped (STRIP_ALL enabled)\n"); return 0; #endif /* !defined(STRIP_ALL) */ }