1const ULAW_BIAS: i32 = 0x84;
15const ULAW_CLIP: i32 = 32_635;
16const ALAW_CLIP: i32 = 32_635;
17
18#[must_use]
20pub fn ulaw_encode(sample: i16) -> u8 {
21 let mut pcm = i32::from(sample);
22 let sign = if pcm < 0 { 0x80 } else { 0x00 };
23 if pcm < 0 {
24 pcm = -pcm;
25 }
26 pcm = pcm.min(ULAW_CLIP);
27 pcm += ULAW_BIAS;
28
29 let mut exponent = 7i32;
30 let mut mask = 0x4000i32;
31 while exponent > 0 && (pcm & mask) == 0 {
32 exponent -= 1;
33 mask >>= 1;
34 }
35 let mantissa = (pcm >> (exponent + 3)) & 0x0F;
36 let code = sign | (exponent << 4) | mantissa;
37 u8::try_from(!code & 0xFF).unwrap_or(0)
41}
42
43#[must_use]
45pub fn ulaw_decode(code: u8) -> i16 {
46 let code = i32::from(!code);
47 let sign = code & 0x80;
48 let exponent = (code >> 4) & 0x07;
49 let mantissa = code & 0x0F;
50 let mut sample = ((mantissa << 3) + ULAW_BIAS) << exponent;
51 sample -= ULAW_BIAS;
52 let sample = if sign != 0 { -sample } else { sample };
53 i16::try_from(sample.clamp(i32::from(i16::MIN), i32::from(i16::MAX))).unwrap_or(0)
54}
55
56#[must_use]
58pub fn alaw_encode(sample: i16) -> u8 {
59 let mut pcm = i32::from(sample);
60 let sign = if pcm >= 0 { 0x80 } else { 0x00 };
63 if pcm < 0 {
64 pcm = -pcm - 1;
65 }
66 pcm = pcm.min(ALAW_CLIP);
67
68 let code = if pcm < 256 {
69 pcm >> 4
70 } else {
71 let mut exponent = 7i32;
72 let mut mask = 0x4000i32;
73 while exponent > 0 && (pcm & mask) == 0 {
74 exponent -= 1;
75 mask >>= 1;
76 }
77 let mantissa = (pcm >> (exponent + 3)) & 0x0F;
78 (exponent << 4) | mantissa
79 };
80 u8::try_from((code ^ sign ^ 0x55) & 0xFF).unwrap_or(0)
83}
84
85#[must_use]
87pub fn alaw_decode(code: u8) -> i16 {
88 let code = i32::from(code ^ 0x55);
89 let sign = code & 0x80;
90 let exponent = (code >> 4) & 0x07;
91 let mantissa = code & 0x0F;
92 let sample = if exponent == 0 {
93 (mantissa << 4) + 8
94 } else {
95 ((mantissa << 4) + 0x108) << (exponent - 1)
96 };
97 let sample = if sign != 0 { sample } else { -sample };
98 i16::try_from(sample.clamp(i32::from(i16::MIN), i32::from(i16::MAX))).unwrap_or(0)
99}
100
101#[must_use]
103pub fn ulaw_encode_all(samples: &[i16]) -> Vec<u8> {
104 samples.iter().copied().map(ulaw_encode).collect()
105}
106
107#[must_use]
109pub fn ulaw_decode_all(codes: &[u8]) -> Vec<i16> {
110 codes.iter().copied().map(ulaw_decode).collect()
111}
112
113#[must_use]
115pub fn alaw_encode_all(samples: &[i16]) -> Vec<u8> {
116 samples.iter().copied().map(alaw_encode).collect()
117}
118
119#[must_use]
121pub fn alaw_decode_all(codes: &[u8]) -> Vec<i16> {
122 codes.iter().copied().map(alaw_decode).collect()
123}
124
125#[cfg(test)]
126#[allow(
127 clippy::unwrap_used,
128 clippy::expect_used,
129 clippy::panic,
130 clippy::indexing_slicing
131)]
132mod tests {
133 use super::*;
134
135 #[test]
140 fn ulaw_matches_the_itu_reference_table() {
141 const REFERENCE: &[(i16, u8)] = &[
142 (0, 255),
143 (1, 255),
144 (-1, 127),
145 (100, 242),
146 (-100, 114),
147 (1000, 206),
148 (-1000, 78),
149 (8000, 160),
150 (-8000, 32),
151 (32767, 128),
152 (-32768, 0),
153 (4096, 175),
154 (-4096, 47),
155 ];
156 for &(sample, expected) in REFERENCE {
157 assert_eq!(ulaw_encode(sample), expected, "µ-law encoding of {sample}");
158 }
159 }
160
161 #[test]
162 fn alaw_matches_the_itu_reference_table() {
163 const REFERENCE: &[(i16, u8)] = &[
164 (0, 213),
165 (1, 213),
166 (-1, 85),
167 (100, 211),
168 (-100, 83),
169 (1000, 250),
170 (-1000, 122),
171 (8000, 138),
172 (-8000, 10),
173 (32767, 170),
174 (-32768, 42),
175 (4096, 133),
176 (-4096, 26),
177 ];
178 for &(sample, expected) in REFERENCE {
179 assert_eq!(alaw_encode(sample), expected, "A-law encoding of {sample}");
180 }
181 }
182
183 #[test]
184 fn ulaw_decoding_matches_the_reference() {
185 assert_eq!(ulaw_decode(0xFF), 0);
186 assert_eq!(ulaw_decode(0x7F), 0);
187 assert_eq!(ulaw_decode(0x00), -32_124);
188 assert_eq!(ulaw_decode(0x80), 32_124);
189 }
190
191 #[test]
192 fn alaw_decoding_matches_the_reference() {
193 assert_eq!(alaw_decode(0xD5), 8);
194 assert_eq!(alaw_decode(0x55), -8);
195 assert_eq!(alaw_decode(0x2A), -32_256);
196 assert_eq!(alaw_decode(0xAA), 32_256);
197 }
198
199 #[test]
202 fn the_codec_is_symmetric_about_zero() {
203 let ulaw_sum: i64 = (0..=255u8).map(|c| i64::from(ulaw_decode(c))).sum();
204 let alaw_sum: i64 = (0..=255u8).map(|c| i64::from(alaw_decode(c))).sum();
205 assert_eq!(ulaw_sum, 0, "µ-law is not symmetric");
206 assert_eq!(alaw_sum, 0, "A-law is not symmetric");
207 }
208
209 #[test]
218 fn decoding_then_encoding_returns_the_same_code() {
219 const ULAW_NEGATIVE_ZERO: u8 = 127;
220
221 for code in 0..=255u8 {
222 if code == ULAW_NEGATIVE_ZERO {
223 assert_eq!(ulaw_decode(code), 0, "code 127 is µ-law's negative zero");
224 assert_eq!(
225 ulaw_encode(ulaw_decode(code)),
226 255,
227 "and it normalises to positive zero"
228 );
229 } else {
230 assert_eq!(
231 ulaw_encode(ulaw_decode(code)),
232 code,
233 "µ-law is not idempotent at code {code}"
234 );
235 }
236
237 assert_eq!(
238 alaw_encode(alaw_decode(code)),
239 code,
240 "A-law is not idempotent at code {code}"
241 );
242 }
243 }
244
245 #[test]
249 fn quantisation_error_stays_within_the_step_size() {
250 for sample in (-32_768..=32_767).step_by(37) {
251 let sample = i16::try_from(sample).expect("in range");
252 let error = (i32::from(ulaw_decode(ulaw_encode(sample))) - i32::from(sample)).abs();
253 let allowed = (i32::from(sample).abs() >> 5).max(16);
254 assert!(
255 error <= allowed,
256 "µ-law error {error} at sample {sample} exceeds {allowed}"
257 );
258 }
259 }
260
261 #[test]
264 fn loud_samples_clip_rather_than_wrapping() {
265 assert_eq!(ulaw_encode(32_767), ulaw_encode(32_000));
266 assert!(ulaw_decode(ulaw_encode(32_767)) > 30_000);
267 assert!(ulaw_decode(ulaw_encode(-32_768)) < -30_000);
268 assert!(alaw_decode(alaw_encode(32_767)) > 30_000);
269 assert!(alaw_decode(alaw_encode(-32_768)) < -30_000);
270 }
271
272 #[test]
273 fn buffers_encode_and_decode_as_wholes() {
274 let samples: Vec<i16> = (0..160).map(|i| i * 100 - 8000).collect();
275 let encoded = ulaw_encode_all(&samples);
276 assert_eq!(encoded.len(), samples.len());
277 let decoded = ulaw_decode_all(&encoded);
278 assert_eq!(decoded.len(), samples.len());
279 assert_eq!(
280 ulaw_encode_all(&decoded),
281 encoded,
282 "idempotent over a buffer"
283 );
284 }
285}