diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-07 08:22:12 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-07 08:22:12 +0100 |
| commit | 91e3215b09c458c46eba4cccce602d6917e34923 (patch) | |
| tree | 2194ef37f54c7da633962abf0f32c73c6f1e2092 /src/test_demo.cc | |
| parent | 036114d12d024273e752ffbb68a95a04ee34d4fa (diff) | |
feat(test_demo): Add audio/visual sync debug tool with tempo testing
Implements minimal standalone executable for debugging audio/visual
synchronization and variable tempo system without full demo complexity.
Key Features:
- Simple drum beat (kick-snare) with crash landmarks at bars 3 and 7
- NOTE_A4 (440 Hz) reference tone at start of each bar for testing
- Screen flash effect synchronized to audio peaks
- 16 second duration (8 bars at 120 BPM)
- Variable tempo mode (--tempo) alternating acceleration/deceleration
- Peak logging (--log-peaks) for gnuplot visualization
Command-line options:
- --help: Show usage information
- --fullscreen: Run in fullscreen mode
- --resolution WxH: Set window resolution
- --tempo: Enable tempo variation test (1.0x ↔ 1.5x and 1.0x ↔ 0.66x)
- --log-peaks FILE: Export audio peaks with beat timing for analysis
Files:
- src/test_demo.cc: Main executable (~220 lines)
- assets/test_demo.track: Drum pattern with NOTE_A4
- assets/test_demo.seq: Visual timeline (FlashEffect)
- test_demo_README.md: Comprehensive documentation
Build: cmake --build build --target test_demo
Usage: build/test_demo [--help] [--tempo] [--log-peaks peaks.txt]
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'src/test_demo.cc')
| -rw-r--r-- | src/test_demo.cc | 216 |
1 files changed, 216 insertions, 0 deletions
diff --git a/src/test_demo.cc b/src/test_demo.cc new file mode 100644 index 0000000..1664f7c --- /dev/null +++ b/src/test_demo.cc @@ -0,0 +1,216 @@ +// Minimal audio/visual synchronization test tool +// Plays simple drum beat with synchronized screen flashes + +#include "audio/audio.h" +#include "audio/audio_engine.h" +#include "audio/synth.h" +#include "generated/assets.h" // Note: uses main demo asset bundle +#include "gpu/demo_effects.h" +#include "gpu/gpu.h" +#include "platform.h" +#include <cmath> +#include <cstdio> +#include <cstdlib> +#include <cstring> + +// External declarations from generated files +extern float GetDemoDuration(); +extern void LoadTimeline(MainSequence& main_seq, WGPUDevice device, WGPUQueue queue, WGPUTextureFormat format); + +#if !defined(STRIP_ALL) +static void print_usage(const char* prog_name) { + printf("Usage: %s [OPTIONS]\n", prog_name); + printf("\nMinimal audio/visual synchronization test tool.\n"); + printf("Plays a simple drum beat with synchronized screen flashes.\n"); + printf("\nOptions:\n"); + printf(" --help Show this help message and exit\n"); + printf(" --fullscreen Run in fullscreen mode\n"); + printf(" --resolution WxH Set window resolution (e.g., 1024x768)\n"); + printf(" --tempo Enable tempo variation test mode\n"); + printf(" (alternates between acceleration and deceleration)\n"); + printf(" --log-peaks FILE Log audio peaks to FILE for gnuplot visualization\n"); + printf("\nExamples:\n"); + printf(" %s --fullscreen\n", prog_name); + printf(" %s --resolution 1024x768 --tempo\n", prog_name); + printf(" %s --log-peaks peaks.txt\n", prog_name); + printf("\nControls:\n"); + printf(" ESC Exit the demo\n"); + printf(" F Toggle fullscreen\n"); +} +#endif + +int main(int argc, char** argv) { + // Parse command-line + PlatformState platform_state; + bool fullscreen_enabled = false; + bool tempo_test_enabled = false; + int width = 1280; + int height = 720; + const char* log_peaks_file = nullptr; + +#if !defined(STRIP_ALL) + for (int i = 1; i < argc; ++i) { + if (strcmp(argv[i], "--help") == 0) { + print_usage(argv[0]); + return 0; + } else if (strcmp(argv[i], "--fullscreen") == 0) { + fullscreen_enabled = true; + } else if (strcmp(argv[i], "--tempo") == 0) { + tempo_test_enabled = true; + } else if (strcmp(argv[i], "--resolution") == 0 && i + 1 < argc) { + const char* res_str = argv[++i]; + int w, h; + if (sscanf(res_str, "%dx%d", &w, &h) == 2) { + width = w; + height = h; + } + } else if (strcmp(argv[i], "--log-peaks") == 0 && i + 1 < argc) { + log_peaks_file = argv[++i]; + } + } +#else + (void)argc; + (void)argv; + fullscreen_enabled = true; +#endif + + // Initialize platform, GPU, audio + platform_state = platform_init(fullscreen_enabled, width, height); + gpu_init(&platform_state); + audio_init(); + + static AudioEngine g_audio_engine; + g_audio_engine.init(); + + // Music time tracking with optional tempo variation + static float g_music_time = 0.0f; + static double g_last_physical_time = 0.0; + static float g_tempo_scale = 1.0f; + + auto fill_audio_buffer = [&](double t) { + const float dt = (float)(t - g_last_physical_time); + g_last_physical_time = t; + + // Calculate tempo scale if --tempo flag enabled + if (tempo_test_enabled) { + // Each bar = 2 seconds at 120 BPM (4 beats) + const float bar_duration = 2.0f; + const int bar_number = (int)(t / bar_duration); + const float bar_progress = fmodf((float)t, bar_duration) / bar_duration; // 0.0-1.0 within bar + + if (bar_number % 2 == 0) { + // Even bars: Ramp from 1.0x → 1.5x + g_tempo_scale = 1.0f + (0.5f * bar_progress); + } else { + // Odd bars: Ramp from 1.0x → 0.66x + g_tempo_scale = 1.0f - (0.34f * bar_progress); + } + } else { + g_tempo_scale = 1.0f; // No tempo variation + } + + g_music_time += dt * g_tempo_scale; + + g_audio_engine.update(g_music_time); + audio_render_ahead(g_music_time, dt * g_tempo_scale); + }; + + // Pre-fill audio buffer + g_audio_engine.update(g_music_time); + audio_render_ahead(g_music_time, 1.0f / 60.0f); + audio_start(); + + int last_width = platform_state.width; + int last_height = platform_state.height; + const float demo_duration = GetDemoDuration(); + +#if !defined(STRIP_ALL) + // Open peak log file if requested + FILE* peak_log = nullptr; + if (log_peaks_file) { + peak_log = fopen(log_peaks_file, "w"); + if (peak_log) { + fprintf(peak_log, "# Audio peak log from test_demo\n"); + fprintf(peak_log, "# To plot with gnuplot:\n"); + fprintf(peak_log, "# gnuplot -p -e \"set xlabel 'Time (s)'; set ylabel 'Peak'; plot '%s' using 1:3 with lines title 'Raw Peak'\"\n", log_peaks_file); + fprintf(peak_log, "# Columns: beat_number clock_time raw_peak\n"); + fprintf(peak_log, "#\n"); + } else { + fprintf(stderr, "Warning: Could not open log file '%s'\n", log_peaks_file); + } + } + int last_beat_logged = -1; +#endif + + // Main loop + while (!platform_should_close(&platform_state)) { + platform_poll(&platform_state); + + // Handle resize + if (platform_state.width != last_width || + platform_state.height != last_height) { + last_width = platform_state.width; + last_height = platform_state.height; + gpu_resize(last_width, last_height); + } + + const double current_time = platform_state.time; + + // Auto-exit at end + if (demo_duration > 0.0f && current_time >= demo_duration) { +#if !defined(STRIP_ALL) + printf("test_demo finished at %.2f seconds.\n", current_time); +#endif + break; + } + + fill_audio_buffer(current_time); + + // Audio/visual sync parameters + const float aspect_ratio = platform_state.aspect_ratio; + const float raw_peak = synth_get_output_peak(); + const float visual_peak = fminf(raw_peak * 8.0f, 1.0f); + + // Beat calculation (hardcoded BPM=120) + const float beat_time = (float)current_time * 120.0f / 60.0f; + const int beat_number = (int)beat_time; + const float beat = fmodf(beat_time, 1.0f); + +#if !defined(STRIP_ALL) + // Log peak at each beat boundary + if (peak_log && beat_number != last_beat_logged) { + fprintf(peak_log, "%d %.6f %.6f\n", beat_number, current_time, raw_peak); + last_beat_logged = beat_number; + } + + // Debug output every 0.5 seconds + static float last_print_time = -1.0f; + if (current_time - last_print_time >= 0.5f) { + if (tempo_test_enabled) { + printf("[T=%.2f, MusicT=%.2f, Beat=%d, Frac=%.2f, Peak=%.2f, Tempo=%.2fx]\n", + (float)current_time, g_music_time, beat_number, beat, visual_peak, g_tempo_scale); + } else { + printf("[T=%.2f, Beat=%d, Frac=%.2f, Peak=%.2f]\n", + (float)current_time, beat_number, beat, visual_peak); + } + last_print_time = (float)current_time; + } +#endif + + gpu_draw(visual_peak, aspect_ratio, (float)current_time, beat); + audio_update(); + } + + // Shutdown +#if !defined(STRIP_ALL) + if (peak_log) { + fclose(peak_log); + printf("Peak log written to '%s'\n", log_peaks_file); + } +#endif + + audio_shutdown(); + gpu_shutdown(); + platform_shutdown(&platform_state); + return 0; +} |
