fmod/core/dsp/
data_parameters.rs

1use std::{
2    ffi::{c_float, c_int},
3    mem::MaybeUninit,
4};
5
6use fmod_sys::*;
7
8use crate::{Attributes3D, Dsp, DspParameterDataType, ReadableParameter, WritableParameter};
9use crate::{Error, Result};
10
11#[cfg(doc)]
12use crate::{System, studio};
13
14fn parameter_is(dsp_parameter_desc: &FMOD_DSP_PARAMETER_DESC, kind: DspParameterDataType) -> bool {
15    if dsp_parameter_desc.type_ != FMOD_DSP_PARAMETER_TYPE_DATA {
16        return false;
17    }
18    unsafe { dsp_parameter_desc.__bindgen_anon_1.datadesc }.datatype == kind.into()
19}
20
21/// Overall gain parameter data structure.
22///
23/// This parameter is read by the system to determine the effect's gain for voice virtualization.
24#[derive(Debug, Clone, Copy, PartialEq)]
25#[repr(C)]
26pub struct OverallGain {
27    /// Overall linear gain of the effect on the direct signal path.
28    pub linear_gain: c_float,
29    /// Additive gain for parallel signal paths.
30    pub linear_gain_additive: c_float,
31}
32
33// Safety: we validate the data type matches what we expect.
34impl ReadableParameter for OverallGain {
35    fn get_parameter(dsp: Dsp, index: c_int) -> Result<Self> {
36        let desc = dsp.get_raw_parameter_info(index)?;
37        if !parameter_is(&desc, DspParameterDataType::OverAlign) {
38            return Err(Error::InvalidParam);
39        }
40        let mut this = MaybeUninit::uninit();
41        // Safety: we already validated that this is the right data type, so this is safe.
42        unsafe { dsp.get_raw_parameter_data(&mut this, index)? };
43        Ok(unsafe { this.assume_init() })
44    }
45
46    fn get_parameter_string(dsp: Dsp, index: c_int) -> Result<lanyard::Utf8CString> {
47        dsp.get_data_parameter_string(index)
48    }
49}
50
51/// 3D attributes data structure.
52///
53/// The [`studio::System`] sets this parameter automatically when an [`studio::EventInstance`] position changes.
54/// However, if you are using the core [`System`] and not the [`studio::System`], you must set this DSP parameter explicitly.
55///
56/// Attributes must use a coordinate system with the positive Y axis being up and the positive X axis being right.
57/// The FMOD Engine converts passed-in coordinates to left-handed for the plug-in if the system was initialized with the [`FMOD_INIT_3D_RIGHTHANDED`] flag.
58///
59/// When using a listener attenuation position,
60/// the direction of the relative attributes will be relative to the listener position and the length will be the distance to the attenuation position.
61#[derive(Debug, Clone, Copy, PartialEq)]
62#[repr(C)]
63pub struct DspAttributes3D {
64    /// Position of the sound relative to the listener.
65    pub relative: Attributes3D,
66    /// Position of the sound in world coordinates.
67    pub absolute: Attributes3D,
68}
69
70impl ReadableParameter for DspAttributes3D {
71    fn get_parameter(dsp: Dsp, index: c_int) -> Result<Self> {
72        let desc = dsp.get_raw_parameter_info(index)?;
73        if !parameter_is(&desc, DspParameterDataType::Attributes3D) {
74            return Err(Error::InvalidParam);
75        }
76        let mut this = MaybeUninit::uninit();
77        // Safety: we already validated that this is the right data type, so this is safe.
78        unsafe { dsp.get_raw_parameter_data(&mut this, index)? };
79        Ok(unsafe { this.assume_init() })
80    }
81
82    fn get_parameter_string(dsp: Dsp, index: c_int) -> Result<lanyard::Utf8CString> {
83        dsp.get_data_parameter_string(index)
84    }
85}
86
87impl WritableParameter for DspAttributes3D {
88    fn set_parameter(self, dsp: Dsp, index: c_int) -> Result<()> {
89        let desc = dsp.get_raw_parameter_info(index)?;
90        if !parameter_is(&desc, DspParameterDataType::Attributes3D) {
91            return Err(Error::InvalidParam);
92        }
93        // Safety: we already validated that this is the right data type, so this is safe.
94        unsafe { dsp.set_raw_parameter_data(&self, index) }
95    }
96}
97
98/// Side chain parameter data structure.
99#[derive(Debug, Clone, Copy, PartialEq)]
100#[repr(C)]
101pub struct Sidechain {
102    /// Whether sidechains are enabled.
103    pub enable: bool,
104}
105
106impl ReadableParameter for Sidechain {
107    fn get_parameter(dsp: Dsp, index: c_int) -> Result<Self> {
108        let desc = dsp.get_raw_parameter_info(index)?;
109        if !parameter_is(&desc, DspParameterDataType::Attributes3D) {
110            return Err(Error::InvalidParam);
111        }
112        let mut raw = MaybeUninit::<FMOD_DSP_PARAMETER_SIDECHAIN>::uninit();
113        // Safety: we already validated that this is the right data type, so this is safe.
114        unsafe { dsp.get_raw_parameter_data(&mut raw, index)? };
115        let raw = unsafe { raw.assume_init() };
116        Ok(Self {
117            enable: raw.sidechainenable.into(),
118        })
119    }
120
121    fn get_parameter_string(dsp: Dsp, index: c_int) -> Result<lanyard::Utf8CString> {
122        dsp.get_data_parameter_string(index)
123    }
124}
125
126impl WritableParameter for Sidechain {
127    fn set_parameter(self, dsp: Dsp, index: c_int) -> Result<()> {
128        let desc = dsp.get_raw_parameter_info(index)?;
129        if !parameter_is(&desc, DspParameterDataType::Attributes3D) {
130            return Err(Error::InvalidParam);
131        }
132        let raw = FMOD_DSP_PARAMETER_SIDECHAIN {
133            sidechainenable: self.enable.into(),
134        };
135        // Safety: we already validated that this is the right data type, so this is safe.
136        unsafe { dsp.set_raw_parameter_data(&raw, index) }
137    }
138}
139
140/// FFT parameter data structure.
141#[derive(Debug, Clone, PartialEq)]
142pub struct Fft {
143    channels: usize,
144    spectrum_size: usize,
145    data: Box<[c_float]>,
146}
147
148impl Fft {
149    /// Number of channels in spectrum.
150    pub fn channels(&self) -> usize {
151        self.channels
152    }
153
154    /// Number of entries in this spectrum window.
155    ///
156    /// Divide this by the output rate to get the hz per entry.
157    pub fn spectrum_size(&self) -> usize {
158        self.spectrum_size
159    }
160
161    /// Channel spectrum data.
162    ///
163    /// Values inside the float buffer are typically between 0 and 1.0.
164    /// Each top level array represents one PCM channel of data.
165    ///
166    /// Address data as `spectrum(channel)[bin]`. A bin is 1 fft window entry.
167    ///
168    /// Only read/display half of the buffer typically for analysis as the 2nd half is usually the same data reversed due to the nature of the way FFT works.
169    pub fn spectrum(&self, channel: usize) -> &[c_float] {
170        let offset = self.spectrum_size * channel;
171        &self.data[offset..offset + self.spectrum_size]
172    }
173
174    /// Per channel spectrum arrays.
175    pub fn data(&self) -> &[c_float] {
176        &self.data
177    }
178}
179
180// So glad this is read only because this would be AWFUL to implement writing for
181impl ReadableParameter for Fft {
182    fn get_parameter(dsp: Dsp, index: c_int) -> Result<Self> {
183        let desc = dsp.get_raw_parameter_info(index)?;
184        if !parameter_is(&desc, DspParameterDataType::Attributes3D) {
185            return Err(Error::InvalidParam);
186        }
187        let mut raw = MaybeUninit::<FMOD_DSP_PARAMETER_FFT>::uninit();
188        // Safety: we already validated that this is the right data type, so this is safe.
189        unsafe { dsp.get_raw_parameter_data(&mut raw, index)? };
190        let raw = unsafe { raw.assume_init() };
191
192        let mut data = Vec::with_capacity(raw.numchannels as _);
193        for i in 0..raw.numchannels as _ {
194            let ptr = raw.spectrum[i];
195            let slice = unsafe { std::slice::from_raw_parts(ptr, raw.length as _) };
196            data.extend_from_slice(slice);
197        }
198        Ok(Self {
199            channels: raw.numchannels as _,
200            spectrum_size: raw.length as _,
201            data: data.into_boxed_slice(),
202        })
203    }
204
205    fn get_parameter_string(dsp: Dsp, index: c_int) -> Result<lanyard::Utf8CString> {
206        dsp.get_data_parameter_string(index)
207    }
208}
209
210/// 3D attributes data structure for multiple listeners.
211///
212/// The [`studio::System`] sets this parameter automatically when an [`studio::EventInstance`] position changes.
213/// However, if you are using the core API's [`System`] and not the [`studio::System`],
214/// you must set this DSP parameter explicitly.
215///
216/// Attributes must use a coordinate system with the positive Y axis being up and the positive X axis being right.
217/// The FMOD Engine converts passed in coordinates to left-handed for the plug-in if the System was initialized with the [`FMOD_INIT_3D_RIGHTHANDED`] flag.
218///
219/// When using a listener attenuation position,
220/// the direction of the relative attributes will be relative to the listener position and the length will be the distance to the attenuation position.
221#[derive(Debug, Clone, Copy, PartialEq)]
222#[repr(C)]
223pub struct Attributes3DMulti {
224    listener_count: c_int,
225    relative: [Attributes3D; FMOD_MAX_LISTENERS as usize],
226    weight: [c_float; FMOD_MAX_LISTENERS as usize],
227    /// Position of the sound in world coordinates.
228    pub absolute: Attributes3D,
229}
230
231impl Attributes3DMulti {
232    /// Create a new [`Attributes3DMulti`].
233    ///
234    /// Only values from <code>data[..[FMOD_MAX_LISTENERS]]</code> will be read
235    pub fn new(data: &[(Attributes3D, c_float)], absolute: Attributes3D) -> Self {
236        let relative = std::array::from_fn(|i| data.get(i).map(|d| d.0).unwrap_or_default());
237        let weight = std::array::from_fn(|i| data.get(i).map(|d| d.1).unwrap_or_default());
238        Self {
239            listener_count: data.len() as _,
240            relative,
241            weight,
242            absolute,
243        }
244    }
245
246    /// Position of the sound relative to the listeners.
247    pub fn relative(&self) -> &[Attributes3D] {
248        &self.relative[..self.listener_count as _]
249    }
250
251    /// Position of the sound relative to the listeners.
252    pub fn relative_mut(&mut self) -> &mut [Attributes3D] {
253        &mut self.relative[..self.listener_count as _]
254    }
255
256    /// Weighting of the listeners where 0 means listener has no contribution and 1 means full contribution.
257    pub fn weight(&self) -> &[c_float] {
258        &self.weight[..self.listener_count as _]
259    }
260
261    /// Weighting of the listeners where 0 means listener has no contribution and 1 means full contribution.
262    pub fn weight_mut(&mut self) -> &mut [c_float] {
263        &mut self.weight[..self.listener_count as _]
264    }
265
266    /// Number of listeners.
267    pub fn listener_count(&self) -> usize {
268        self.listener_count as _
269    }
270}
271
272impl ReadableParameter for Attributes3DMulti {
273    fn get_parameter(dsp: Dsp, index: c_int) -> Result<Self> {
274        let desc = dsp.get_raw_parameter_info(index)?;
275        if !parameter_is(&desc, DspParameterDataType::Attributes3DMulti) {
276            return Err(Error::InvalidParam);
277        }
278        let mut raw = MaybeUninit::uninit();
279        // Safety: we already validated that this is the right data type, so this is safe.
280        unsafe { dsp.get_raw_parameter_data(&mut raw, index)? };
281        Ok(unsafe { raw.assume_init() })
282    }
283
284    fn get_parameter_string(dsp: Dsp, index: c_int) -> Result<lanyard::Utf8CString> {
285        dsp.get_data_parameter_string(index)
286    }
287}
288
289impl WritableParameter for Attributes3DMulti {
290    fn set_parameter(self, dsp: Dsp, index: c_int) -> Result<()> {
291        let desc = dsp.get_raw_parameter_info(index)?;
292        if !parameter_is(&desc, DspParameterDataType::Attributes3DMulti) {
293            return Err(Error::InvalidParam);
294        }
295        // Safety: we already validated that this is the right data type, so this is safe.
296        unsafe { dsp.set_raw_parameter_data(&self, index) }
297    }
298}
299/// Attenuation range parameter data structure.
300///
301/// The [`studio::System`] will set this parameter automatically if an [`studio::EventInstance`] min or max distance changes.
302#[derive(Debug, Clone, Copy, PartialEq)]
303#[repr(C)]
304pub struct AttenuationRange {
305    /// Minimum distance for attenuation.
306    pub min: c_float,
307    /// Maximum distance for attenuation.
308    pub max: c_float,
309}
310
311impl ReadableParameter for AttenuationRange {
312    fn get_parameter(dsp: Dsp, index: c_int) -> Result<Self> {
313        let desc = dsp.get_raw_parameter_info(index)?;
314        if !parameter_is(&desc, DspParameterDataType::AttenuationRange) {
315            return Err(Error::InvalidParam);
316        }
317        let mut raw = MaybeUninit::uninit();
318        // Safety: we already validated that this is the right data type, so this is safe.
319        unsafe { dsp.get_raw_parameter_data(&mut raw, index)? };
320        Ok(unsafe { raw.assume_init() })
321    }
322
323    fn get_parameter_string(dsp: Dsp, index: c_int) -> Result<lanyard::Utf8CString> {
324        dsp.get_data_parameter_string(index)
325    }
326}
327
328/// Dynamic response data structure.
329#[cfg(fmod_2_3)]
330#[derive(Debug, Clone, Copy, PartialEq)]
331#[repr(C)]
332pub struct DynamicResponse {
333    /// The number of channels recorded in the rms array.
334    pub channel_count: c_int,
335    /// The RMS (Root Mean Square) averaged gain factor applied per channel for the last processed block of audio.
336    pub rms: [c_float; 32],
337}
338
339#[cfg(fmod_2_3)]
340impl ReadableParameter for DynamicResponse {
341    fn get_parameter(dsp: Dsp, index: c_int) -> Result<Self> {
342        let desc = dsp.get_raw_parameter_info(index)?;
343        if !parameter_is(&desc, DspParameterDataType::DynamicResponse) {
344            return Err(Error::InvalidParam);
345        }
346        let mut raw = MaybeUninit::uninit();
347        // Safety: we already validated that this is the right data type, so this is safe.
348        unsafe { dsp.get_raw_parameter_data(&mut raw, index)? };
349        Ok(unsafe { raw.assume_init() })
350    }
351
352    fn get_parameter_string(dsp: Dsp, index: c_int) -> Result<lanyard::Utf8CString> {
353        dsp.get_data_parameter_string(index)
354    }
355}