// This file is part of the 64k demo project. // It tests variable tempo system with music_time scaling. // Verifies 2x speed-up and 2x slow-down reset tricks. #include "audio/audio.h" #include "audio/audio_engine.h" #include "audio/backend/mock_audio_backend.h" #include "audio/tracker.h" #include #include #include #if !defined(STRIP_ALL) // Helper: Setup audio engine for testing static void setup_audio_test(MockAudioBackend& backend, AudioEngine& engine) { audio_set_backend(&backend); engine.init(); engine.load_music_data(&g_tracker_score, g_tracker_samples, g_tracker_sample_assets, g_tracker_samples_count); } // Helper: Simulate tempo advancement with fixed steps static void simulate_tempo(AudioEngine& engine, float& music_time, float duration, float tempo_scale, float dt = 0.1f) { const int steps = (int)(duration / dt); for (int i = 0; i < steps; ++i) { music_time += dt * tempo_scale; engine.update(music_time, dt * tempo_scale); } } // Helper: Simulate tempo with variable scaling function static void simulate_tempo_fn(AudioEngine& engine, float& music_time, float& physical_time, float duration, float dt, float (*tempo_fn)(float)) { const int steps = (int)(duration / dt); for (int i = 0; i < steps; ++i) { physical_time += dt; const float tempo_scale = tempo_fn(physical_time); music_time += dt * tempo_scale; engine.update(music_time, dt * tempo_scale); } } void test_basic_tempo_scaling() { printf("Test: Basic tempo scaling (1.0x, 2.0x, 0.5x)...\n"); MockAudioBackend backend; AudioEngine engine; setup_audio_test(backend, engine); // Test 1: Normal tempo (1.0x) { backend.clear_events(); float music_time = 0.0f; simulate_tempo(engine, music_time, 1.0f, 1.0f); printf(" 1.0x tempo: music_time = %.3f (expected ~1.0)\n", music_time); assert(std::abs(music_time - 1.0f) < 0.01f); } // Test 2: Fast tempo (2.0x) { backend.clear_events(); engine.reset(); float music_time = 0.0f; simulate_tempo(engine, music_time, 1.0f, 2.0f); printf(" 2.0x tempo: music_time = %.3f (expected ~2.0)\n", music_time); assert(std::abs(music_time - 2.0f) < 0.01f); } // Test 3: Slow tempo (0.5x) { backend.clear_events(); engine.reset(); float music_time = 0.0f; simulate_tempo(engine, music_time, 1.0f, 0.5f); printf(" 0.5x tempo: music_time = %.3f (expected ~0.5)\n", music_time); assert(std::abs(music_time - 0.5f) < 0.01f); } engine.shutdown(); printf(" ✓ Basic tempo scaling works correctly\n"); } void test_2x_speedup_reset_trick() { printf("Test: 2x SPEED-UP reset trick...\n"); MockAudioBackend backend; AudioEngine engine; setup_audio_test(backend, engine); float music_time = 0.0f; float physical_time = 0.0f; const float dt = 0.1f; // Phase 1: Accelerate from 1.0x to 2.0x over 5 seconds printf(" Phase 1: Accelerating 1.0x → 2.0x\n"); auto accel_fn = [](float t) { return fminf(1.0f + (t / 5.0f), 2.0f); }; simulate_tempo_fn(engine, music_time, physical_time, 5.0f, dt, accel_fn); const float tempo_scale = accel_fn(physical_time); printf(" After 5s physical: tempo=%.2fx, music_time=%.3f\n", tempo_scale, music_time); assert(tempo_scale >= 1.99f); // Phase 2: RESET - back to 1.0x tempo printf(" Phase 2: RESET to 1.0x tempo\n"); const float music_time_before_reset = music_time; simulate_tempo(engine, music_time, 2.0f, 1.0f, dt); printf(" After reset + 2s: tempo=1.0x, music_time=%.3f\n", music_time); const float music_time_delta = music_time - music_time_before_reset; printf(" Music time delta: %.3f (expected ~2.0)\n", music_time_delta); assert(std::abs(music_time_delta - 2.0f) < 0.1f); engine.shutdown(); printf(" ✓ 2x speed-up reset trick verified\n"); } void test_2x_slowdown_reset_trick() { printf("Test: 2x SLOW-DOWN reset trick...\n"); MockAudioBackend backend; AudioEngine engine; setup_audio_test(backend, engine); float music_time = 0.0f; float physical_time = 0.0f; const float dt = 0.1f; // Phase 1: Decelerate from 1.0x to 0.5x over 5 seconds printf(" Phase 1: Decelerating 1.0x → 0.5x\n"); auto decel_fn = [](float t) { return fmaxf(1.0f - (t / 10.0f), 0.5f); }; simulate_tempo_fn(engine, music_time, physical_time, 5.0f, dt, decel_fn); const float tempo_scale = decel_fn(physical_time); printf(" After 5s physical: tempo=%.2fx, music_time=%.3f\n", tempo_scale, music_time); assert(tempo_scale <= 0.51f); // Phase 2: RESET - back to 1.0x tempo printf(" Phase 2: RESET to 1.0x tempo\n"); const float music_time_before_reset = music_time; simulate_tempo(engine, music_time, 2.0f, 1.0f, dt); printf(" After reset + 2s: tempo=1.0x, music_time=%.3f\n", music_time); const float music_time_delta = music_time - music_time_before_reset; printf(" Music time delta: %.3f (expected ~2.0)\n", music_time_delta); assert(std::abs(music_time_delta - 2.0f) < 0.1f); engine.shutdown(); printf(" ✓ 2x slow-down reset trick verified\n"); } void test_pattern_density_swap() { printf("Test: Pattern density swap at reset points...\n"); MockAudioBackend backend; AudioEngine engine; setup_audio_test(backend, engine); float music_time = 0.0f; // Phase 1: Sparse pattern at normal tempo printf(" Phase 1: Sparse pattern, normal tempo\n"); simulate_tempo(engine, music_time, 3.0f, 1.0f); const size_t sparse_events = backend.get_events().size(); printf(" Events during sparse phase: %zu\n", sparse_events); // Phase 2: Accelerate to 2.0x printf(" Phase 2: Accelerating to 2.0x\n"); simulate_tempo(engine, music_time, 2.0f, 2.0f); const size_t events_at_2x = backend.get_events().size() - sparse_events; printf(" Additional events during 2.0x: %zu\n", events_at_2x); // Phase 3: Reset to 1.0x printf(" Phase 3: Reset to 1.0x (simulating denser pattern)\n"); const size_t events_before_reset_phase = backend.get_events().size(); simulate_tempo(engine, music_time, 2.0f, 1.0f); const size_t events_after_reset = backend.get_events().size(); printf(" Events during reset phase: %zu\n", events_after_reset - events_before_reset_phase); assert(backend.get_events().size() > 0); engine.shutdown(); printf(" ✓ Pattern density swap points verified\n"); } void test_continuous_acceleration() { printf("Test: Continuous acceleration from 0.5x to 2.0x...\n"); MockAudioBackend backend; AudioEngine engine; setup_audio_test(backend, engine); float music_time = 0.0f; float physical_time = 0.0f; const float dt = 0.05f; const float min_tempo = 0.5f; const float max_tempo = 2.0f; printf(" Accelerating 0.5x → 2.0x over 10 seconds\n"); auto accel_fn = [min_tempo, max_tempo](float t) { const float progress = t / 10.0f; return fmaxf( min_tempo, fminf(max_tempo, min_tempo + progress * (max_tempo - min_tempo))); }; const int steps = (int)(10.0f / dt); for (int i = 0; i < steps; ++i) { physical_time += dt; const float tempo_scale = accel_fn(physical_time); music_time += dt * tempo_scale; engine.update(music_time, dt * tempo_scale); if (i % 50 == 0) { printf(" t=%.1fs: tempo=%.2fx, music_time=%.3f\n", physical_time, tempo_scale, music_time); } } const float final_tempo = accel_fn(physical_time); printf(" Final: tempo=%.2fx, music_time=%.3f\n", final_tempo, music_time); assert(final_tempo >= 1.99f); // Verify music_time (integral: 0.5*10 + 1.5*10²/(2*10) = 12.5) const float expected_music_time = 12.5f; printf(" Expected music_time: %.3f, actual: %.3f\n", expected_music_time, music_time); assert(std::abs(music_time - expected_music_time) < 0.5f); engine.shutdown(); printf(" ✓ Continuous acceleration verified\n"); } void test_oscillating_tempo() { printf("Test: Oscillating tempo (sine wave)...\n"); MockAudioBackend backend; AudioEngine engine; setup_audio_test(backend, engine); float music_time = 0.0f; float physical_time = 0.0f; const float dt = 0.05f; printf(" Oscillating tempo: 0.8x ↔ 1.2x\n"); auto oscil_fn = [](float t) { return 1.0f + 0.2f * sinf(t * 2.0f); }; const int steps = 100; for (int i = 0; i < steps; ++i) { physical_time += dt; const float tempo_scale = oscil_fn(physical_time); music_time += dt * tempo_scale; engine.update(music_time, dt * tempo_scale); if (i % 25 == 0) { printf(" t=%.2fs: tempo=%.3fx, music_time=%.3f\n", physical_time, tempo_scale, music_time); } } printf(" Final: physical_time=%.2fs, music_time=%.3f (expected ~%.2f)\n", physical_time, music_time, physical_time); assert(std::abs(music_time - physical_time) < 0.5f); engine.shutdown(); printf(" ✓ Oscillating tempo verified\n"); } #endif /* !defined(STRIP_ALL) */ int main() { #if !defined(STRIP_ALL) printf("Running Variable Tempo tests...\n\n"); test_basic_tempo_scaling(); test_2x_speedup_reset_trick(); test_2x_slowdown_reset_trick(); test_pattern_density_swap(); test_continuous_acceleration(); test_oscillating_tempo(); printf("\n✅ All Variable Tempo tests PASSED\n"); return 0; #else printf("Variable Tempo tests skipped (STRIP_ALL enabled)\n"); return 0; #endif /* !defined(STRIP_ALL) */ }