code wiki / (root) / wav_writer.nx

wav_writer.nx source

↩ module page · 146 lines · 5388 B

1// wav_writer.nx -- serialise 16-bit PCM WAV files (canonical 2// 44-byte header + raw samples). Paired with wav_header.nx for 3// the reader half of the roundtrip. 4// 5// Use cases: debug tones, sovereign test-signal generation, 6// dumping buffers from a DSP pipeline during bring-up, tiny 7// synth prototypes without external tooling. 8// 9// Produces the standard 44-byte canonical PCM header: 10// RIFF [size] WAVE 11// fmt [16] fmt_code=1 channels sample_rate byte_rate 12// block_align bits_per_sample 13// data [size] <samples> 14// 15// Samples are little-endian signed i16 in (-32768..32767). 16// Caller owns the sample buffer; this module writes header + copies. 17// 18// Invariants: 19// W1 Only 16-bit PCM produced today. 8 / 24 / 32 bit and 20// float formats pending a future commit. 21// W2 num_samples is PER-CHANNEL sample count (not total 22// sample-frames across channels). Total bytes = 23// num_samples * num_channels * 2. 24// W3 Output buffer must be at least 44 + 2*channels*num_samples 25// bytes or WAV_ERR_SHORT is returned. 26 27import "syscalls.nx" 28 29const WAV_WR_ERR_SHORT: i64 = -1 30const WAV_WR_ERR_BITS: i64 = -2 31 32// Write a little-endian u16 at out[off]. Returns off + 2. 33func wav_write_u16(out: *u8, off: i64, v: i64) -> i64 { 34 out[off] = v & 0xFF 35 out[off + 1] = (v >> 8) & 0xFF 36 return off + 2 37} 38 39// Write a little-endian u32. Returns off + 4. 40func wav_write_u32(out: *u8, off: i64, v: i64) -> i64 { 41 out[off] = v & 0xFF 42 out[off + 1] = (v >> 8) & 0xFF 43 out[off + 2] = (v >> 16) & 0xFF 44 out[off + 3] = (v >> 24) & 0xFF 45 return off + 4 46} 47 48// Write the 44-byte canonical PCM header. `data_bytes` is the 49// number of sample bytes the caller will append immediately after 50// (total wav size = 44 + data_bytes). 51func wav_write_header(out: *u8, cap: i64, 52 num_channels: i64, sample_rate: i64, 53 bits_per_sample: i64, data_bytes: i64) -> i64 { 54 if cap < 44 { return WAV_WR_ERR_SHORT } 55 if bits_per_sample != 16 { return WAV_WR_ERR_BITS } 56 57 // RIFF 58 out[0] = 0x52; out[1] = 0x49; out[2] = 0x46; out[3] = 0x46 59 // chunk size = 36 + data_bytes 60 wav_write_u32(out, 4, 36 + data_bytes) 61 // WAVE 62 out[8] = 0x57; out[9] = 0x41; out[10] = 0x56; out[11] = 0x45 63 // "fmt " 64 out[12] = 0x66; out[13] = 0x6D; out[14] = 0x74; out[15] = 0x20 65 // fmt subchunk size = 16 (PCM) 66 wav_write_u32(out, 16, 16) 67 // format code = 1 (PCM), channels 68 wav_write_u16(out, 20, 1) 69 wav_write_u16(out, 22, num_channels) 70 // sample rate 71 wav_write_u32(out, 24, sample_rate) 72 // byte_rate = sample_rate * channels * bits/8 73 let byte_rate: i64 = sample_rate * num_channels * (bits_per_sample / 8) 74 wav_write_u32(out, 28, byte_rate) 75 // block_align = channels * bits/8 76 let block_align: i64 = num_channels * (bits_per_sample / 8) 77 wav_write_u16(out, 32, block_align) 78 // bits per sample 79 wav_write_u16(out, 34, bits_per_sample) 80 // "data" 81 out[36] = 0x64; out[37] = 0x61; out[38] = 0x74; out[39] = 0x61 82 // data size 83 wav_write_u32(out, 40, data_bytes) 84 return 44 85} 86 87// Convenience: write header + one sample frame from caller's 88// i16 sample array (interleaved [ch0,ch1,...,chN, ch0,ch1,...]). 89// `samples` points to an array of length num_samples * num_channels 90// i16 values (packed little-endian into *u8 by this function). 91func wav_write_pcm16(out: *u8, cap: i64, 92 num_channels: i64, sample_rate: i64, 93 samples: *i64, num_samples: i64) -> i64 { 94 let total_samples: i64 = num_samples * num_channels 95 let data_bytes: i64 = total_samples * 2 96 if cap < 44 + data_bytes { return WAV_WR_ERR_SHORT } 97 98 let hdr_len: i64 = wav_write_header(out, cap, num_channels, 99 sample_rate, 16, data_bytes) 100 if hdr_len < 0 { return hdr_len } 101 102 var i: i64 = 0 103 var off: i64 = hdr_len 104 while i < total_samples { 105 let s: i64 = samples[i] 106 wav_write_u16(out, off, s & 0xFFFF) 107 off = off + 2 108 i = i + 1 109 } 110 return off 111} 112 113// Compile-only smoke: write 2 stereo samples @ 44100 Hz. 114func main() -> i64 { 115 let out: *u8 = sys_mmap(256) 116 let samples: *i64 = sys_mmap(64) as *i64 117 // Frame 0: L=1000, R=-1000. Frame 1: L=0, R=32767. 118 samples[0] = 1000 119 samples[1] = -1000 120 samples[2] = 0 121 samples[3] = 32767 122 123 let n: i64 = wav_write_pcm16(out, 256, 2, 44100, samples, 2) 124 // 44 header + 2 frames * 2 channels * 2 bytes = 52. 125 if n != 52 { return 1 } 126 127 // Magic check. 128 if out[0] != 0x52 { return 2 } // 'R' 129 if out[11] != 0x45 { return 3 } // 'E' of WAVE 130 131 // fmt subchunk says PCM (1) and 2 channels. 132 if out[20] != 1 { return 4 } 133 if out[22] != 2 { return 5 } 134 135 // data tag at offset 36. 136 if out[36] != 0x64 { return 6 } // 'd' 137 138 // First sample little-endian at offset 44: L=1000 = 0x03E8. 139 if out[44] != 0xE8 { return 7 } 140 if out[45] != 0x03 { return 8 } 141 // Second sample (right channel frame 0): -1000 = 0xFC18 142 // in two's complement 16-bit. 0xFFFFFC18 & 0xFFFF = 0xFC18. 143 if out[46] != 0x18 { return 9 } 144 if out[47] != 0xFC { return 10 } 145 return 0 146}