code wiki / (root) / nx_wav_writer.nx

nx_wav_writer.nx source

↩ module page · 154 lines · 5586 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 27// nx_safety_envelope: 28// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 29// sil_target: SIL1 30// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 31// verdict: NOT_YET_EVALUATED 32 33import "nx_syscalls.nx" 34const WAV_MAGIC_32767: i64 = 32767 35const WAV_MAGIC_44100: i64 = 44100 36 37const WAV_WR_ERR_SHORT: i64 = -1 38const WAV_WR_ERR_BITS: i64 = -2 39 40// Write a little-endian u16 at out[off]. Returns off + 2. 41func wav_write_u16(out: *u8, off: i64, v: i64) -> i64 { 42 out[off] = v & 0xFF 43 out[off + 1] = (v >> 8) & 0xFF 44 return off + 2 45} 46 47// Write a little-endian u32. Returns off + 4. 48func wav_write_u32(out: *u8, off: i64, v: i64) -> i64 { 49 out[off] = v & 0xFF 50 out[off + 1] = (v >> 8) & 0xFF 51 out[off + 2] = (v >> 16) & 0xFF 52 out[off + 3] = (v >> 24) & 0xFF 53 return off + 4 54} 55 56// Write the 44-byte canonical PCM header. `data_bytes` is the 57// number of sample bytes the caller will append immediately after 58// (total wav size = 44 + data_bytes). 59func wav_write_header(out: *u8, cap: i64, 60 num_channels: i64, sample_rate: i64, 61 bits_per_sample: i64, data_bytes: i64) -> i64 { 62 if cap < 44 { return WAV_WR_ERR_SHORT } 63 if bits_per_sample != 16 { return WAV_WR_ERR_BITS } 64 65 // RIFF 66 out[0] = 0x52; out[1] = 0x49; out[2] = 0x46; out[3] = 0x46 67 // chunk size = 36 + data_bytes 68 wav_write_u32(out, 4, 36 + data_bytes) 69 // WAVE 70 out[8] = 0x57; out[9] = 0x41; out[10] = 0x56; out[11] = 0x45 71 // "fmt " 72 out[12] = 0x66; out[13] = 0x6D; out[14] = 0x74; out[15] = 0x20 73 // fmt subchunk size = 16 (PCM) 74 wav_write_u32(out, 16, 16) 75 // format code = 1 (PCM), channels 76 wav_write_u16(out, 20, 1) 77 wav_write_u16(out, 22, num_channels) 78 // sample rate 79 wav_write_u32(out, 24, sample_rate) 80 // byte_rate = sample_rate * channels * bits/8 81 let byte_rate: i64 = sample_rate * num_channels * (bits_per_sample / 8) 82 wav_write_u32(out, 28, byte_rate) 83 // block_align = channels * bits/8 84 let block_align: i64 = num_channels * (bits_per_sample / 8) 85 wav_write_u16(out, 32, block_align) 86 // bits per sample 87 wav_write_u16(out, 34, bits_per_sample) 88 // "data" 89 out[36] = 0x64; out[37] = 0x61; out[38] = 0x74; out[39] = 0x61 90 // data size 91 wav_write_u32(out, 40, data_bytes) 92 return 44 93} 94 95// Convenience: write header + one sample frame from caller's 96// i16 sample array (interleaved [ch0,ch1,...,chN, ch0,ch1,...]). 97// `samples` points to an array of length num_samples * num_channels 98// i16 values (packed little-endian into *u8 by this function). 99func wav_write_pcm16(out: *u8, cap: i64, 100 num_channels: i64, sample_rate: i64, 101 samples: *i64, num_samples: i64) -> i64 { 102 let total_samples: i64 = num_samples * num_channels 103 let data_bytes: i64 = total_samples * 2 104 if cap < 44 + data_bytes { return WAV_WR_ERR_SHORT } 105 106 let hdr_len: i64 = wav_write_header(out, cap, num_channels, 107 sample_rate, 16, data_bytes) 108 if hdr_len < 0 { return hdr_len } 109 110 var i: i64 = 0 111 var off: i64 = hdr_len 112 while i < total_samples { 113 let s: i64 = samples[i] 114 wav_write_u16(out, off, s & 0xFFFF) 115 off = off + 2 116 i = i + 1 117 } 118 return off 119} 120 121// Compile-only smoke: write 2 stereo samples @ 44100 Hz. 122func main() -> i64 { 123 let out: *u8 = sys_mmap(256) 124 let samples: *i64 = sys_mmap(64) as *i64 125 // Frame 0: L=1000, R=-1000. Frame 1: L=0, R=32767. 126 samples[0] = 1000 127 samples[1] = -1000 128 samples[2] = 0 129 samples[3] = WAV_MAGIC_32767 130 131 let n: i64 = wav_write_pcm16(out, 256, 2, WAV_MAGIC_44100, samples, 2) 132 // 44 header + 2 frames * 2 channels * 2 bytes = 52. 133 if n != 52 { return 1 } 134 135 // Magic check. 136 if out[0] != 0x52 { return 2 } // 'R' 137 if out[11] != 0x45 { return 3 } // 'E' of WAVE 138 139 // fmt subchunk says PCM (1) and 2 channels. 140 if out[20] != 1 { return 4 } 141 if out[22] != 2 { return 5 } 142 143 // data tag at offset 36. 144 if out[36] != 0x64 { return 6 } // 'd' 145 146 // First sample little-endian at offset 44: L=1000 = 0x03E8. 147 if out[44] != 0xE8 { return 7 } 148 if out[45] != 0x03 { return 8 } 149 // Second sample (right channel frame 0): -1000 = 0xFC18 150 // in two's complement 16-bit. 0xFFFFFC18 & 0xFFFF = 0xFC18. 151 if out[46] != 0x18 { return 9 } 152 if out[47] != 0xFC { return 10 } 153 return 0 154}