code wiki / (root) / wav_header.nx

wav_header.nx source

↩ module page · 176 lines · 5973 B

1// wav_header.nx -- parse RIFF WAVE (WAV) audio file headers. 2// 3// RIFF container with one "fmt " subchunk (mandatory, describes 4// audio format) and one "data" subchunk (the PCM samples). The 5// canonical 44-byte PCM header is: 6// 7// off size field 8// 0 4 "RIFF" 9// 4 4 chunk size (file size - 8) 10// 8 4 "WAVE" 11// 12 4 "fmt " 12// 16 4 fmt subchunk size (16 for PCM) 13// 20 2 audio format code (1 = PCM, 3 = IEEE float, ...) 14// 22 2 num_channels 15// 24 4 sample_rate 16// 28 4 byte_rate = sample_rate * num_channels * bits/8 17// 32 2 block_align = num_channels * bits/8 18// 34 2 bits_per_sample 19// 36 4 "data" 20// 40 4 data_size (bytes of PCM samples) 21// 44 N PCM samples 22// 23// Non-PCM formats may have a larger "fmt " chunk (extensions 24// following the canonical 16 bytes) and other subchunks ("LIST", 25// "fact", "INFO") interleaved before "data". We walk the chunk 26// stream to find "fmt " and "data" by tag, so extra chunks are 27// tolerated. 28// 29// Invariants: 30// W1 "RIFF" + "WAVE" magic required. 31// W2 Chunk walker returns both "fmt " and "data" offsets or 32// WAV_ERR_FORMAT if either is missing. 33// W3 Multi-byte integers are LITTLE-endian (RIFX variant is 34// big-endian but we don't accept it here). 35 36import "syscalls.nx" 37 38const WAV_ERR_FORMAT: i64 = -1 39const WAV_ERR_SHORT: i64 = -2 40 41const WAV_FMT_PCM: i64 = 1 42const WAV_FMT_IEEE_FLOAT: i64 = 3 43const WAV_FMT_ALAW: i64 = 6 44const WAV_FMT_MULAW: i64 = 7 45const WAV_FMT_EXTENSIBLE: i64 = 65534 46 47struct WavHeader { 48 format_code: i64, // 1 = PCM, 3 = IEEE float, etc. 49 num_channels: i64, // 1 = mono, 2 = stereo 50 sample_rate: i64, // Hz (e.g. 44100) 51 byte_rate: i64, 52 block_align: i64, 53 bits_per_sample: i64, // 8, 16, 24, 32 54 // Where the PCM samples start and how many bytes. 55 data_off: i64, 56 data_len: i64, 57} 58 59func wav_read_u16(buf: *u8, off: i64) -> i64 { 60 return buf[off] | (buf[off + 1] << 8) 61} 62 63func wav_read_u32(buf: *u8, off: i64) -> i64 { 64 let b0: i64 = buf[off] 65 let b1: i64 = buf[off + 1] 66 let b2: i64 = buf[off + 2] 67 let b3: i64 = buf[off + 3] 68 return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24) 69} 70 71// Check a 4-byte tag at offset matches (c0,c1,c2,c3). 72func wav_tag_is(buf: *u8, off: i64, 73 c0: i64, c1: i64, c2: i64, c3: i64) -> i64 { 74 if buf[off] != c0 { return 0 } 75 if buf[off + 1] != c1 { return 0 } 76 if buf[off + 2] != c2 { return 0 } 77 if buf[off + 3] != c3 { return 0 } 78 return 1 79} 80 81// Parse a WAV file. Walks the RIFF chunks looking for "fmt " and 82// "data". Returns 0 on success or a negative WAV_ERR_*. 83func wav_parse(buf: *u8, n: i64, h: *WavHeader) -> i64 { 84 if n < 12 { return WAV_ERR_SHORT } 85 // "RIFF" magic. 86 if wav_tag_is(buf, 0, 0x52, 0x49, 0x46, 0x46) != 1 { return WAV_ERR_FORMAT } 87 // "WAVE" form. 88 if wav_tag_is(buf, 8, 0x57, 0x41, 0x56, 0x45) != 1 { return WAV_ERR_FORMAT } 89 90 h.data_off = 0 91 h.data_len = 0 92 h.format_code = 0 93 94 var off: i64 = 12 95 var have_fmt: i64 = 0 96 var have_data: i64 = 0 97 98 while off + 8 <= n { 99 let size: i64 = wav_read_u32(buf, off + 4) 100 if off + 8 + size > n { return WAV_ERR_SHORT } 101 102 if wav_tag_is(buf, off, 0x66, 0x6D, 0x74, 0x20) == 1 { 103 // "fmt " 104 if size < 16 { return WAV_ERR_FORMAT } 105 h.format_code = wav_read_u16(buf, off + 8) 106 h.num_channels = wav_read_u16(buf, off + 10) 107 h.sample_rate = wav_read_u32(buf, off + 12) 108 h.byte_rate = wav_read_u32(buf, off + 16) 109 h.block_align = wav_read_u16(buf, off + 20) 110 h.bits_per_sample = wav_read_u16(buf, off + 22) 111 have_fmt = 1 112 } 113 if wav_tag_is(buf, off, 0x64, 0x61, 0x74, 0x61) == 1 { 114 // "data" 115 h.data_off = off + 8 116 h.data_len = size 117 have_data = 1 118 } 119 120 // RIFF chunks pad to even boundaries. 121 var advance: i64 = 8 + size 122 if advance % 2 == 1 { advance = advance + 1 } 123 off = off + advance 124 } 125 if have_fmt == 0 { return WAV_ERR_FORMAT } 126 if have_data == 0 { return WAV_ERR_FORMAT } 127 return 0 128} 129 130// Compile-only smoke -- 44-byte canonical PCM header, 1-sample 131// stereo 16-bit 44.1 kHz. 132func main() -> i64 { 133 let raw: *u8 = sys_mmap(64) 134 var i: i64 = 0 135 while i < 64 { raw[i] = 0; i = i + 1 } 136 137 // "RIFF" + chunk size + "WAVE" 138 raw[0] = 0x52; raw[1] = 0x49; raw[2] = 0x46; raw[3] = 0x46 139 raw[4] = 36; raw[5] = 0; raw[6] = 0; raw[7] = 0 140 raw[8] = 0x57; raw[9] = 0x41; raw[10] = 0x56; raw[11] = 0x45 141 142 // "fmt " subchunk, size 16 143 raw[12] = 0x66; raw[13] = 0x6D; raw[14] = 0x74; raw[15] = 0x20 144 raw[16] = 16 145 146 // format_code = 1 (PCM), channels = 2 147 raw[20] = 1; raw[22] = 2 148 149 // sample_rate = 44100 = 0x0000AC44 150 raw[24] = 0x44; raw[25] = 0xAC 151 152 // byte_rate = 176400 = 0x0002B110 153 raw[28] = 0x10; raw[29] = 0xB1; raw[30] = 0x02 154 155 // block_align = 4, bits_per_sample = 16 156 raw[32] = 4; raw[34] = 16 157 158 // "data" subchunk, size = 4 (one stereo sample = 4 bytes) 159 raw[36] = 0x64; raw[37] = 0x61; raw[38] = 0x74; raw[39] = 0x61 160 raw[40] = 4 161 162 let h_raw: *u8 = sys_mmap(128) 163 let h: *WavHeader = h_raw as *WavHeader 164 if wav_parse(raw, 64, h) != 0 { return 1 } 165 if h.format_code != 1 { return 2 } 166 if h.num_channels != 2 { return 3 } 167 if h.sample_rate != 44100 { return 4 } 168 if h.bits_per_sample != 16 { return 5 } 169 if h.data_off != 44 { return 6 } 170 if h.data_len != 4 { return 7 } 171 172 // Corrupt magic. 173 raw[0] = 0 174 if wav_parse(raw, 64, h) != WAV_ERR_FORMAT { return 8 } 175 return 0 176}