diff options
Diffstat (limited to 'src/tests/audio/test_variable_tempo.cc')
| -rw-r--r-- | src/tests/audio/test_variable_tempo.cc | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/src/tests/audio/test_variable_tempo.cc b/src/tests/audio/test_variable_tempo.cc new file mode 100644 index 0000000..bbc9ebf --- /dev/null +++ b/src/tests/audio/test_variable_tempo.cc @@ -0,0 +1,291 @@ +// 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 <assert.h> +#include <cmath> +#include <stdio.h> + +#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) */ +} |
