code wiki / (root) / nx_audio_playback.nx

nx_audio_playback.nx source

↩ module page · 180 lines · 6190 B

1// nx_audio_playback.nx -- abstract audio-playback backend + null impl. 2// 3// Symmetric to nx_audio_capture: backends accept PCM frames from 4// nx_audio_session and route them to a speaker (or /dev/null). 5 6// nx_safety_envelope: 7// intended_use: reusable audio playback abstraction 8// sil_target: SIL2 9// evidence: composes nx_fn_ptr_typed (compiler-enforced arity) 10// verdict: NOT_YET_EVALUATED 11 12import "nx_syscalls_x86_64.nx" 13 14// ---- Sealed enum: backend kind ---- 15 16const NX_AUDIO_PLAYBACK_KIND_UNKNOWN: i64 = 0 17const NX_AUDIO_PLAYBACK_KIND_NULL: i64 = 1 18const NX_AUDIO_PLAYBACK_KIND_TAP: i64 = 2 // capture into a buffer for the smoke 19const NX_AUDIO_PLAYBACK_KIND_ALSA: i64 = 3 20const NX_AUDIO_PLAYBACK_KIND_WEBAUDIO: i64 = 4 21const NX_AUDIO_PLAYBACK_KIND_COREAUDIO: i64 = 5 22const NX_AUDIO_PLAYBACK_KIND_N: i64 = 6 23 24// ---- Sealed error codes ---- 25 26const NX_AUDIO_PLAYBACK_ERR_NOT_OPEN: i64 = -1 27const NX_AUDIO_PLAYBACK_ERR_BAD_SIZE: i64 = -2 28const NX_AUDIO_PLAYBACK_ERR_BACKEND: i64 = -3 29 30// ---- The struct ---- 31 32struct nx_audio_playback { 33 kind: i64, 34 sample_rate: i64, 35 channels: i64, 36 state: i64, 37 private: i64, 38 39 // -- vtable -- 40 open_fn: func(*nx_audio_playback) -> i64, 41 close_fn: func(*nx_audio_playback) -> i64, 42 push_frame_fn: func(*nx_audio_playback, *u8, i64) -> i64, 43} 44 45const NX_AUDIO_PLAYBACK_BYTES: i64 = 64 46 47// ---- Generic accessors ---- 48 49func nx_audio_playback_kind(p: *nx_audio_playback) -> i64 { return p.kind } 50func nx_audio_playback_sample_rate(p: *nx_audio_playback) -> i64 { return p.sample_rate } 51func nx_audio_playback_channels(p: *nx_audio_playback) -> i64 { return p.channels } 52func nx_audio_playback_is_open(p: *nx_audio_playback) -> i64 { return p.state } 53 54// ---- Virtual call sites ---- 55 56func nx_audio_playback_open(p: *nx_audio_playback) -> i64 { 57 return p.open_fn(p) 58} 59 60func nx_audio_playback_close(p: *nx_audio_playback) -> i64 { 61 return p.close_fn(p) 62} 63 64func nx_audio_playback_push_frame(p: *nx_audio_playback, 65 src: *u8, src_n: i64) -> i64 { 66 if p.state == 0 { return NX_AUDIO_PLAYBACK_ERR_NOT_OPEN } 67 if src_n <= 0 { return NX_AUDIO_PLAYBACK_ERR_BAD_SIZE } 68 return p.push_frame_fn(p, src, src_n) 69} 70 71// ---- Default vtable impls ---- 72 73func nx_audio_playback_default_open(p: *nx_audio_playback) -> i64 { 74 p.state = 1 75 return 0 76} 77 78func nx_audio_playback_default_close(p: *nx_audio_playback) -> i64 { 79 p.state = 0 80 return 0 81} 82 83func nx_audio_playback_default_push_frame(p: *nx_audio_playback, 84 src: *u8, src_n: i64) -> i64 { 85 return 0 86} 87 88// ---- Base initializer ---- 89 90func nx_audio_playback_init(p: *nx_audio_playback, 91 kind: i64, sample_rate: i64, channels: i64) -> i64 { 92 p.kind = kind 93 p.sample_rate = sample_rate 94 p.channels = channels 95 p.state = 0 96 p.private = 0 97 p.open_fn = nx_audio_playback_default_open 98 p.close_fn = nx_audio_playback_default_close 99 p.push_frame_fn = nx_audio_playback_default_push_frame 100 return 0 101} 102 103// =================================================================== 104// nx_audio_playback_null -- /dev/null backend 105// =================================================================== 106 107func nx_audio_playback_null_push_frame(p: *nx_audio_playback, 108 src: *u8, src_n: i64) -> i64 { 109 return 0 110} 111 112func nx_audio_playback_null_new(p: *nx_audio_playback, 113 sample_rate: i64, channels: i64) -> i64 { 114 nx_audio_playback_init(p, NX_AUDIO_PLAYBACK_KIND_NULL, sample_rate, channels) 115 p.push_frame_fn = nx_audio_playback_null_push_frame 116 return 0 117} 118 119// =================================================================== 120// nx_audio_playback_tap -- in-memory tap backend (smoke fixture) 121// =================================================================== 122// 123// Accumulates pushed bytes into a heap-mmap'd ring so the smoke can 124// assert "decoder produced N bytes that round-trip the synth input". 125// `private` holds the *u8 ring; bytes_seen is tracked via the 126// ring-header layout: first 8 bytes = byte count, then payload. 127 128struct nx_audio_playback_tap_state { 129 ring: *u8, 130 cap: i64, 131 bytes_seen: i64, 132} 133 134const NX_AUDIO_PLAYBACK_TAP_STATE_BYTES: i64 = 24 135 136func nx_audio_playback_tap_push_frame(p: *nx_audio_playback, 137 src: *u8, src_n: i64) -> i64 { 138 let st: *nx_audio_playback_tap_state = p.private as *nx_audio_playback_tap_state 139 // Append to ring up to cap; overflow tracked via bytes_seen. 140 var i: i64 = 0 141 while i < src_n { 142 let pos: i64 = st.bytes_seen + i 143 if pos < st.cap { 144 st.ring[pos] = src[i] 145 } 146 i = i + 1 147 } 148 st.bytes_seen = st.bytes_seen + src_n 149 return 0 150} 151 152func nx_audio_playback_tap_new(p: *nx_audio_playback, 153 sample_rate: i64, channels: i64, 154 ring_cap: i64) -> i64 { 155 nx_audio_playback_init(p, NX_AUDIO_PLAYBACK_KIND_TAP, sample_rate, channels) 156 let st: *nx_audio_playback_tap_state = sys_mmap(NX_AUDIO_PLAYBACK_TAP_STATE_BYTES) 157 as *nx_audio_playback_tap_state 158 st.ring = sys_mmap(ring_cap) 159 st.cap = ring_cap 160 st.bytes_seen = 0 161 p.private = st as i64 162 p.push_frame_fn = nx_audio_playback_tap_push_frame 163 return 0 164} 165 166// Read bytes_seen from a tap-backend playback. Smoke helper. 167func nx_audio_playback_tap_bytes_seen(p: *nx_audio_playback) -> i64 { 168 if p.kind != NX_AUDIO_PLAYBACK_KIND_TAP { return -1 } 169 let st: *nx_audio_playback_tap_state = p.private as *nx_audio_playback_tap_state 170 return st.bytes_seen 171} 172 173func nx_audio_playback_tap_byte_at(p: *nx_audio_playback, off: i64) -> i64 { 174 if p.kind != NX_AUDIO_PLAYBACK_KIND_TAP { return -1 } 175 let st: *nx_audio_playback_tap_state = p.private as *nx_audio_playback_tap_state 176 if off < 0 { return -1 } 177 if off >= st.bytes_seen { return -1 } 178 if off >= st.cap { return -1 } 179 return st.ring[off] as i64 180}