fmod/studio/
bus.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::{
8    ffi::{c_float, c_uint},
9    mem::MaybeUninit,
10};
11
12use fmod_sys::*;
13use lanyard::Utf8CString;
14
15use crate::{core::ChannelGroup, Guid};
16
17use super::{MemoryUsage, StopMode};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20#[repr(transparent)] // so we can transmute between types
21pub struct Bus {
22    pub(crate) inner: *mut FMOD_STUDIO_BUS,
23}
24
25unsafe impl Send for Bus {}
26unsafe impl Sync for Bus {}
27
28impl From<*mut FMOD_STUDIO_BUS> for Bus {
29    fn from(value: *mut FMOD_STUDIO_BUS) -> Self {
30        Bus { inner: value }
31    }
32}
33
34impl From<Bus> for *mut FMOD_STUDIO_BUS {
35    fn from(value: Bus) -> Self {
36        value.inner
37    }
38}
39
40impl Bus {
41    /// Sets the pause state.
42    ///
43    /// This function allows pausing/unpausing of all audio routed into the bus.
44    ///
45    /// An individual pause state is kept for each bus.
46    /// Pausing a bus will override the pause state of its inputs (meaning they return true from [`Bus::get_paused`]), while unpausing a bus will cause its inputs to obey their individual pause state.
47    /// The pause state is processed in the Studio system update, so [`Bus::get_paused`] will return the state as determined by the last update.
48    pub fn set_paused(&self, paused: bool) -> Result<()> {
49        unsafe { FMOD_Studio_Bus_SetPaused(self.inner, paused.into()).to_result() }
50    }
51
52    /// Retrieves the pause state.
53    pub fn get_paused(&self) -> Result<bool> {
54        let mut paused = FMOD_BOOL::FALSE;
55        unsafe {
56            FMOD_Studio_Bus_GetPaused(self.inner, &mut paused).to_result()?;
57        }
58        Ok(paused.into())
59    }
60
61    /// Stops all event instances that are routed into the bus.
62    pub fn stop_all_events(&self, stop_mode: StopMode) -> Result<()> {
63        unsafe { FMOD_Studio_Bus_StopAllEvents(self.inner, stop_mode.into()).to_result() }
64    }
65}
66
67impl Bus {
68    /// Sets the volume level.
69    ///          
70    /// This volume is applied as a scaling factor to the volume level set in FMOD Studio.
71    pub fn set_volume(&self, volume: c_float) -> Result<()> {
72        unsafe { FMOD_Studio_Bus_SetVolume(self.inner, volume).to_result() }
73    }
74
75    /// Retrieves the volume level.
76    ///
77    /// The second tuple field is calculated by combining the volume set via [`Bus::set_volume`] with the bus's default volume and any snapshots or [`super::Vca`]s that affect the bus.
78    /// Volume changes are processed in the Studio system update, so second field will be the value calculated by the last update.
79    pub fn get_volume(&self) -> Result<(c_float, c_float)> {
80        let mut volume = 0.0;
81        let mut final_volume = 0.0;
82        unsafe {
83            FMOD_Studio_Bus_GetVolume(self.inner, &mut volume, &mut final_volume).to_result()?;
84        }
85        Ok((volume, final_volume))
86    }
87
88    /// Sets the mute state.
89    ///
90    /// Mute is an additional control for volume, the effect of which is equivalent to setting the volume to zero.
91    ///
92    /// An individual mute state is kept for each bus.
93    /// Muting a bus will override the mute state of its inputs (meaning they return true from [`Bus::get_mute`]), while unmuting a bus will cause its inputs to obey their individual mute state.
94    /// The mute state is processed in the Studio system update, so [`Bus::get_mute`] will return the state as determined by the last update.
95    pub fn set_mute(&self, mute: bool) -> Result<()> {
96        unsafe { FMOD_Studio_Bus_SetMute(self.inner, mute.into()).to_result() }
97    }
98
99    /// Retrieves the mute state.
100    pub fn get_mute(&self) -> Result<bool> {
101        let mut mute = FMOD_BOOL::FALSE;
102        unsafe {
103            FMOD_Studio_Bus_GetMute(self.inner, &mut mute).to_result()?;
104        }
105        Ok(mute.into())
106    }
107
108    /// Sets the port index to use when attaching to an output port.
109    ///
110    /// When a bus which is an output port is instantiated it will be connected to an output port based on the port type set in Studio.
111    /// For some port types a platform specific port index is required to connect to the correct output port.
112    /// For example, if the output port type is a speaker in a controller then a platform specific port index may be required to specify which controller the bus is to attach to.
113    /// In such a case call this function passing the platform specific port index.
114    ///
115    /// There is no need to call this function for port types which do not require an index.
116    ///
117    /// This function may be called at any time after a bank containing the bus has been loaded.
118    pub fn set_port_index(&self, index: FMOD_PORT_INDEX) -> Result<()> {
119        unsafe { FMOD_Studio_Bus_SetPortIndex(self.inner, index).to_result() }
120    }
121
122    /// Retrieves the port index assigned to the bus.
123    pub fn get_port_index(&self) -> Result<FMOD_PORT_INDEX> {
124        let mut index = 0;
125        unsafe {
126            FMOD_Studio_Bus_GetPortIndex(self.inner, &mut index).to_result()?;
127        }
128        Ok(index)
129    }
130}
131
132impl Bus {
133    /// Retrieves the core [`ChannelGroup`].
134    ///
135    /// By default the [`ChannelGroup`] will only exist when it is needed; see Signal Paths in the FMOD documentation for details.
136    /// If the [`ChannelGroup`] does not exist, this function will return [`FMOD_RESULT::FMOD_ERR_STUDIO_NOT_LOADED`].
137    pub fn get_channel_group(&self) -> Result<ChannelGroup> {
138        let mut channel_group = std::ptr::null_mut();
139        unsafe {
140            FMOD_Studio_Bus_GetChannelGroup(self.inner, &mut channel_group).to_result()?;
141        }
142        Ok(channel_group.into())
143    }
144
145    /// Locks the core [`ChannelGroup`].
146    ///
147    /// This function forces the system to create the [`ChannelGroup`] and keep it available until [`Bus::unlock_channel_group`] is called.
148    /// See Signal Paths in the FMOD documentation for details.
149    ///
150    /// The [`ChannelGroup`] may not be available immediately after calling this function.
151    /// When Studio has been initialized in asynchronous mode, the [`ChannelGroup`] will not be created until the command has been executed in the async thread.
152    /// When Studio has been initialized with [`super::InitFlags::SYNCHRONOUS_UPDATE`], the [`ChannelGroup`] will be created in the next [`super::System::update`] call.
153    ///
154    /// You can call [`super::System::flush_commands`] to ensure the [`ChannelGroup`] has been created.
155    /// Alternatively you can keep trying to obtain the [`ChannelGroup`] via [`Bus::get_channel_group`] until it is ready.
156    pub fn lock_channel_group(&self) -> Result<()> {
157        unsafe { FMOD_Studio_Bus_LockChannelGroup(self.inner).to_result() }
158    }
159
160    /// Unlocks the core [`ChannelGroup`].
161    ///
162    /// This function allows the system to destroy the [`ChannelGroup`] when it is not needed.
163    /// See Signal Paths in the FMOD documentation for details.
164    pub fn unlock_channel_group(&self) -> Result<()> {
165        unsafe { FMOD_Studio_Bus_UnlockChannelGroup(self.inner).to_result() }
166    }
167}
168
169impl Bus {
170    /// Retrieves the bus CPU usage data.
171    ///
172    /// The first tuple field is the CPU time spent processing the events of this bus, in microseconds.
173    ///
174    /// The second tuple field is the CPU time spent processing the events and all input buses of this bus, in microseconds.
175    ///
176    /// [`crate::InitFlags::PROFILE_ENABLE`] with [`crate::SystemBuilder::build`] is required to call this function.
177    pub fn get_cpu_usage(&self) -> Result<(c_uint, c_uint)> {
178        let mut exclusive = 0;
179        let mut inclusive = 0;
180        unsafe {
181            FMOD_Studio_Bus_GetCPUUsage(self.inner, &mut exclusive, &mut inclusive).to_result()?;
182        }
183        Ok((exclusive, inclusive))
184    }
185
186    /// Retrieves memory usage statistics.
187    ///
188    /// Memory usage statistics are only available in logging builds, in release builds the return value will contain zero for all values after calling this function.
189    pub fn get_memory_usage(&self) -> Result<MemoryUsage> {
190        let mut memory_usage = MaybeUninit::zeroed();
191        unsafe {
192            FMOD_Studio_Bus_GetMemoryUsage(self.inner, memory_usage.as_mut_ptr()).to_result()?;
193
194            let memory_usage = memory_usage.assume_init().into();
195            Ok(memory_usage)
196        }
197    }
198}
199
200impl Bus {
201    /// Retrieves the GUID.
202    pub fn get_id(&self) -> Result<Guid> {
203        let mut guid = MaybeUninit::zeroed();
204        unsafe {
205            FMOD_Studio_Bus_GetID(self.inner, guid.as_mut_ptr()).to_result()?;
206
207            let guid = guid.assume_init().into();
208
209            Ok(guid)
210        }
211    }
212
213    /// Retrieves the path.
214    ///
215    /// The strings bank must be loaded prior to calling this function, otherwise [`FMOD_RESULT::FMOD_ERR_EVENT_NOTFOUND`] is returned.
216    // TODO: convert into possible macro for the sake of reusing code
217    pub fn get_path(&self) -> Result<Utf8CString> {
218        let mut string_len = 0;
219
220        // retrieve the length of the string.
221        // this includes the null terminator, so we don't need to account for that.
222        unsafe {
223            let error =
224                FMOD_Studio_Bus_GetPath(self.inner, std::ptr::null_mut(), 0, &mut string_len)
225                    .to_error();
226
227            // we expect the error to be fmod_err_truncated.
228            // if it isn't, we return the error.
229            match error {
230                Some(error) if error != FMOD_RESULT::FMOD_ERR_TRUNCATED => return Err(error),
231                _ => {}
232            }
233        };
234
235        let mut path = vec![0u8; string_len as usize];
236        let mut expected_string_len = 0;
237
238        unsafe {
239            FMOD_Studio_Bus_GetPath(
240                self.inner,
241                // u8 and i8 have the same layout, so this is ok
242                path.as_mut_ptr().cast(),
243                string_len,
244                &mut expected_string_len,
245            )
246            .to_result()?;
247
248            debug_assert_eq!(string_len, expected_string_len);
249
250            // all public fmod apis return UTF-8 strings. this should be safe.
251            // if i turn out to be wrong, perhaps we should add extra error types?
252            let path = Utf8CString::from_utf8_with_nul_unchecked(path);
253
254            Ok(path)
255        }
256    }
257
258    /// Checks that the [`Bus`] reference is valid.
259    pub fn is_valid(&self) -> bool {
260        unsafe { FMOD_Studio_Bus_IsValid(self.inner).into() }
261    }
262}