diff options
Diffstat (limited to 'src/tests')
| -rw-r--r-- | src/tests/test_tracker_timing.cc | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/src/tests/test_tracker_timing.cc b/src/tests/test_tracker_timing.cc new file mode 100644 index 0000000..20269a8 --- /dev/null +++ b/src/tests/test_tracker_timing.cc @@ -0,0 +1,312 @@ +// 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 <assert.h> +#include <stdio.h> +#include <cmath> + +#if !defined(STRIP_ALL) + +// Helper: Check if a timestamp exists in events within tolerance +static bool has_event_at_time(const std::vector<VoiceTriggerEvent>& 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<VoiceTriggerEvent>& 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<float> get_unique_timestamps( + const std::vector<VoiceTriggerEvent>& events, float tolerance = 0.001f) { + std::vector<float> 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) */ +} |
