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 BinaryVectorOp = unsafe extern "C" fn(*const f32, *const f32, *mut f32, usize) -> bool;
168type ReduceOp = unsafe extern "C" fn(*const f32, *mut f32, usize) -> bool;
169
170fn binary_vector_op(a: &[f32], b: &[f32], f: BinaryVectorOp) -> Result<Vec<f32>> {
171    if a.len() != b.len() {
172        return Err(Error::InvalidLength {
173            expected: a.len(),
174            actual: b.len(),
175        });
176    }
177
178    let mut out = vec![0.0_f32; a.len()];
179    // SAFETY: All slices are valid for `a.len()` contiguous `f32` elements.
180    let ok = unsafe { f(a.as_ptr(), b.as_ptr(), out.as_mut_ptr(), a.len()) };
181    if ok {
182        Ok(out)
183    } else {
184        Err(Error::OperationFailed("vDSP vector operation failed"))
185    }
186}
187
188fn reduce_f32(values: &[f32], f: ReduceOp) -> Result<f32> {
189    if values.is_empty() {
190        return Err(Error::InvalidLength {
191            expected: 1,
192            actual: 0,
193        });
194    }
195
196    let mut out = 0.0_f32;
197    // SAFETY: The slice is valid for `values.len()` contiguous `f32` elements.
198    let ok = unsafe { f(values.as_ptr(), &mut out, values.len()) };
199    if ok {
200        Ok(out)
201    } else {
202        Err(Error::OperationFailed("vDSP reduction failed"))
203    }
204}
205
206pub fn add_f32(a: &[f32], b: &[f32]) -> Result<Vec<f32>> {
207    binary_vector_op(a, b, bridge::acc_vdsp_add_f32)
208}
209
210pub fn sub_f32(a: &[f32], b: &[f32]) -> Result<Vec<f32>> {
211    binary_vector_op(a, b, bridge::acc_vdsp_sub_f32)
212}
213
214pub fn dot_f32(a: &[f32], b: &[f32]) -> Result<f32> {
215    if a.len() != b.len() {
216        return Err(Error::InvalidLength {
217            expected: a.len(),
218            actual: b.len(),
219        });
220    }
221
222    let mut out = 0.0_f32;
223    // SAFETY: The slices are valid for `a.len()` contiguous `f32` elements.
224    let ok = unsafe { bridge::acc_vdsp_dot_f32(a.as_ptr(), b.as_ptr(), &mut out, a.len()) };
225    if ok {
226        Ok(out)
227    } else {
228        Err(Error::OperationFailed("vDSP dot-product failed"))
229    }
230}
231
232pub fn max_f32(values: &[f32]) -> Result<f32> {
233    reduce_f32(values, bridge::acc_vdsp_max_f32)
234}
235
236pub fn min_f32(values: &[f32]) -> Result<f32> {
237    reduce_f32(values, bridge::acc_vdsp_min_f32)
238}
239
240pub fn mean_f32(values: &[f32]) -> Result<f32> {
241    reduce_f32(values, bridge::acc_vdsp_mean_f32)
242}
243
244pub fn sum_f32(values: &[f32]) -> Result<f32> {
245    reduce_f32(values, bridge::acc_vdsp_sum_f32)
246}
247
248#[must_use]
249pub fn hamming_window(length: usize, flags: i32) -> Vec<f32> {
250    let mut out = vec![0.0_f32; length];
251    if length == 0 {
252        return out;
253    }
254
255    // SAFETY: `out` is valid for `length` contiguous `f32` values.
256    let _ = unsafe { bridge::acc_vdsp_hamming_window(out.as_mut_ptr(), length, flags) };
257    out
258}
259
260#[must_use]
261pub fn blackman_window(length: usize, flags: i32) -> Vec<f32> {
262    let mut out = vec![0.0_f32; length];
263    if length == 0 {
264        return out;
265    }
266
267    // SAFETY: `out` is valid for `length` contiguous `f32` values.
268    let _ = unsafe { bridge::acc_vdsp_blackman_window(out.as_mut_ptr(), length, flags) };
269    out
270}