code wiki / (root) / nx_wav.nx

nx_wav.nx source

↩ module page · 160 lines · 5889 B

1// nx_wav.nx -- pure-NishiLang RIFF/WAVE (PCM 16-bit LE) writer. 2// 3// Why a sovereign WAV writer: it gives the substrate a path from i64 4// sample buffers to a file the operating system can play through any 5// sound card with no library, no codec, no userspace audio stack 6// dependency. /tmp/foo.wav -> aplay -> speakers; we hear our BFSK 7// tones in the air. That is the "real test" hook between the modem 8// primitives and human ears. 9// 10// The WAV header is the 1991 Microsoft RIFF spec; fully public. 11// We write the canonical 44-byte form (no LIST/INFO chunks). 12// 13// genealogy_id: rfc_riff_1991 + microsoft_wave_spec + nx_bfsk_q10 14// lineage_id: nishi_wav_writer_q10 15// 16// Header layout (PCM 16-bit LE, 44 bytes total): 17// [0..3] "RIFF" 18// [4..7] chunk size = 36 + data_bytes (LE u32) 19// [8..11] "WAVE" 20// [12..15] "fmt " 21// [16..19] 16 (fmt chunk size, PCM) 22// [20..21] 1 (PCM format) 23// [22..23] channels 24// [24..27] sample_rate 25// [28..31] byte_rate = sample_rate * channels * 2 26// [32..33] block_align = channels * 2 27// [34..35] bits_per_sample = 16 28// [36..39] "data" 29// [40..43] data_bytes = n_samples * channels * 2 30// [44..] sample data, int16 LE 31 32// nx_safety_envelope: 33// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 34// sil_target: SIL1 35// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 36// verdict: NOT_YET_EVALUATED 37 38import "nx_syscalls_x86_64.nx" 39 40// Sealed verdict per write call. 41const NX_WAV_VERDICT_UNKNOWN: i64 = 0 42const NX_WAV_VERDICT_OK: i64 = 1 43const NX_WAV_VERDICT_OPEN_FAIL: i64 = 2 44const NX_WAV_VERDICT_WRITE_FAIL: i64 = 3 45const NX_WAV_VERDICT_BAD_PARAMS: i64 = 4 46const NX_WAV_VERDICT_N: i64 = 5 47 48const NX_WAV_HEADER_BYTES: i64 = 44 49 50// Write little-endian u32 into buf[off..off+4]. Returns off+4. 51func _w_le32(buf: *u8, off: i64, v: i64) -> i64 { 52 buf[off] = v & 0xff 53 buf[off + 1] = (v >> 8) & 0xff 54 buf[off + 2] = (v >> 16) & 0xff 55 buf[off + 3] = (v >> 24) & 0xff 56 return off + 4 57} 58 59// Write little-endian u16 into buf[off..off+2]. Returns off+2. 60func _w_le16(buf: *u8, off: i64, v: i64) -> i64 { 61 buf[off] = v & 0xff 62 buf[off + 1] = (v >> 8) & 0xff 63 return off + 2 64} 65 66// Build the 44-byte WAV header into `header` (caller-allocated, >= 44 67// bytes). Returns NX_WAV_HEADER_BYTES. 68func nx_wav_build_header( 69 header: *u8, 70 sample_rate_hz: i64, channels: i64, n_samples: i64 71) -> i64 { 72 let data_bytes: i64 = n_samples * channels * 2 73 let chunk_size: i64 = 36 + data_bytes 74 let byte_rate: i64 = sample_rate_hz * channels * 2 75 let block_align: i64 = channels * 2 76 77 header[0]=82; header[1]=73; header[2]=70; header[3]=70 // "RIFF" 78 _w_le32(header, 4, chunk_size) 79 header[8]=87; header[9]=65; header[10]=86; header[11]=69 // "WAVE" 80 header[12]=102; header[13]=109; header[14]=116; header[15]=32 // "fmt " 81 _w_le32(header, 16, 16) // fmt chunk size 82 _w_le16(header, 20, 1) // PCM format 83 _w_le16(header, 22, channels) 84 _w_le32(header, 24, sample_rate_hz) 85 _w_le32(header, 28, byte_rate) 86 _w_le16(header, 32, block_align) 87 _w_le16(header, 34, 16) // bits per sample 88 header[36]=100; header[37]=97; header[38]=116; header[39]=97 // "data" 89 _w_le32(header, 40, data_bytes) 90 return NX_WAV_HEADER_BYTES 91} 92 93// Open `path` for writing (creates / truncates), write header, return fd 94// or -errno-as-NX-verdict (negative = open failed). 95func nx_wav_open(path: *u8, sample_rate_hz: i64, channels: i64, n_samples: i64, 96 out_fd: *i64) -> i64 { 97 if sample_rate_hz < 1 { return NX_WAV_VERDICT_BAD_PARAMS } 98 if channels < 1 { return NX_WAV_VERDICT_BAD_PARAMS } 99 if channels > 8 { return NX_WAV_VERDICT_BAD_PARAMS } 100 if n_samples < 0 { return NX_WAV_VERDICT_BAD_PARAMS } 101 102 let fd: i64 = sys_openat_wr(path, 0x1a4) // 0644 103 if fd < 0 { *out_fd = fd; return NX_WAV_VERDICT_OPEN_FAIL } 104 105 let header: *u8 = sys_mmap(64) 106 nx_wav_build_header(header, sample_rate_hz, channels, n_samples) 107 let wn: i64 = sys_write(fd, header, NX_WAV_HEADER_BYTES) 108 if wn != NX_WAV_HEADER_BYTES { 109 sys_close(fd) 110 return NX_WAV_VERDICT_WRITE_FAIL 111 } 112 *out_fd = fd 113 return NX_WAV_VERDICT_OK 114} 115 116// Write `n` samples (i64; low 16 bits used as signed int16) to fd. 117// Samples are clipped to [-32768, 32767]. 118func nx_wav_write_samples(fd: i64, samples: *i64, n: i64) -> i64 { 119 if n < 0 { return NX_WAV_VERDICT_BAD_PARAMS } 120 let chunk: *u8 = sys_mmap(2048) 121 var i: i64 = 0 122 while i < n { 123 var batch: i64 = 1024 124 if i + batch > n { batch = n - i } 125 var j: i64 = 0 126 while j < batch { 127 var v: i64 = samples[i + j] 128 if v > 32767 { v = 32767 } 129 if v < -32768 { v = -32768 } 130 if v < 0 { v = v + 65536 } 131 chunk[j * 2] = v & 0xff 132 chunk[j * 2 + 1] = (v >> 8) & 0xff 133 j = j + 1 134 } 135 let wn: i64 = sys_write(fd, chunk, batch * 2) 136 if wn != batch * 2 { return NX_WAV_VERDICT_WRITE_FAIL } 137 i = i + batch 138 } 139 return NX_WAV_VERDICT_OK 140} 141 142// One-shot: open + write samples + close. 143func nx_wav_write_file(path: *u8, sample_rate_hz: i64, channels: i64, 144 samples: *i64, n_samples: i64) -> i64 { 145 let fd_slot: *i64 = sys_mmap(16) as *i64 146 let v: i64 = nx_wav_open(path, sample_rate_hz, channels, n_samples, fd_slot) 147 if v != NX_WAV_VERDICT_OK { return v } 148 let fd: i64 = *fd_slot 149 let wv: i64 = nx_wav_write_samples(fd, samples, n_samples) 150 sys_close(fd) 151 if wv != NX_WAV_VERDICT_OK { return wv } 152 return NX_WAV_VERDICT_OK 153} 154 155// Sealed-enum validity gate. 156func nx_wav_verdict_is_valid(v: i64) -> i64 { 157 if v < 0 { return 0 } 158 if v >= NX_WAV_VERDICT_N { return 0 } 159 return 1 160}