summaryrefslogtreecommitdiff
path: root/src/tests/audio/test_variable_tempo.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/tests/audio/test_variable_tempo.cc')
-rw-r--r--src/tests/audio/test_variable_tempo.cc291
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) */
+}