1use std::sync::OnceLock;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct SimdSupport {
11 pub sse2: bool,
13 pub avx2: bool,
15 pub neon: bool,
17}
18
19static SIMD_SUPPORT: OnceLock<SimdSupport> = OnceLock::new();
21
22#[cfg(target_arch = "x86_64")]
23const fn extracted_i16(value: i32) -> i16 {
24 let bytes = value.to_le_bytes();
25 i16::from_le_bytes([bytes[0], bytes[1]])
26}
27
28pub fn init_simd_support() {
30 SIMD_SUPPORT.get_or_init(detect_simd_support);
31}
32
33fn detect_simd_support() -> SimdSupport {
35 #[cfg(target_arch = "x86_64")]
36 {
37 SimdSupport {
38 sse2: is_x86_feature_detected!("sse2"),
39 avx2: is_x86_feature_detected!("avx2"),
40 neon: false,
41 }
42 }
43 #[cfg(target_arch = "aarch64")]
44 {
45 SimdSupport {
46 sse2: false,
47 avx2: false,
48 neon: std::arch::is_aarch64_feature_detected!("neon"),
49 }
50 }
51 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
52 {
53 SimdSupport {
54 sse2: false,
55 avx2: false,
56 neon: false,
57 }
58 }
59}
60
61#[must_use]
63pub fn get_simd_support() -> SimdSupport {
64 *SIMD_SUPPORT.get_or_init(detect_simd_support)
65}
66
67#[must_use]
69pub fn has_simd_support() -> bool {
70 let support = get_simd_support();
71 support.sse2 || support.avx2 || support.neon
72}
73
74#[cfg(target_arch = "x86_64")]
76pub fn encode_mulaw_simd_sse2(samples: &[i16], output: &mut [u8]) {
77 use std::arch::x86_64::*;
78
79 if !get_simd_support().sse2 {
80 return encode_mulaw_scalar(samples, output);
81 }
82
83 let mut chunks = samples.chunks_exact(8);
84 let mut out_idx = 0;
85
86 unsafe {
87 for chunk in chunks.by_ref() {
88 let samples_vec = _mm_loadu_si128(chunk.as_ptr().cast::<__m128i>());
90
91 output[out_idx] =
94 linear_to_mulaw_scalar(extracted_i16(_mm_extract_epi16(samples_vec, 0)));
95 output[out_idx + 1] =
96 linear_to_mulaw_scalar(extracted_i16(_mm_extract_epi16(samples_vec, 1)));
97 output[out_idx + 2] =
98 linear_to_mulaw_scalar(extracted_i16(_mm_extract_epi16(samples_vec, 2)));
99 output[out_idx + 3] =
100 linear_to_mulaw_scalar(extracted_i16(_mm_extract_epi16(samples_vec, 3)));
101 output[out_idx + 4] =
102 linear_to_mulaw_scalar(extracted_i16(_mm_extract_epi16(samples_vec, 4)));
103 output[out_idx + 5] =
104 linear_to_mulaw_scalar(extracted_i16(_mm_extract_epi16(samples_vec, 5)));
105 output[out_idx + 6] =
106 linear_to_mulaw_scalar(extracted_i16(_mm_extract_epi16(samples_vec, 6)));
107 output[out_idx + 7] =
108 linear_to_mulaw_scalar(extracted_i16(_mm_extract_epi16(samples_vec, 7)));
109 out_idx += 8;
110 }
111 }
112
113 for &sample in chunks.remainder() {
115 output[out_idx] = linear_to_mulaw_scalar(sample);
116 out_idx += 1;
117 }
118}
119
120#[cfg(target_arch = "aarch64")]
122pub fn encode_mulaw_simd_neon(samples: &[i16], output: &mut [u8]) {
123 if !get_simd_support().neon {
124 return encode_mulaw_scalar(samples, output);
125 }
126
127 encode_mulaw_scalar(samples, output);
129}
130
131pub fn encode_mulaw_scalar(samples: &[i16], output: &mut [u8]) {
133 for (i, &sample) in samples.iter().enumerate() {
134 output[i] = linear_to_mulaw_scalar(sample);
135 }
136}
137
138#[cfg(target_arch = "x86_64")]
140pub fn encode_alaw_simd_sse2(samples: &[i16], output: &mut [u8]) {
141 use std::arch::x86_64::*;
142
143 if !get_simd_support().sse2 {
144 return encode_alaw_scalar(samples, output);
145 }
146
147 let mut chunks = samples.chunks_exact(8);
148 let mut out_idx = 0;
149
150 unsafe {
151 for chunk in chunks.by_ref() {
152 let samples_vec = _mm_loadu_si128(chunk.as_ptr().cast::<__m128i>());
154
155 output[out_idx] =
158 linear_to_alaw_scalar(extracted_i16(_mm_extract_epi16(samples_vec, 0)));
159 output[out_idx + 1] =
160 linear_to_alaw_scalar(extracted_i16(_mm_extract_epi16(samples_vec, 1)));
161 output[out_idx + 2] =
162 linear_to_alaw_scalar(extracted_i16(_mm_extract_epi16(samples_vec, 2)));
163 output[out_idx + 3] =
164 linear_to_alaw_scalar(extracted_i16(_mm_extract_epi16(samples_vec, 3)));
165 output[out_idx + 4] =
166 linear_to_alaw_scalar(extracted_i16(_mm_extract_epi16(samples_vec, 4)));
167 output[out_idx + 5] =
168 linear_to_alaw_scalar(extracted_i16(_mm_extract_epi16(samples_vec, 5)));
169 output[out_idx + 6] =
170 linear_to_alaw_scalar(extracted_i16(_mm_extract_epi16(samples_vec, 6)));
171 output[out_idx + 7] =
172 linear_to_alaw_scalar(extracted_i16(_mm_extract_epi16(samples_vec, 7)));
173 out_idx += 8;
174 }
175 }
176
177 for &sample in chunks.remainder() {
179 output[out_idx] = linear_to_alaw_scalar(sample);
180 out_idx += 1;
181 }
182}
183
184#[cfg(target_arch = "aarch64")]
186pub fn encode_alaw_simd_neon(samples: &[i16], output: &mut [u8]) {
187 if !get_simd_support().neon {
188 return encode_alaw_scalar(samples, output);
189 }
190
191 encode_alaw_scalar(samples, output);
193}
194
195pub fn encode_alaw_scalar(samples: &[i16], output: &mut [u8]) {
197 for (i, &sample) in samples.iter().enumerate() {
198 output[i] = linear_to_alaw_scalar(sample);
199 }
200}
201
202pub fn encode_mulaw_optimized(samples: &[i16], output: &mut [u8]) {
204 #[cfg(target_arch = "x86_64")]
205 {
206 if get_simd_support().sse2 {
207 return encode_mulaw_simd_sse2(samples, output);
208 }
209 }
210
211 #[cfg(target_arch = "aarch64")]
212 {
213 if get_simd_support().neon {
214 return encode_mulaw_simd_neon(samples, output);
215 }
216 }
217
218 encode_mulaw_scalar(samples, output);
219}
220
221pub fn encode_alaw_optimized(samples: &[i16], output: &mut [u8]) {
223 #[cfg(target_arch = "x86_64")]
224 {
225 if get_simd_support().sse2 {
226 return encode_alaw_simd_sse2(samples, output);
227 }
228 }
229
230 #[cfg(target_arch = "aarch64")]
231 {
232 if get_simd_support().neon {
233 return encode_alaw_simd_neon(samples, output);
234 }
235 }
236
237 encode_alaw_scalar(samples, output);
238}
239
240#[must_use]
242pub const fn linear_to_mulaw_scalar(sample: i16) -> u8 {
243 const CLIP: i16 = 32635;
244 const BIAS: i16 = 0x84;
245 const MULAW_MAX: u8 = 0x7F;
246
247 let mut sample = sample;
248 let sign = if sample < 0 {
249 sample = if sample == i16::MIN {
251 i16::MAX
252 } else {
253 -sample
254 };
255 0x80
256 } else {
257 0x00
258 };
259
260 if sample > CLIP {
261 sample = CLIP;
262 }
263
264 sample += BIAS;
265
266 let exponent = if sample <= 0x1F {
267 0
268 } else if sample <= 0x3F {
269 1
270 } else if sample <= 0x7F {
271 2
272 } else if sample <= 0xFF {
273 3
274 } else if sample <= 0x1FF {
275 4
276 } else if sample <= 0x3FF {
277 5
278 } else if sample <= 0x7FF {
279 6
280 } else {
281 7
282 };
283
284 let mantissa = (sample >> (exponent + 3)) & 0x0F;
285 let mulaw = ((exponent << 4) | mantissa).to_le_bytes()[0];
286
287 (mulaw ^ MULAW_MAX) | sign
288}
289
290#[must_use]
292pub const fn linear_to_alaw_scalar(sample: i16) -> u8 {
293 const CLIP: i16 = 32635;
294 const ALAW_MAX: u8 = 0x7F;
295
296 let mut sample = sample;
297 let sign = if sample < 0 {
298 sample = if sample == i16::MIN {
300 i16::MAX
301 } else {
302 -sample
303 };
304 0x80
305 } else {
306 0x00
307 };
308
309 if sample > CLIP {
310 sample = CLIP;
311 }
312
313 let alaw = if sample < 256 {
314 sample >> 4
315 } else {
316 let exponent = if sample < 512 {
317 1
318 } else if sample < 1024 {
319 2
320 } else if sample < 2048 {
321 3
322 } else if sample < 4096 {
323 4
324 } else if sample < 8192 {
325 5
326 } else if sample < 16384 {
327 6
328 } else {
329 7
330 };
331
332 let mantissa = (sample >> (exponent + 3)) & 0x0F;
333 ((exponent << 4) | mantissa) + 16
334 };
335
336 (alaw.to_le_bytes()[0] ^ ALAW_MAX) | sign
337}
338
339#[must_use]
341#[allow(clippy::cast_lossless)]
342pub const fn mulaw_to_linear_scalar(mulaw: u8) -> i16 {
343 const BIAS: i16 = 0x84;
344 const MULAW_MAX: u8 = 0x7F;
345
346 let mulaw = mulaw ^ MULAW_MAX;
347 let sign = mulaw & 0x80;
348 let exponent = (mulaw >> 4) & 0x07;
349 let mantissa = mulaw & 0x0F;
350
351 let mut sample = ((mantissa as i16) << (exponent + 3)) + BIAS;
352
353 if exponent > 0 {
354 sample += 1i16 << (exponent + 2);
355 }
356
357 if sign != 0 {
358 -sample
359 } else {
360 sample
361 }
362}
363
364#[must_use]
366pub fn alaw_to_linear_scalar(alaw: u8) -> i16 {
367 const ALAW_MAX: u8 = 0x7F;
368
369 let alaw = alaw ^ ALAW_MAX;
370 let sign = alaw & 0x80;
371 let magnitude = alaw & 0x7F;
372
373 let sample = if magnitude < 16 {
374 u16::from(magnitude) << 4
375 } else {
376 let exponent = (magnitude >> 4) & 0x07;
377 let mantissa = magnitude & 0x0F;
378
379 let exp_shift = u32::from(exponent + 3).min(15);
381 let gain_shift = u32::from(exponent + 2).min(15);
382
383 (u16::from(mantissa) << exp_shift) + (1_u16 << gain_shift)
384 } + 8;
385
386 if sign != 0 {
387 -sample.cast_signed()
388 } else {
389 sample.cast_signed()
390 }
391}
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396
397 #[test]
398 fn test_simd_support_detection() {
399 init_simd_support();
400 let support = get_simd_support();
401
402 #[cfg(target_arch = "x86_64")]
404 {
405 println!("SSE2 support: {}", support.sse2);
407 }
408
409 #[cfg(target_arch = "aarch64")]
410 {
411 println!("NEON support: {}", support.neon);
413 }
414 }
415
416 #[test]
417 fn test_mulaw_roundtrip() {
418 let original = 12345i16;
419 let encoded = linear_to_mulaw_scalar(original);
420 let decoded = mulaw_to_linear_scalar(encoded);
421
422 let error = (original - decoded).abs();
424 assert!(error < 1000, "Error too large: {error}");
425 }
426
427 #[test]
428 fn test_alaw_roundtrip() {
429 let original = 12345i16;
430 let encoded = linear_to_alaw_scalar(original);
431 let decoded = alaw_to_linear_scalar(encoded);
432
433 let error = (original - decoded).abs();
437 assert!(
438 error < 5000,
439 "Error too large: {error} (original: {original}, decoded: {decoded})"
440 );
441 }
442
443 #[test]
444 fn test_simd_vs_scalar() {
445 let samples = vec![0, 1000, -1000, 16000, -16000, 32000, -32000, 12345];
446 let mut simd_output = vec![0u8; samples.len()];
447 let mut scalar_output = vec![0u8; samples.len()];
448
449 encode_mulaw_optimized(&samples, &mut simd_output);
450 encode_mulaw_scalar(&samples, &mut scalar_output);
451
452 assert_eq!(simd_output, scalar_output);
454 }
455
456 #[test]
457 fn test_empty_input() {
458 let samples: Vec<i16> = vec![];
459 let mut output: Vec<u8> = vec![];
460
461 encode_mulaw_optimized(&samples, &mut output);
462 assert_eq!(output.len(), 0);
463 }
464
465 #[test]
466 fn test_edge_cases() {
467 let samples = vec![i16::MAX, i16::MIN, 0];
468 let mut output = vec![0u8; samples.len()];
469
470 encode_mulaw_optimized(&samples, &mut output);
471
472 assert_eq!(output.len(), samples.len());
474 }
475}