Skip to main content

apple_accelerate/
vdsp.rs

1use crate::error::{Error, Result};
2use crate::ffi;
3use core::ptr;
4
5/// `FFTDirection` constants.
6pub mod fft_direction {
7    pub const FORWARD: i32 = 1;
8    pub const INVERSE: i32 = -1;
9}
10
11/// `FFTRadix` constants.
12pub mod fft_radix {
13    pub const RADIX2: i32 = 0;
14    pub const RADIX3: i32 = 1;
15    pub const RADIX5: i32 = 2;
16}
17
18/// Window-generation flags.
19pub mod window_flags {
20    pub const HALF_WINDOW: i32 = 1;
21    pub const HANN_DENORM: i32 = 0;
22    pub const HANN_NORM: i32 = 2;
23}
24
25/// Owned `FFTSetup` handle.
26pub struct FftSetup {
27    ptr: ffi::FFTSetup,
28}
29
30unsafe impl Send for FftSetup {}
31unsafe impl Sync for FftSetup {}
32
33impl Drop for FftSetup {
34    fn drop(&mut self) {
35        if !self.ptr.is_null() {
36            // SAFETY: `ptr` was returned by `vDSP_create_fftsetup` and is owned by this wrapper.
37            unsafe { ffi::vDSP_destroy_fftsetup(self.ptr) };
38            self.ptr = ptr::null_mut();
39        }
40    }
41}
42
43impl FftSetup {
44    #[must_use]
45    pub fn new(log2n: usize, radix: i32) -> Option<Self> {
46        // SAFETY: Pure constructor over scalar inputs.
47        let ptr = unsafe { ffi::vDSP_create_fftsetup(log2n, radix) };
48        if ptr.is_null() {
49            None
50        } else {
51            Some(Self { ptr })
52        }
53    }
54
55    pub fn fft_zip(
56        &self,
57        real: &mut [f32],
58        imag: &mut [f32],
59        log2n: usize,
60        direction: i32,
61    ) -> Result<()> {
62        let shift = u32::try_from(log2n)
63            .map_err(|_| Error::OperationFailed("FFT log2 length exceeds u32"))?;
64        let expected = 1_usize
65            .checked_shl(shift)
66            .ok_or(Error::OperationFailed("FFT length overflowed"))?;
67        if real.len() != expected {
68            return Err(Error::InvalidLength {
69                expected,
70                actual: real.len(),
71            });
72        }
73        if imag.len() != expected {
74            return Err(Error::InvalidLength {
75                expected,
76                actual: imag.len(),
77            });
78        }
79
80        let split = ffi::DSPSplitComplex {
81            realp: real.as_mut_ptr(),
82            imagp: imag.as_mut_ptr(),
83        };
84        // SAFETY: The split-complex buffers are valid for `expected` elements.
85        unsafe { ffi::vDSP_fft_zip(self.ptr, &split, 1, log2n, direction) };
86        Ok(())
87    }
88}
89
90/// Owned `vDSP_biquad_Setup` handle.
91pub struct BiquadSetup {
92    ptr: ffi::vDSP_biquad_Setup,
93}
94
95unsafe impl Send for BiquadSetup {}
96unsafe impl Sync for BiquadSetup {}
97
98impl Drop for BiquadSetup {
99    fn drop(&mut self) {
100        if !self.ptr.is_null() {
101            // SAFETY: `ptr` was returned by `vDSP_biquad_CreateSetup` and is owned by this wrapper.
102            unsafe { ffi::vDSP_biquad_DestroySetup(self.ptr) };
103            self.ptr = ptr::null_mut();
104        }
105    }
106}
107
108impl BiquadSetup {
109    #[must_use]
110    pub fn new(coefficients: &[f64]) -> Option<Self> {
111        if coefficients.is_empty() || coefficients.len() % 5 != 0 {
112            return None;
113        }
114
115        let sections = coefficients.len() / 5;
116        // SAFETY: `coefficients` is valid for `sections * 5` entries.
117        let ptr = unsafe { ffi::vDSP_biquad_CreateSetup(coefficients.as_ptr(), sections) };
118        if ptr.is_null() {
119            None
120        } else {
121            Some(Self { ptr })
122        }
123    }
124
125    pub fn apply(&self, delay: &mut [f32], input: &[f32], output: &mut [f32]) -> Result<()> {
126        if input.len() != output.len() {
127            return Err(Error::InvalidLength {
128                expected: input.len(),
129                actual: output.len(),
130            });
131        }
132
133        // SAFETY: The slices are valid for the provided strides and element count.
134        unsafe {
135            ffi::vDSP_biquad(
136                self.ptr,
137                delay.as_mut_ptr(),
138                input.as_ptr(),
139                1,
140                output.as_mut_ptr(),
141                1,
142                input.len(),
143            );
144        }
145        Ok(())
146    }
147}
148
149fn binary_vector_op(
150    a: &[f32],
151    b: &[f32],
152    f: unsafe extern "C" fn(*const f32, isize, *const f32, isize, *mut f32, isize, usize),
153) -> Result<Vec<f32>> {
154    if a.len() != b.len() {
155        return Err(Error::InvalidLength {
156            expected: a.len(),
157            actual: b.len(),
158        });
159    }
160
161    let mut out = vec![0.0_f32; a.len()];
162    // SAFETY: All slices are valid for `a.len()` contiguous `f32` elements.
163    unsafe { f(a.as_ptr(), 1, b.as_ptr(), 1, out.as_mut_ptr(), 1, a.len()) };
164    Ok(out)
165}
166
167pub fn add_f32(a: &[f32], b: &[f32]) -> Result<Vec<f32>> {
168    binary_vector_op(a, b, ffi::vDSP_vadd)
169}
170
171pub fn sub_f32(a: &[f32], b: &[f32]) -> Result<Vec<f32>> {
172    if a.len() != b.len() {
173        return Err(Error::InvalidLength {
174            expected: a.len(),
175            actual: b.len(),
176        });
177    }
178
179    let mut out = vec![0.0_f32; a.len()];
180    // SAFETY: `vDSP_vsub` computes `a - b` with `b` and `a` intentionally swapped in the argument list.
181    unsafe { ffi::vDSP_vsub(b.as_ptr(), 1, a.as_ptr(), 1, out.as_mut_ptr(), 1, a.len()) };
182    Ok(out)
183}
184
185pub fn dot_f32(a: &[f32], b: &[f32]) -> Result<f32> {
186    if a.len() != b.len() {
187        return Err(Error::InvalidLength {
188            expected: a.len(),
189            actual: b.len(),
190        });
191    }
192
193    let mut out = 0.0_f32;
194    // SAFETY: The slices are valid for `a.len()` contiguous `f32` elements.
195    unsafe { ffi::vDSP_dotpr(a.as_ptr(), 1, b.as_ptr(), 1, &mut out, a.len()) };
196    Ok(out)
197}
198
199fn reduce_f32(
200    values: &[f32],
201    f: unsafe extern "C" fn(*const f32, isize, *mut f32, usize),
202) -> Result<f32> {
203    if values.is_empty() {
204        return Err(Error::InvalidLength {
205            expected: 1,
206            actual: 0,
207        });
208    }
209
210    let mut out = 0.0_f32;
211    // SAFETY: The slice is valid for `values.len()` contiguous `f32` elements.
212    unsafe { f(values.as_ptr(), 1, &mut out, values.len()) };
213    Ok(out)
214}
215
216pub fn max_f32(values: &[f32]) -> Result<f32> {
217    reduce_f32(values, ffi::vDSP_maxv)
218}
219
220pub fn min_f32(values: &[f32]) -> Result<f32> {
221    reduce_f32(values, ffi::vDSP_minv)
222}
223
224pub fn mean_f32(values: &[f32]) -> Result<f32> {
225    reduce_f32(values, ffi::vDSP_meanv)
226}
227
228pub fn sum_f32(values: &[f32]) -> Result<f32> {
229    reduce_f32(values, ffi::vDSP_sve)
230}
231
232#[must_use]
233pub fn hamming_window(length: usize, flags: i32) -> Vec<f32> {
234    let mut out = vec![0.0_f32; length];
235    if length > 0 {
236        // SAFETY: `out` is valid for `length` contiguous `f32` values.
237        unsafe { ffi::vDSP_hamm_window(out.as_mut_ptr(), length, flags) };
238    }
239    out
240}
241
242#[must_use]
243pub fn blackman_window(length: usize, flags: i32) -> Vec<f32> {
244    let mut out = vec![0.0_f32; length];
245    if length > 0 {
246        // SAFETY: `out` is valid for `length` contiguous `f32` values.
247        unsafe { ffi::vDSP_blkman_window(out.as_mut_ptr(), length, flags) };
248    }
249    out
250}