-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProgram.cs
More file actions
257 lines (218 loc) · 9.79 KB
/
Program.cs
File metadata and controls
257 lines (218 loc) · 9.79 KB
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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
using System;
using System.IO;
using System.Linq;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
namespace WAVPlayer
{
// WAV File Format Constants
public static class WAVConstants
{
// RIFF Header
public const string RIFF_ID = "RIFF";
public const string WAVE_ID = "WAVE";
// Common Chunk IDs
public const string FORMAT_CHUNK_ID = "fmt "; // Note the space!
public const string DATA_CHUNK_ID = "data";
public const string LIST_CHUNK_ID = "LIST";
public const string FACT_CHUNK_ID = "fact";
public const string JUNK_CHUNK_ID = "JUNK";
// Format Types
public const ushort AUDIO_FORMAT_PCM = 1;
public const ushort AUDIO_FORMAT_IEEE_FLOAT = 3;
// Standard Sizes
public const uint STANDARD_FORMAT_CHUNK_SIZE = 16;
public const int CHUNK_HEADER_SIZE = 8; // 4 bytes ID + 4 bytes size
}
public class WAVFormat
{
// file header info?
public string FileType { get; set; } = ""; // RIFF
public string AudioType { get; set; } = ""; // WAVE
public int FileSize { get; set; }
// essential audio info
public int Channels { get; set; } // 1=mono,2=stereo
public int SampleRate { get; set; }
public int BitsPerSample { get; set; }
public int BitRate { get; set; }
public double DurationSeconds { get; set; }
public long AudioDataOffset { get; set; } // Where audio data starts in file
public int AudioDataSize { get; set; }
}
class Program
{
static void Main(string[] args)
{
U.Log("WAV Player Starting...");
string filename = "./audio_files/90s-Office-Phone.wav";
// string filename = "sample-3s.wav";
// string filename = "sample-3s.mp3";
if (File.Exists(filename))
{
ReadWAVFile(filename);
// Read all audio data
}
else
{
U.Log($"File not found: {filename}", true);
U.Log(
"Put a WAV file named 'test.wav' in your project folder, or change the filename above."
);
}
}
static void ReadWAVFile(string filename)
{
U.Log($"Reading: {filename}", true);
try
{
WAVFormat wav = new WAVFormat();
using (FileStream file = new FileStream(filename, FileMode.Open, FileAccess.Read))
using (BinaryReader reader = new BinaryReader(file))
{
// Read and store the RIFF header
wav.FileType = ReadString(reader, 4); // "RIFF"
wav.FileSize = (int)reader.ReadUInt32(); // File size
wav.AudioType = ReadString(reader, 4); // "WAVE"
// Validate it's a WAV file
if (!IsValidWAV(wav))
{
U.Log("Not a valid WAV file!");
return;
}
U.Log("✓ Valid RIFF/WAVE file detected!");
bool foundFormat = false;
bool foundData = false;
while (
reader.BaseStream.Position < reader.BaseStream.Length
&& (!foundFormat || !foundData)
)
{
// FIRST: Check if we have enough bytes left
if (
reader.BaseStream.Position + WAVConstants.CHUNK_HEADER_SIZE
> reader.BaseStream.Length
)
break; // Not enough bytes for a chunk header - stop safely
// SECOND: If we get here, it's safe to read the chunk header
string chunkID = ReadString(reader, 4);
uint chunkSize = reader.ReadUInt32();
U.Log($"Found chunk: '{chunkID}' (size: {chunkSize:N0} bytes)", true);
if (chunkID == WAVConstants.FORMAT_CHUNK_ID)
{
ReadFormatChunk(reader, wav, chunkSize);
foundFormat = true;
}
else if (chunkID == WAVConstants.DATA_CHUNK_ID)
{
wav.AudioDataOffset = reader.BaseStream.Position; // WHERE it starts
wav.AudioDataSize = (int)chunkSize; // HOW BIG it is
U.Log($"Found audio data: {chunkSize:N0} bytes");
foundData = true;
// Skip the data for now
reader.BaseStream.Seek(chunkSize, SeekOrigin.Current);
}
else
{
// Unknown chunk - skip it
U.Log($"Skipping unknown chunk: '{chunkID}'");
reader.BaseStream.Seek(chunkSize, SeekOrigin.Current);
}
}
if (foundFormat && foundData)
{
U.Log("\n=== WAV Analysis Complete ===");
// Calculate duration
double durationSeconds = (double)wav.AudioDataSize / wav.BitRate;
wav.DurationSeconds = durationSeconds; // Add this line!
U.Log($"Duration: {durationSeconds:F2} seconds");
// Calculate total samples
int samplesPerChannel =
wav.AudioDataSize / (wav.Channels * (wav.BitsPerSample / 8));
U.Log($"Total samples per channel: {samplesPerChannel:N0}");
// Read some actual audio data
// ReadAudioSamples(reader, wav);
reader.BaseStream.Seek(wav.AudioDataOffset, SeekOrigin.Begin);
byte[] audioData = reader.ReadBytes(wav.AudioDataSize);
// TEST: Let's see what our windowing extracts
U.Log("=== Testing Frequency Analyzer ===", true);
FrequencyAnalyzer.AnalyzeTimeSlices(audioData, wav);
// Play it!
// SimpleAudioPlayer.PlayAudio(audioData, wav);
}
else
{
U.Log("ERROR: Missing required chunks!");
if (!foundFormat)
U.Log(" - Missing format chunk");
if (!foundData)
U.Log(" - Missing data chunk");
}
}
}
catch (Exception ex)
{
U.Log($"Error reading file: {ex.Message}");
throw;
}
}
static void ReadAudioSamples(BinaryReader reader, WAVFormat wav)
{
U.Log("\n=== Audio Sample Preview ===");
// Jump to where the audio data starts
reader.BaseStream.Seek(wav.AudioDataOffset, SeekOrigin.Begin);
U.Log("First 10 stereo frames:");
for (int frame = 0; frame < 10; frame++)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
break;
if (wav.BitsPerSample == 16 && wav.Channels == 2)
{
short left = reader.ReadInt16();
short right = reader.ReadInt16();
U.Log(
$"Frame {frame}: L={left, 6} R={right, 6} (diff: {Math.Abs(left - right)})"
);
}
else if (wav.BitsPerSample == 16 && wav.Channels == 1)
{
short mono = reader.ReadInt16();
U.Log($"Frame {frame}: {mono}");
}
}
}
static void ReadFormatChunk(BinaryReader reader, WAVFormat wav, uint chunkSize)
{
U.Log($"Reading format chunk (size: {chunkSize})");
// Your existing format reading code goes here:
ushort audioFormat = reader.ReadUInt16(); // 1 = PCM
wav.Channels = reader.ReadUInt16(); // mono/stereo
wav.SampleRate = (int)reader.ReadUInt32(); // eg 44100
wav.BitRate = (int)reader.ReadUInt32(); // b per sec
ushort blockAlign = reader.ReadUInt16(); // b per sample frame
wav.BitsPerSample = reader.ReadUInt16(); // eg 16
// Log what we found
U.Log($" Audio Format: {audioFormat} ({(audioFormat == 1 ? "PCM" : "Other")})");
U.Log($" Channels: {wav.Channels}");
U.Log($" Sample Rate: {wav.SampleRate:N0} Hz");
U.Log($" Bit Rate: {wav.BitRate:N0} bytes/sec");
U.Log($" Bits Per Sample: {wav.BitsPerSample}");
// Skip any extra bytes in the format chunk
if (chunkSize > WAVConstants.STANDARD_FORMAT_CHUNK_SIZE)
{
int extraBytes = (int)(chunkSize - WAVConstants.STANDARD_FORMAT_CHUNK_SIZE);
reader.ReadBytes(extraBytes);
U.Log($" Skipped {extraBytes} extra format bytes");
}
}
// Helper methods to make code cleaner
static string ReadString(BinaryReader reader, int length)
{
byte[] bytes = reader.ReadBytes(length);
return System.Text.Encoding.ASCII.GetString(bytes);
}
static bool IsValidWAV(WAVFormat wav)
{
return wav.FileType == WAVConstants.RIFF_ID && wav.AudioType == WAVConstants.WAVE_ID;
}
}
}