fmod/studio/vca.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 std::{ffi::c_float, mem::MaybeUninit};
8
9use fmod_sys::*;
10use lanyard::Utf8CString;
11
12use crate::Guid;
13
14/// Represents a global mixer VCA.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16#[repr(transparent)] // so we can transmute between types
17pub struct Vca {
18 pub(crate) inner: *mut FMOD_STUDIO_VCA,
19}
20
21unsafe impl Send for Vca {}
22unsafe impl Sync for Vca {}
23
24impl From<*mut FMOD_STUDIO_VCA> for Vca {
25 fn from(value: *mut FMOD_STUDIO_VCA) -> Self {
26 Vca { inner: value }
27 }
28}
29
30impl From<Vca> for *mut FMOD_STUDIO_VCA {
31 fn from(value: Vca) -> Self {
32 value.inner
33 }
34}
35
36impl Vca {
37 /// Sets the volume level.
38 ///
39 /// The VCA volume level is used to linearly modulate the levels of the buses and VCAs which it controls.
40 pub fn set_volume(&self, volume: c_float) -> Result<()> {
41 unsafe { FMOD_Studio_VCA_SetVolume(self.inner, volume).to_result() }
42 }
43
44 /// Retrieves the volume level.
45 ///
46 /// The final combined volume returned in the second tuple field combines the user value set using [`Vca::set_volume`] with the result of any automation or modulation applied to the VCA.
47 /// The final combined volume is calculated asynchronously when the Studio system updates.
48 pub fn get_volume(&self) -> Result<(c_float, c_float)> {
49 let mut volume = 0.0;
50 let mut final_volume = 0.0;
51 unsafe {
52 FMOD_Studio_VCA_GetVolume(self.inner, &mut volume, &mut final_volume).to_result()?;
53 }
54 Ok((volume, final_volume))
55 }
56}
57
58impl Vca {
59 /// Retrieves the GUID.
60 pub fn get_id(&self) -> Result<Guid> {
61 let mut guid = MaybeUninit::zeroed();
62 unsafe {
63 FMOD_Studio_VCA_GetID(self.inner, guid.as_mut_ptr()).to_result()?;
64
65 let guid = guid.assume_init().into();
66
67 Ok(guid)
68 }
69 }
70
71 /// Retrieves the path.
72 ///
73 /// The strings bank must be loaded prior to calling this function, otherwise [`FMOD_RESULT::FMOD_ERR_EVENT_NOTFOUND`] is returned.
74 // TODO: convert into possible macro for the sake of reusing code
75 pub fn get_path(&self) -> Result<Utf8CString> {
76 let mut string_len = 0;
77
78 // retrieve the length of the string.
79 // this includes the null terminator, so we don't need to account for that.
80 unsafe {
81 let error =
82 FMOD_Studio_VCA_GetPath(self.inner, std::ptr::null_mut(), 0, &mut string_len)
83 .to_error();
84
85 // we expect the error to be fmod_err_truncated.
86 // if it isn't, we return the error.
87 match error {
88 Some(error) if error != FMOD_RESULT::FMOD_ERR_TRUNCATED => return Err(error),
89 _ => {}
90 }
91 };
92
93 let mut path = vec![0u8; string_len as usize];
94 let mut expected_string_len = 0;
95
96 unsafe {
97 FMOD_Studio_VCA_GetPath(
98 self.inner,
99 // u8 and i8 have the same layout, so this is ok
100 path.as_mut_ptr().cast(),
101 string_len,
102 &mut expected_string_len,
103 )
104 .to_result()?;
105
106 debug_assert_eq!(string_len, expected_string_len);
107
108 // all public fmod apis return UTF-8 strings. this should be safe.
109 // if i turn out to be wrong, perhaps we should add extra error types?
110 let path = Utf8CString::from_utf8_with_nul_unchecked(path);
111
112 Ok(path)
113 }
114 }
115
116 /// Checks that the VCA reference is valid.
117 pub fn is_valid(&self) -> bool {
118 unsafe { FMOD_Studio_VCA_IsValid(self.inner).into() }
119 }
120}