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
|
// 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 "audio.h"
#include "ring_buffer.h"
#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 small chunks with tracker updates
// This matches the seek logic in main.cc
const int max_duration_sec = 60;
const float update_dt = 1.0f / 60.0f; // 60Hz update rate (matches main loop)
const int frames_per_update = (int)(kSampleRate * update_dt); // ~533 frames
const int samples_per_update = frames_per_update * 2; // Stereo: 2 samples per frame
const int total_updates = (int)(max_duration_sec / update_dt);
// Music time tracking
float music_time = 0.0f;
float tempo_scale = 1.0f;
float physical_time = 0.0f;
// Get ring buffer for reading
AudioRingBuffer* ring_buffer = audio_get_ring_buffer();
// Temporary buffer for each update chunk (stereo)
std::vector<float> chunk_buffer(samples_per_update);
for (int update_count = 0; update_count < total_updates; ++update_count) {
// 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
music_time += update_dt * tempo_scale;
physical_time += update_dt;
// Update tracker (triggers patterns)
tracker_update(music_time);
// Fill ring buffer with upcoming audio
audio_render_ahead(music_time, update_dt);
// Read from ring buffer (same as audio callback would do)
if (ring_buffer != nullptr) {
ring_buffer->read(chunk_buffer.data(), samples_per_update);
}
// Convert float to int16 and write to WAV (stereo interleaved)
for (int i = 0; i < samples_per_update; ++i) {
float sample = chunk_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_ += samples_per_update;
// Progress indicator
if (update_count % 60 == 0) {
printf(" Rendering: %.1fs / %ds (music: %.1fs, tempo: %.2fx)\r",
physical_time, max_duration_sec, music_time, tempo_scale);
fflush(stdout);
}
// Call frame rendering hook (pass frames, not samples)
on_frames_rendered(frames_per_update);
}
printf(
"\nWAV dump complete: %zu samples (%.2f seconds stereo, %.2f music time)\n",
samples_written_, (float)samples_written_ / (kSampleRate * 2), 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 = 2; // Stereo (matches miniaudio config)
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) */
|