// This file is part of the 64k demo project. // It serves as the application entry point. // Orchestrates platform initialization, main loop, and subsystem coordination. #include "3d/renderer.h" #include "audio/audio.h" #include "audio/gen.h" #include "audio/synth.h" #include "audio/tracker.h" #if !defined(STRIP_ALL) #include "audio/wav_dump_backend.h" #endif #include "generated/assets.h" // Include generated asset header #include "gpu/gpu.h" #include "platform.h" #include "util/math.h" #include #include #include #include #define SPEC_FRAMES 16 static float* g_spec_buffer_a[SPEC_FRAMES * DCT_SIZE] = {0}; static float* g_spec_buffer_b[SPEC_FRAMES * DCT_SIZE] = {0}; // Global storage for the melody to ensure it persists // Global storage for the melody to ensure it persists // std::vector g_melody_data; // Tracker now handles melody generation // int generate_melody() { ... } // Replaced by tracker float* generate_tone(float* buffer, float freq) { if (buffer == nullptr) { buffer = (float*)calloc(SPEC_FRAMES * DCT_SIZE, sizeof(float)); } else { memset(buffer, 0, SPEC_FRAMES * DCT_SIZE * sizeof(float)); } for (int frame = 0; frame < SPEC_FRAMES; ++frame) { float* spec_frame = buffer + frame * DCT_SIZE; float amplitude = 1000. * powf(1.0f - (float)frame / SPEC_FRAMES, 2.0f); int bin = (int)(freq / (32000.0f / 2.0f) * DCT_SIZE); if (bin > 0 && bin < DCT_SIZE) { spec_frame[bin] = amplitude; } } return buffer; } int main(int argc, char** argv) { PlatformState platform_state; bool fullscreen_enabled = false; float seek_time = 0.0f; int width = 1280; int height = 720; bool dump_wav = false; const char* wav_output_file = "audio_dump.wav"; #if !defined(STRIP_ALL) for (int i = 1; i < argc; ++i) { if (strcmp(argv[i], "--fullscreen") == 0) { fullscreen_enabled = true; } else if (strcmp(argv[i], "--seek") == 0 && i + 1 < argc) { seek_time = atof(argv[i + 1]); ++i; } 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], "--debug") == 0) { Renderer3D::SetDebugEnabled(true); } else if (strcmp(argv[i], "--dump_wav") == 0) { dump_wav = true; // Optional: allow specifying output filename if (i + 1 < argc && argv[i + 1][0] != '-') { wav_output_file = argv[++i]; } } } #else (void)argc; (void)argv; fullscreen_enabled = true; #endif /* STRIP_ALL */ platform_state = platform_init(fullscreen_enabled, width, height); gpu_init(&platform_state); #if !defined(STRIP_ALL) // Set WAV dump backend if requested WavDumpBackend wav_backend; if (dump_wav) { wav_backend.set_output_file(wav_output_file); audio_set_backend(&wav_backend); printf("WAV dump mode enabled: %s\n", wav_output_file); } #endif audio_init(); synth_init(); tracker_init(); // Still keep the dynamic tone for bass (can be integrated into tracker too) const float* g_spec_buffer_a = generate_tone(nullptr, 110.0f); // A2 const float* g_spec_buffer_b = generate_tone(nullptr, 110.0f); const Spectrogram bass_spec = {g_spec_buffer_a, g_spec_buffer_b, SPEC_FRAMES}; int bass_id = synth_register_spectrogram(&bass_spec); // Generate and play melody (replaced by tracker) // int melody_id = generate_melody(); // synth_trigger_voice(melody_id, 0.6f, 0.0f); // Music time state for variable tempo static float g_music_time = 0.0f; static float g_tempo_scale = 1.0f; // 1.0 = normal speed static double g_last_physical_time = 0.0; double last_beat_time = 0.0; int beat_count = 0; auto update_game_logic = [&](double t) { // Variable tempo test: Accelerate and decelerate based on physical time // Phase 1 (0-10s): Steady at 1.0x // Phase 2 (10-15s): Accelerate from 1.0x to 2.0x // Phase 3 (15-20s): Reset to 1.0x (with denser patterns in track) // Phase 4 (20-25s): Decelerate from 1.0x to 0.5x // Phase 5 (25s+): Reset to 1.0x (back to normal) const float prev_tempo = g_tempo_scale; if (t < 10.0) { g_tempo_scale = 1.0f; // Phase 1: Steady } else if (t < 15.0) { // Phase 2: Linear acceleration const float progress = (float)(t - 10.0) / 5.0f; g_tempo_scale = 1.0f + progress * 1.0f; // 1.0 → 2.0 } else if (t < 20.0) { g_tempo_scale = 1.0f; // Phase 3: Reset to normal } else if (t < 25.0) { // Phase 4: Linear deceleration const float progress = (float)(t - 20.0) / 5.0f; g_tempo_scale = 1.0f - progress * 0.5f; // 1.0 → 0.5 } else { g_tempo_scale = 1.0f; // Phase 5: Reset to normal } #if !defined(STRIP_ALL) // Debug output when tempo changes significantly if (fabsf(g_tempo_scale - prev_tempo) > 0.05f) { printf("[Tempo] t=%.2fs, tempo=%.3fx, music_time=%.3fs\n", (float)t, g_tempo_scale, g_music_time); } #endif // Calculate delta time and advance music time at scaled rate const float dt = (float)(t - g_last_physical_time); g_last_physical_time = t; g_music_time += dt * g_tempo_scale; if (t - last_beat_time > (60.0f / g_tracker_score.bpm) / 2.0) { // 8th notes last_beat_time = t; // Sync to t const int step = beat_count % 16; /* // Bass pattern if (step % 4 == 0) { float* back_buffer = synth_begin_update(bass_id); if (back_buffer) { float bass_freq = (step < 8) ? 110.0f : 164.82f; // A3 then E3 generate_tone(back_buffer, bass_freq); synth_commit_update(bass_id); } synth_trigger_voice(bass_id, 0.9f, 1.2f); } */ ++beat_count; } // Pass music_time (not physical time) to tracker tracker_update(g_music_time); // Fill ring buffer with upcoming audio (look-ahead rendering) audio_render_ahead(g_music_time, dt); }; #if !defined(STRIP_ALL) if (seek_time > 0.0) { printf("Seeking to %.2f seconds...\n", seek_time); // Simulate audio/game logic // We step at ~60hz const double step = 1.0 / 60.0; for (double t = 0.0; t < seek_time; t += step) { update_game_logic(t); audio_render_silent((float)step); } // Simulate Visuals gpu_simulate_until((float)seek_time); } #endif /* !defined(STRIP_ALL) */ // Start audio (or render to WAV file) audio_start(); #if !defined(STRIP_ALL) // In WAV dump mode, audio_start() renders everything and we can exit if (dump_wav) { audio_shutdown(); gpu_shutdown(); platform_shutdown(&platform_state); return 0; } #endif int last_width = platform_state.width; int last_height = platform_state.height; while (!platform_should_close(&platform_state)) { platform_poll(&platform_state); 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); } double current_time = platform_state.time + seek_time; // Offset logic time update_game_logic(current_time); float aspect_ratio = platform_state.aspect_ratio; // Adjusted multiplier for visuals (preventing constant 1.0 saturation) float raw_peak = synth_get_output_peak(); float visual_peak = fminf(raw_peak * 8.0f, 1.0f); // float beat = fmodf((float)current_time * DEMO_BPM / 60.0f, 1.0f); // Use // tracker BPM float beat = fmodf((float)current_time * g_tracker_score.bpm / 60.0f, 1.0f); gpu_draw(visual_peak, aspect_ratio, (float)current_time, beat); audio_update(); } audio_shutdown(); gpu_shutdown(); platform_shutdown(&platform_state); return 0; }