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}