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}