summaryrefslogtreecommitdiff
path: root/src/audio/wav_dump_backend.cc
blob: f558c39b02b7a709026a2bd8f966810e8bf7a07a (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
// This file is part of the 64k demo project.
// Implementation of WAV dump backend for debugging.

#include "wav_dump_backend.h"

#if !defined(STRIP_ALL)

#include "synth.h"
#include "tracker.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>

WavDumpBackend::WavDumpBackend()
    : wav_file_(nullptr),
      samples_written_(0),
      output_filename_("audio_dump.wav"),
      is_active_(false) {
  sample_buffer_.resize(kBufferSize);
}

WavDumpBackend::~WavDumpBackend() { shutdown(); }

void WavDumpBackend::set_output_file(const char* filename) {
  output_filename_ = filename;
}

void WavDumpBackend::init() {
  // Open WAV file for writing
  wav_file_ = fopen(output_filename_, "wb");
  if (wav_file_ == nullptr) {
    fprintf(stderr, "Error: Failed to open WAV file: %s\n", output_filename_);
    return;
  }

  // Write placeholder header (we'll update it in shutdown())
  write_wav_header(wav_file_, 0);

  samples_written_ = 0;
  printf("WAV dump backend initialized: %s\n", output_filename_);
}

void WavDumpBackend::start() {
  is_active_ = true;
  printf("WAV dump started, rendering audio...\n");

  // Render audio in chunks until we reach desired duration
  // For debugging, render 60 seconds max
  const int max_duration_sec = 60;
  const size_t total_samples = kSampleRate * max_duration_sec;
  const size_t total_frames = total_samples / kBufferSize;

  // Music time tracking (matches main.cc logic)
  float music_time = 0.0f;
  float tempo_scale = 1.0f;
  float physical_time = 0.0f;
  const float buffer_dt = (float)kBufferSize / kSampleRate;  // Time per buffer
  const float update_dt = 1.0f / 60.0f;  // Update rate: 60Hz (matches main loop)

  for (size_t frame = 0; frame < total_frames; ++frame) {
    // Call tracker_update() multiple times per audio buffer
    // This matches the main loop update frequency (~60 Hz)
    const int num_updates = (int)(buffer_dt / update_dt) + 1;
    for (int update = 0; update < num_updates; ++update) {
      // Update tempo scaling (matches main.cc phases)
      if (physical_time < 10.0f) {
        tempo_scale = 1.0f;
      } else if (physical_time < 15.0f) {
        const float progress = (physical_time - 10.0f) / 5.0f;
        tempo_scale = 1.0f + progress * 1.0f;  // 1.0 → 2.0
      } else if (physical_time < 20.0f) {
        tempo_scale = 1.0f;
      } else if (physical_time < 25.0f) {
        const float progress = (physical_time - 20.0f) / 5.0f;
        tempo_scale = 1.0f - progress * 0.5f;  // 1.0 → 0.5
      } else {
        tempo_scale = 1.0f;
      }

      // Advance music time at smaller time steps
      music_time += update_dt * tempo_scale;
      physical_time += update_dt;

      // Update tracker to trigger patterns (high frequency)
      tracker_update(music_time);
    }

    // Render audio from synth (accumulated triggers from updates above)
    synth_render(sample_buffer_.data(), kBufferSize);

    // Convert float to int16 and write to WAV
    for (int i = 0; i < kBufferSize; ++i) {
      // Clamp to [-1, 1] and convert to 16-bit signed
      float sample = sample_buffer_[i];
      if (sample > 1.0f) sample = 1.0f;
      if (sample < -1.0f) sample = -1.0f;

      const int16_t sample_i16 = (int16_t)(sample * 32767.0f);
      fwrite(&sample_i16, sizeof(int16_t), 1, wav_file_);
    }

    samples_written_ += kBufferSize;

    // Progress indicator
    if (frame % 100 == 0) {
      const float progress_sec = (float)samples_written_ / kSampleRate;
      printf("  Rendering: %.1fs / %ds (music: %.1fs, tempo: %.2fx)\r",
             progress_sec, max_duration_sec, music_time, tempo_scale);
      fflush(stdout);
    }

    // Call frame rendering hook
    on_frames_rendered(kBufferSize);
  }

  printf("\nWAV dump complete: %zu samples (%.2f seconds, %.2f music time)\n",
         samples_written_, (float)samples_written_ / kSampleRate, music_time);

  is_active_ = false;
}

void WavDumpBackend::shutdown() {
  if (wav_file_ != nullptr) {
    // Update header with final sample count
    update_wav_header();
    fclose(wav_file_);
    wav_file_ = nullptr;

    printf("WAV file written: %s\n", output_filename_);
  }
}

void WavDumpBackend::write_wav_header(FILE* file, uint32_t num_samples) {
  // WAV file header structure
  // Reference: http://soundfile.sapp.org/doc/WaveFormat/

  const uint32_t num_channels = 1;  // Mono
  const uint32_t sample_rate = kSampleRate;
  const uint32_t bits_per_sample = 16;
  const uint32_t byte_rate = sample_rate * num_channels * bits_per_sample / 8;
  const uint16_t block_align = num_channels * bits_per_sample / 8;
  const uint32_t data_size = num_samples * num_channels * bits_per_sample / 8;

  // RIFF header
  fwrite("RIFF", 1, 4, file);
  const uint32_t chunk_size = 36 + data_size;
  fwrite(&chunk_size, 4, 1, file);
  fwrite("WAVE", 1, 4, file);

  // fmt subchunk
  fwrite("fmt ", 1, 4, file);
  const uint32_t subchunk1_size = 16;
  fwrite(&subchunk1_size, 4, 1, file);
  const uint16_t audio_format = 1;  // PCM
  fwrite(&audio_format, 2, 1, file);
  fwrite(&num_channels, 2, 1, file);
  fwrite(&sample_rate, 4, 1, file);
  fwrite(&byte_rate, 4, 1, file);
  fwrite(&block_align, 2, 1, file);
  fwrite(&bits_per_sample, 2, 1, file);

  // data subchunk header
  fwrite("data", 1, 4, file);
  fwrite(&data_size, 4, 1, file);
}

void WavDumpBackend::update_wav_header() {
  if (wav_file_ == nullptr) return;

  // Seek to beginning and rewrite header with actual sample count
  fseek(wav_file_, 0, SEEK_SET);
  write_wav_header(wav_file_, samples_written_);
}

#endif /* !defined(STRIP_ALL) */