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}