summaryrefslogtreecommitdiff
path: root/src/test_demo.cc
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-07 08:22:12 +0100
committerskal <pascal.massimino@gmail.com>2026-02-07 08:22:12 +0100
commit91e3215b09c458c46eba4cccce602d6917e34923 (patch)
tree2194ef37f54c7da633962abf0f32c73c6f1e2092 /src/test_demo.cc
parent036114d12d024273e752ffbb68a95a04ee34d4fa (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.cc216
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;
+}