summaryrefslogtreecommitdiff
path: root/src/audio/wav_dump_backend.cc
blob: 82803a22324a51792e9b8b3e4aca9c436a418a91 (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
// 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 <assert.h>
#include <stdio.h>
#include <string.h>

WavDumpBackend::WavDumpBackend()
    : wav_file_(nullptr), samples_written_(0), clipped_samples_(0),
      output_filename_("audio_dump.wav"), is_active_(false),
      duration_sec_(0.0f) {
  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;
  clipped_samples_ = 0;
  printf("WAV dump backend initialized: %s\n", output_filename_);
}

void WavDumpBackend::start() {
  is_active_ = true;
  printf("WAV dump started (passive mode - frontend drives rendering)\n");
}

void WavDumpBackend::write_audio(const float* samples, int num_samples) {
  if (!is_active_ || wav_file_ == nullptr) {
    return;
  }

  // Convert float samples to int16 and write to WAV
  // Note: We do NOT clamp samples (matches MiniaudioBackend behavior)
  // Instead, we count samples that would clip during int16 conversion
  for (int i = 0; i < num_samples; ++i) {
    float sample = samples[i];

    // Track clipping for diagnostics (but don't prevent it)
    if (sample > 1.0f || sample < -1.0f) {
      clipped_samples_++;
    }

    // Convert to int16 (allows overflow for diagnostic purposes)
    // This matches hardware behavior where clipping occurs in the DAC
    const int16_t sample_i16 = (int16_t)(sample * 32767.0f);
    fwrite(&sample_i16, sizeof(int16_t), 1, wav_file_);
  }

  samples_written_ += num_samples;
}

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

    const float duration_sec = (float)samples_written_ / (kSampleRate * 2);
    printf("WAV file written: %s (%.2f seconds, %zu samples)\n",
           output_filename_, duration_sec, samples_written_);

    // Report clipping diagnostics
    if (clipped_samples_ > 0) {
      const float clip_percent =
          (float)clipped_samples_ / (float)samples_written_ * 100.0f;
      printf("  WARNING: %zu samples clipped (%.2f%% of total)\n",
             clipped_samples_, clip_percent);
      printf("  This indicates audio distortion - consider reducing volume\n");
    } else {
      printf("  ✓ No clipping detected\n");
    }
  }

  is_active_ = false;
}

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