// 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 #include #include 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 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) */