fmod/studio/system/
parameter.rs

1// Copyright (c) 2024 Lily Lyons
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
7use fmod_sys::*;
8use lanyard::{Utf8CStr, Utf8CString};
9use std::{
10    ffi::{c_float, c_int},
11    mem::MaybeUninit,
12};
13
14use crate::studio::{ParameterDescription, ParameterID, System};
15
16impl System {
17    /// Retrieves a global parameter value by unique identifier.
18    ///
19    /// The second tuple field is the final value of the parameter after applying adjustments due to automation, modulation, seek speed, and parameter velocity to value.
20    /// This is calculated asynchronously when the Studio system updates.
21    pub fn get_parameter_by_id(&self, id: ParameterID) -> Result<(c_float, c_float)> {
22        let mut value = 0.0;
23        let mut final_value = 0.0;
24
25        unsafe {
26            FMOD_Studio_System_GetParameterByID(
27                self.inner,
28                id.into(),
29                &mut value,
30                &mut final_value,
31            )
32            .to_result()?;
33        }
34
35        Ok((value, final_value))
36    }
37
38    /// Sets a global parameter value by unique identifier.
39    pub fn set_parameter_by_id(
40        &self,
41        id: ParameterID,
42        value: c_float,
43        ignore_seek_speed: bool,
44    ) -> Result<()> {
45        unsafe {
46            FMOD_Studio_System_SetParameterByID(
47                self.inner,
48                id.into(),
49                value,
50                ignore_seek_speed.into(),
51            )
52            .to_result()
53        }
54    }
55
56    /// Sets a global parameter value by unique identifier, looking up the value label.
57    ///
58    /// If the specified label is not found, [`FMOD_RESULT::FMOD_ERR_EVENT_NOTFOUND`] is returned.
59    /// This lookup is case sensitive.
60    pub fn set_parameter_by_id_with_label(
61        &self,
62        id: ParameterID,
63        label: &Utf8CStr,
64        ignore_seek_speed: bool,
65    ) -> Result<()> {
66        unsafe {
67            FMOD_Studio_System_SetParameterByIDWithLabel(
68                self.inner,
69                id.into(),
70                label.as_ptr(),
71                ignore_seek_speed.into(),
72            )
73            .to_result()
74        }
75    }
76
77    /// Sets multiple global parameter values by unique identifier.
78    ///
79    /// If any ID is set to all zeroes then the corresponding value will be ignored.
80    // TODO iterator version?
81    pub fn set_parameters_by_ids(
82        &self,
83        ids: &[ParameterID], // TODO fmod says that the size of this must range from 1-32. do we need to enforce this?
84        values: &mut [c_float], // TODO is this &mut correct? does fmod perform any writes?
85        ignore_seek_speed: bool,
86    ) -> Result<()> {
87        // TODO don't panic, return result
88        assert_eq!(ids.len(), values.len());
89
90        unsafe {
91            FMOD_Studio_System_SetParametersByIDs(
92                self.inner,
93                ids.as_ptr().cast(),
94                values.as_mut_ptr(),
95                ids.len() as c_int,
96                ignore_seek_speed.into(),
97            )
98            .to_result()
99        }
100    }
101
102    /// Retrieves a global parameter value by name.
103    ///
104    /// The second tuple field is the final value of the parameter after applying adjustments due to automation, modulation, seek speed, and parameter velocity to value.
105    /// This is calculated asynchronously when the Studio system updates.
106    pub fn get_parameter_by_name(&self, name: &Utf8CStr) -> Result<(c_float, c_float)> {
107        let mut value = 0.0;
108        let mut final_value = 0.0;
109
110        unsafe {
111            FMOD_Studio_System_GetParameterByName(
112                self.inner,
113                name.as_ptr(),
114                &mut value,
115                &mut final_value,
116            )
117            .to_result()?;
118        }
119
120        Ok((value, final_value))
121    }
122
123    /// Sets a global parameter value by name.
124    pub fn set_parameter_by_name(
125        &self,
126        name: &Utf8CStr,
127        value: c_float,
128        ignore_seek_speed: bool,
129    ) -> Result<()> {
130        unsafe {
131            FMOD_Studio_System_SetParameterByName(
132                self.inner,
133                name.as_ptr(),
134                value,
135                ignore_seek_speed.into(),
136            )
137            .to_result()
138        }
139    }
140
141    /// Sets a global parameter value by name, looking up the value label.
142    ///
143    /// If the specified label is not found, [`FMOD_RESULT::FMOD_ERR_EVENT_NOTFOUND`] is returned. This lookup is case sensitive.
144    pub fn set_parameter_by_name_with_label(
145        &self,
146        name: &Utf8CStr,
147        label: &Utf8CStr,
148        ignore_seek_speed: bool,
149    ) -> Result<()> {
150        unsafe {
151            FMOD_Studio_System_SetParameterByNameWithLabel(
152                self.inner,
153                name.as_ptr(),
154                label.as_ptr(),
155                ignore_seek_speed.into(),
156            )
157            .to_result()
158        }
159    }
160
161    /// Retrieves a global parameter by name or path.
162    ///
163    /// `name` can be the short name (such as `Wind`) or the full path (such as `parameter:/Ambience/Wind`).
164    /// Path lookups will only succeed if the strings bank has been loaded.
165    pub fn get_parameter_description_by_name(
166        &self,
167        name: &Utf8CStr,
168    ) -> Result<ParameterDescription> {
169        let mut description = MaybeUninit::zeroed();
170        unsafe {
171            FMOD_Studio_System_GetParameterDescriptionByName(
172                self.inner,
173                name.as_ptr(),
174                description.as_mut_ptr(),
175            )
176            .to_result()?;
177
178            let description = ParameterDescription::from_ffi(description.assume_init());
179            Ok(description)
180        }
181    }
182
183    /// Retrieves a global parameter by ID.
184    pub fn get_parameter_description_by_id(&self, id: ParameterID) -> Result<ParameterDescription> {
185        let mut description = MaybeUninit::zeroed();
186        unsafe {
187            FMOD_Studio_System_GetParameterDescriptionByID(
188                self.inner,
189                id.into(),
190                description.as_mut_ptr(),
191            )
192            .to_result()?;
193
194            let description = ParameterDescription::from_ffi(description.assume_init());
195            Ok(description)
196        }
197    }
198
199    /// Retrieves the number of global parameters.
200    pub fn parameter_description_count(&self) -> Result<c_int> {
201        let mut count = 0;
202        unsafe {
203            FMOD_Studio_System_GetParameterDescriptionCount(self.inner, &mut count).to_result()?;
204        }
205        Ok(count)
206    }
207
208    /// Retrieves a list of global parameters.
209    pub fn get_parameter_description_list(&self) -> Result<Vec<ParameterDescription>> {
210        let expected_count = self.parameter_description_count()?;
211        let mut count = 0;
212        // FIXME: is the use of MaybeUninit necessary?
213        // it does imply intention though, which is ok.
214        let mut list = vec![MaybeUninit::zeroed(); expected_count as usize];
215
216        unsafe {
217            FMOD_Studio_System_GetParameterDescriptionList(
218                self.inner,
219                // bank is repr transparent and has the same layout as *mut FMOD_STUDIO_BANK, so this cast is ok
220                list.as_mut_ptr()
221                    .cast::<FMOD_STUDIO_PARAMETER_DESCRIPTION>(),
222                list.capacity() as c_int,
223                &mut count,
224            )
225            .to_result()?;
226
227            debug_assert_eq!(count, expected_count);
228
229            let list = list
230                .into_iter()
231                .map(|uninit| {
232                    let description = uninit.assume_init();
233                    ParameterDescription::from_ffi(description)
234                })
235                .collect();
236
237            Ok(list)
238        }
239    }
240
241    /// Retrieves a global parameter label by name or path.
242    ///
243    /// `name` can be the short name (such as `Wind`) or the full path (such as `parameter:/Ambience/Wind`).
244    /// Path lookups will only succeed if the strings bank has been loaded.
245    pub fn get_parameter_label_by_name(
246        &self,
247        name: &Utf8CStr,
248        label_index: c_int,
249    ) -> Result<Utf8CString> {
250        let mut string_len = 0;
251
252        // retrieve the length of the string.
253        // this includes the null terminator, so we don't need to account for that.
254        unsafe {
255            let error = FMOD_Studio_System_GetParameterLabelByName(
256                self.inner,
257                name.as_ptr(),
258                label_index,
259                std::ptr::null_mut(),
260                0,
261                &mut string_len,
262            )
263            .to_error();
264
265            // we expect the error to be fmod_err_truncated.
266            // if it isn't, we return the error.
267            match error {
268                Some(error) if error != FMOD_RESULT::FMOD_ERR_TRUNCATED => return Err(error),
269                _ => {}
270            }
271        };
272
273        let mut path = vec![0u8; string_len as usize];
274        let mut expected_string_len = 0;
275
276        unsafe {
277            FMOD_Studio_System_GetParameterLabelByName(
278                self.inner,
279                name.as_ptr(),
280                label_index,
281                // u8 and i8 have the same layout, so this is ok
282                path.as_mut_ptr().cast(),
283                string_len,
284                &mut expected_string_len,
285            )
286            .to_result()?;
287
288            debug_assert_eq!(string_len, expected_string_len);
289
290            // all public fmod apis return UTF-8 strings. this should be safe.
291            // if i turn out to be wrong, perhaps we should add extra error types?
292            let path = Utf8CString::from_utf8_with_nul_unchecked(path);
293
294            Ok(path)
295        }
296    }
297
298    /// Retrieves a global parameter label by ID.
299    pub fn get_parameter_label_by_id(
300        &self,
301        id: ParameterID,
302        label_index: c_int,
303    ) -> Result<Utf8CString> {
304        let mut string_len = 0;
305
306        // retrieve the length of the string.
307        // this includes the null terminator, so we don't need to account for that.
308        unsafe {
309            let error = FMOD_Studio_System_GetParameterLabelByID(
310                self.inner,
311                id.into(),
312                label_index,
313                std::ptr::null_mut(),
314                0,
315                &mut string_len,
316            )
317            .to_error();
318
319            // we expect the error to be fmod_err_truncated.
320            // if it isn't, we return the error.
321            match error {
322                Some(error) if error != FMOD_RESULT::FMOD_ERR_TRUNCATED => return Err(error),
323                _ => {}
324            }
325        };
326
327        let mut path = vec![0u8; string_len as usize];
328        let mut expected_string_len = 0;
329
330        unsafe {
331            FMOD_Studio_System_GetParameterLabelByID(
332                self.inner,
333                id.into(),
334                label_index,
335                // u8 and i8 have the same layout, so this is ok
336                path.as_mut_ptr().cast(),
337                string_len,
338                &mut expected_string_len,
339            )
340            .to_result()?;
341
342            debug_assert_eq!(string_len, expected_string_len);
343
344            // all public fmod apis return UTF-8 strings. this should be safe.
345            // if i turn out to be wrong, perhaps we should add extra error types?
346            let path = Utf8CString::from_utf8_with_nul_unchecked(path);
347
348            Ok(path)
349        }
350    }
351}