summaryrefslogtreecommitdiff
path: root/src/main.cc
blob: 978a34e99ee9722e133b5695cbb33954b85ccfa5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
// 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/audio_engine.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/demo_effects.h" // For GetDemoDuration()
#include "gpu/gpu.h"
#include "platform.h"
#include "util/math.h"
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>

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

  // Initialize audio backend (miniaudio device)
  audio_init();

  // Initialize audio engine (synth + tracker unified management)
  static AudioEngine g_audio_engine;
  g_audio_engine.init();

  // 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;

  auto fill_audio_buffer = [&](double t) {
    // Variable tempo system - acceleration phases for demo effect
    // Phase 1 (0-5s): Steady 1.0x
    // Phase 2 (5-10s): Steady 1.0x
    // Phase 3 (10-15s): Accelerate from 1.0x to 2.0x
    // Phase 4 (15-20s): Steady 1.0x (reset after acceleration)
    // Phase 5 (20-25s): Decelerate from 1.0x to 0.5x
    // Phase 6 (25s+): Steady 1.0x (reset after deceleration)
    const float prev_tempo = g_tempo_scale;
    if (t < 10.0) {
      g_tempo_scale = 1.0f; // Steady at start
    } else if (t < 15.0) {
      // Phase 3: 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; // Reset to normal
    } else if (t < 25.0) {
      // Phase 5: 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; // 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;

    // Pass music_time (not physical time) to tracker
    g_audio_engine.update(g_music_time);

    // Fill ring buffer with upcoming audio (look-ahead rendering)
    // CRITICAL: Scale dt by tempo to render enough audio during
    // acceleration/deceleration At 2.0x tempo, we consume 2x audio per physical
    // second, so we must render 2x per frame
    audio_render_ahead(g_music_time, dt * g_tempo_scale);
  };

#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) {
      fill_audio_buffer(t);
      audio_render_silent((float)step);
    }

    // Simulate Visuals
    gpu_simulate_until((float)seek_time);
  }
#endif /* !defined(STRIP_ALL) */

  // PRE-FILL: Fill ring buffer with initial 200ms before starting audio device
  // This prevents underrun on first callback
  g_audio_engine.update(g_music_time);
  audio_render_ahead(g_music_time, 1.0f / 60.0f); // Fill buffer with lookahead

  // 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;

  // Get demo duration from sequence file (or -1 if not specified)
  const float demo_duration = GetDemoDuration();

  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);
    }

    const double current_time = platform_state.time + seek_time; // Offset logic time

    // Auto-exit when demo finishes (if duration is specified)
    if (demo_duration > 0.0f && current_time >= demo_duration) {
#if !defined(STRIP_ALL)
      printf("Demo finished at %.2f seconds. Exiting...\n", current_time);
#endif
      break;
    }

    fill_audio_buffer(current_time);

    const float aspect_ratio = platform_state.aspect_ratio;

    // Adjusted multiplier for visuals (preventing constant 1.0 saturation)
    const float raw_peak = synth_get_output_peak();
    const float visual_peak = fminf(raw_peak * 8.0f, 1.0f);

    // Calculate beat information for synchronization
    const float beat_time = (float)current_time * g_tracker_score.bpm / 60.0f;
    const int beat_number = (int)beat_time;
    const float beat = fmodf(beat_time, 1.0f); // Fractional part (0.0 to 1.0)

#if !defined(STRIP_ALL)
    // Print beat/time info periodically for identifying sync points
    static float last_print_time = -1.0f;
    if (current_time - last_print_time >= 0.5f) { // Print every 0.5 seconds
      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 /* !defined(STRIP_ALL) */

    gpu_draw(visual_peak, aspect_ratio, (float)current_time, beat);
    audio_update();
  }

  audio_shutdown();
  gpu_shutdown();
  platform_shutdown(&platform_state);
  return 0;
}