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}