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