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}