Skip to main content

apple_accelerate/
vdsp.rs

1use crate::bridge;
2use crate::error::{Error, Result};
3use core::ffi::c_void;
4use core::ptr;
5
6/// `FFTDirection` constants.
7pub mod fft_direction {
8    pub const FORWARD: i32 = 1;
9    pub const INVERSE: i32 = -1;
10}
11
12/// `FFTRadix` constants.
13pub mod fft_radix {
14    pub const RADIX2: i32 = 0;
15    pub const RADIX3: i32 = 1;
16    pub const RADIX5: i32 = 2;
17}
18
19/// Window-generation flags.
20pub mod window_flags {
21    pub const HALF_WINDOW: i32 = 1;
22    pub const HANN_DENORM: i32 = 0;
23    pub const HANN_NORM: i32 = 2;
24}
25
26/// Owned `FFTSetup` handle backed by the Swift bridge.
27pub struct FftSetup {
28    ptr: *mut c_void,
29}
30
31unsafe impl Send for FftSetup {}
32unsafe impl Sync for FftSetup {}
33
34impl Drop for FftSetup {
35    fn drop(&mut self) {
36        if !self.ptr.is_null() {
37            // SAFETY: `ptr` is an opaque Swift object retained by the bridge.
38            unsafe { bridge::acc_release_handle(self.ptr) };
39            self.ptr = ptr::null_mut();
40        }
41    }
42}
43
44impl FftSetup {
45    #[must_use]
46    pub fn new(log2n: usize, radix: i32) -> Option<Self> {
47        // SAFETY: Pure constructor over scalar inputs.
48        let ptr = unsafe { bridge::acc_vdsp_fft_setup_create(log2n, radix) };
49        if ptr.is_null() {
50            None
51        } else {
52            Some(Self { ptr })
53        }
54    }
55
56    pub fn fft_zip(
57        &self,
58        real: &mut [f32],
59        imag: &mut [f32],
60        log2n: usize,
61        direction: i32,
62    ) -> Result<()> {
63        let shift = u32::try_from(log2n)
64            .map_err(|_| Error::OperationFailed("FFT log2 length exceeds u32"))?;
65        let expected = 1_usize
66            .checked_shl(shift)
67            .ok_or(Error::OperationFailed("FFT length overflowed"))?;
68        if real.len() != expected {
69            return Err(Error::InvalidLength {
70                expected,
71                actual: real.len(),
72            });
73        }
74        if imag.len() != expected {
75            return Err(Error::InvalidLength {
76                expected,
77                actual: imag.len(),
78            });
79        }
80
81        // SAFETY: Buffers are valid for `expected` elements and `self.ptr` is a live bridge handle.
82        let ok = unsafe {
83            bridge::acc_vdsp_fft_setup_apply(
84                self.ptr,
85                real.as_mut_ptr(),
86                imag.as_mut_ptr(),
87                log2n,
88                direction,
89            )
90        };
91        if ok {
92            Ok(())
93        } else {
94            Err(Error::OperationFailed("vDSP FFT operation failed"))
95        }
96    }
97}
98
99/// Owned `vDSP_biquad_Setup` handle backed by the Swift bridge.
100pub struct BiquadSetup {
101    ptr: *mut c_void,
102}
103
104unsafe impl Send for BiquadSetup {}
105unsafe impl Sync for BiquadSetup {}
106
107impl Drop for BiquadSetup {
108    fn drop(&mut self) {
109        if !self.ptr.is_null() {
110            // SAFETY: `ptr` is an opaque Swift object retained by the bridge.
111            unsafe { bridge::acc_release_handle(self.ptr) };
112            self.ptr = ptr::null_mut();
113        }
114    }
115}
116
117impl BiquadSetup {
118    #[must_use]
119    pub fn new(coefficients: &[f64]) -> Option<Self> {
120        if coefficients.is_empty() || coefficients.len() % 5 != 0 {
121            return None;
122        }
123
124        // SAFETY: `coefficients` is valid for `count` contiguous `f64` values.
125        let ptr = unsafe {
126            bridge::acc_vdsp_biquad_setup_create(coefficients.as_ptr(), coefficients.len())
127        };
128        if ptr.is_null() {
129            None
130        } else {
131            Some(Self { ptr })
132        }
133    }
134
135    pub fn apply(&self, delay: &mut [f32], input: &[f32], output: &mut [f32]) -> Result<()> {
136        if delay.is_empty() {
137            return Err(Error::InvalidLength {
138                expected: 1,
139                actual: 0,
140            });
141        }
142        if input.len() != output.len() {
143            return Err(Error::InvalidLength {
144                expected: input.len(),
145                actual: output.len(),
146            });
147        }
148
149        // SAFETY: Buffers are valid and `self.ptr` is a live bridge handle.
150        let ok = unsafe {
151            bridge::acc_vdsp_biquad_setup_apply(
152                self.ptr,
153                delay.as_mut_ptr(),
154                input.as_ptr(),
155                output.as_mut_ptr(),
156                input.len(),
157            )
158        };
159        if ok {
160            Ok(())
161        } else {
162            Err(Error::OperationFailed("vDSP biquad operation failed"))
163        }
164    }
165}
166
167type BinaryVectorOpF32 = unsafe extern "C" fn(*const f32, *const f32, *mut f32, usize) -> bool;
168type BinaryVectorOpF64 = unsafe extern "C" fn(*const f64, *const f64, *mut f64, usize) -> bool;
169type ReduceOpF32 = unsafe extern "C" fn(*const f32, *mut f32, usize) -> bool;
170type ReduceOpF64 = unsafe extern "C" fn(*const f64, *mut f64, usize) -> bool;
171type WindowOpF32 = unsafe extern "C" fn(*mut f32, usize, i32) -> bool;
172type WindowOpF64 = unsafe extern "C" fn(*mut f64, usize, i32) -> bool;
173
174fn binary_vector_op_f32(a: &[f32], b: &[f32], f: BinaryVectorOpF32) -> Result<Vec<f32>> {
175    if a.len() != b.len() {
176        return Err(Error::InvalidLength {
177            expected: a.len(),
178            actual: b.len(),
179        });
180    }
181
182    let mut out = vec![0.0_f32; a.len()];
183    // SAFETY: All slices are valid for `a.len()` contiguous `f32` elements.
184    let ok = unsafe { f(a.as_ptr(), b.as_ptr(), out.as_mut_ptr(), a.len()) };
185    if ok {
186        Ok(out)
187    } else {
188        Err(Error::OperationFailed("vDSP vector operation failed"))
189    }
190}
191
192fn binary_vector_op_f64(a: &[f64], b: &[f64], f: BinaryVectorOpF64) -> Result<Vec<f64>> {
193    if a.len() != b.len() {
194        return Err(Error::InvalidLength {
195            expected: a.len(),
196            actual: b.len(),
197        });
198    }
199
200    let mut out = vec![0.0_f64; a.len()];
201    // SAFETY: All slices are valid for `a.len()` contiguous `f64` elements.
202    let ok = unsafe { f(a.as_ptr(), b.as_ptr(), out.as_mut_ptr(), a.len()) };
203    if ok {
204        Ok(out)
205    } else {
206        Err(Error::OperationFailed("vDSP vector operation failed"))
207    }
208}
209
210fn reduce_f32(values: &[f32], f: ReduceOpF32) -> Result<f32> {
211    if values.is_empty() {
212        return Err(Error::InvalidLength {
213            expected: 1,
214            actual: 0,
215        });
216    }
217
218    let mut out = 0.0_f32;
219    // SAFETY: The slice is valid for `values.len()` contiguous `f32` elements.
220    let ok = unsafe { f(values.as_ptr(), &mut out, values.len()) };
221    if ok {
222        Ok(out)
223    } else {
224        Err(Error::OperationFailed("vDSP reduction failed"))
225    }
226}
227
228fn reduce_f64(values: &[f64], f: ReduceOpF64) -> Result<f64> {
229    if values.is_empty() {
230        return Err(Error::InvalidLength {
231            expected: 1,
232            actual: 0,
233        });
234    }
235
236    let mut out = 0.0_f64;
237    // SAFETY: The slice is valid for `values.len()` contiguous `f64` elements.
238    let ok = unsafe { f(values.as_ptr(), &mut out, values.len()) };
239    if ok {
240        Ok(out)
241    } else {
242        Err(Error::OperationFailed("vDSP reduction failed"))
243    }
244}
245
246#[must_use]
247fn window_f32(length: usize, flags: i32, f: WindowOpF32) -> Vec<f32> {
248    let mut out = vec![0.0_f32; length];
249    if length == 0 {
250        return out;
251    }
252
253    // SAFETY: `out` is valid for `length` contiguous `f32` values.
254    let _ = unsafe { f(out.as_mut_ptr(), length, flags) };
255    out
256}
257
258#[must_use]
259fn window_f64(length: usize, flags: i32, f: WindowOpF64) -> Vec<f64> {
260    let mut out = vec![0.0_f64; length];
261    if length == 0 {
262        return out;
263    }
264
265    // SAFETY: `out` is valid for `length` contiguous `f64` values.
266    let _ = unsafe { f(out.as_mut_ptr(), length, flags) };
267    out
268}
269
270pub fn add_f32(a: &[f32], b: &[f32]) -> Result<Vec<f32>> {
271    binary_vector_op_f32(a, b, bridge::acc_vdsp_add_f32)
272}
273
274pub fn add_f64(a: &[f64], b: &[f64]) -> Result<Vec<f64>> {
275    binary_vector_op_f64(a, b, bridge::acc_vdsp_add_f64)
276}
277
278pub fn sub_f32(a: &[f32], b: &[f32]) -> Result<Vec<f32>> {
279    binary_vector_op_f32(a, b, bridge::acc_vdsp_sub_f32)
280}
281
282pub fn sub_f64(a: &[f64], b: &[f64]) -> Result<Vec<f64>> {
283    binary_vector_op_f64(a, b, bridge::acc_vdsp_sub_f64)
284}
285
286pub fn dot_f32(a: &[f32], b: &[f32]) -> Result<f32> {
287    if a.len() != b.len() {
288        return Err(Error::InvalidLength {
289            expected: a.len(),
290            actual: b.len(),
291        });
292    }
293
294    let mut out = 0.0_f32;
295    // SAFETY: The slices are valid for `a.len()` contiguous `f32` elements.
296    let ok = unsafe { bridge::acc_vdsp_dot_f32(a.as_ptr(), b.as_ptr(), &mut out, a.len()) };
297    if ok {
298        Ok(out)
299    } else {
300        Err(Error::OperationFailed("vDSP dot-product failed"))
301    }
302}
303
304pub fn dot_f64(a: &[f64], b: &[f64]) -> Result<f64> {
305    if a.len() != b.len() {
306        return Err(Error::InvalidLength {
307            expected: a.len(),
308            actual: b.len(),
309        });
310    }
311
312    let mut out = 0.0_f64;
313    // SAFETY: The slices are valid for `a.len()` contiguous `f64` elements.
314    let ok = unsafe { bridge::acc_vdsp_dot_f64(a.as_ptr(), b.as_ptr(), &mut out, a.len()) };
315    if ok {
316        Ok(out)
317    } else {
318        Err(Error::OperationFailed("vDSP dot-product failed"))
319    }
320}
321
322pub fn max_f32(values: &[f32]) -> Result<f32> {
323    reduce_f32(values, bridge::acc_vdsp_max_f32)
324}
325
326pub fn max_f64(values: &[f64]) -> Result<f64> {
327    reduce_f64(values, bridge::acc_vdsp_max_f64)
328}
329
330pub fn min_f32(values: &[f32]) -> Result<f32> {
331    reduce_f32(values, bridge::acc_vdsp_min_f32)
332}
333
334pub fn min_f64(values: &[f64]) -> Result<f64> {
335    reduce_f64(values, bridge::acc_vdsp_min_f64)
336}
337
338pub fn mean_f32(values: &[f32]) -> Result<f32> {
339    reduce_f32(values, bridge::acc_vdsp_mean_f32)
340}
341
342pub fn mean_f64(values: &[f64]) -> Result<f64> {
343    reduce_f64(values, bridge::acc_vdsp_mean_f64)
344}
345
346pub fn sum_f32(values: &[f32]) -> Result<f32> {
347    reduce_f32(values, bridge::acc_vdsp_sum_f32)
348}
349
350pub fn sum_f64(values: &[f64]) -> Result<f64> {
351    reduce_f64(values, bridge::acc_vdsp_sum_f64)
352}
353
354#[must_use]
355pub fn hamming_window(length: usize, flags: i32) -> Vec<f32> {
356    window_f32(length, flags, bridge::acc_vdsp_hamming_window)
357}
358
359#[must_use]
360pub fn hamming_window_f64(length: usize, flags: i32) -> Vec<f64> {
361    window_f64(length, flags, bridge::acc_vdsp_hamming_window_f64)
362}
363
364#[must_use]
365pub fn blackman_window(length: usize, flags: i32) -> Vec<f32> {
366    window_f32(length, flags, bridge::acc_vdsp_blackman_window)
367}
368
369#[must_use]
370pub fn blackman_window_f64(length: usize, flags: i32) -> Vec<f64> {
371    window_f64(length, flags, bridge::acc_vdsp_blackman_window_f64)
372}