1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
// Copyright (c) 2024 Lily Lyons
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use std::{
    ffi::{c_float, c_uint},
    mem::MaybeUninit,
};

use fmod_sys::*;
use lanyard::Utf8CString;

use crate::{core::ChannelGroup, Guid};

use super::{MemoryUsage, StopMode};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)] // so we can transmute between types
pub struct Bus {
    pub(crate) inner: *mut FMOD_STUDIO_BUS,
}

unsafe impl Send for Bus {}
unsafe impl Sync for Bus {}

impl From<*mut FMOD_STUDIO_BUS> for Bus {
    fn from(value: *mut FMOD_STUDIO_BUS) -> Self {
        Bus { inner: value }
    }
}

impl From<Bus> for *mut FMOD_STUDIO_BUS {
    fn from(value: Bus) -> Self {
        value.inner
    }
}

impl Bus {
    /// Sets the pause state.
    ///
    /// This function allows pausing/unpausing of all audio routed into the bus.
    ///
    /// An individual pause state is kept for each bus.
    /// 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.
    /// The pause state is processed in the Studio system update, so [`Bus::get_paused`] will return the state as determined by the last update.
    pub fn set_paused(&self, paused: bool) -> Result<()> {
        unsafe { FMOD_Studio_Bus_SetPaused(self.inner, paused.into()).to_result() }
    }

    /// Retrieves the pause state.
    pub fn get_paused(&self) -> Result<bool> {
        let mut paused = FMOD_BOOL::FALSE;
        unsafe {
            FMOD_Studio_Bus_GetPaused(self.inner, &mut paused).to_result()?;
        }
        Ok(paused.into())
    }

    /// Stops all event instances that are routed into the bus.
    pub fn stop_all_events(&self, stop_mode: StopMode) -> Result<()> {
        unsafe { FMOD_Studio_Bus_StopAllEvents(self.inner, stop_mode.into()).to_result() }
    }
}

impl Bus {
    /// Sets the volume level.
    ///          
    /// This volume is applied as a scaling factor to the volume level set in FMOD Studio.
    pub fn set_volume(&self, volume: c_float) -> Result<()> {
        unsafe { FMOD_Studio_Bus_SetVolume(self.inner, volume).to_result() }
    }

    /// Retrieves the volume level.
    ///
    /// 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.
    /// Volume changes are processed in the Studio system update, so second field will be the value calculated by the last update.
    pub fn get_volume(&self) -> Result<(c_float, c_float)> {
        let mut volume = 0.0;
        let mut final_volume = 0.0;
        unsafe {
            FMOD_Studio_Bus_GetVolume(self.inner, &mut volume, &mut final_volume).to_result()?;
        }
        Ok((volume, final_volume))
    }

    /// Sets the mute state.
    ///
    /// Mute is an additional control for volume, the effect of which is equivalent to setting the volume to zero.
    ///
    /// An individual mute state is kept for each bus.
    /// 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.
    /// The mute state is processed in the Studio system update, so [`Bus::get_mute`] will return the state as determined by the last update.
    pub fn set_mute(&self, mute: bool) -> Result<()> {
        unsafe { FMOD_Studio_Bus_SetMute(self.inner, mute.into()).to_result() }
    }

    /// Retrieves the mute state.
    pub fn get_mute(&self) -> Result<bool> {
        let mut mute = FMOD_BOOL::FALSE;
        unsafe {
            FMOD_Studio_Bus_GetMute(self.inner, &mut mute).to_result()?;
        }
        Ok(mute.into())
    }

    /// Sets the port index to use when attaching to an output port.
    ///
    /// 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.
    /// For some port types a platform specific port index is required to connect to the correct output port.
    /// 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.
    /// In such a case call this function passing the platform specific port index.
    ///
    /// There is no need to call this function for port types which do not require an index.
    ///
    /// This function may be called at any time after a bank containing the bus has been loaded.
    pub fn set_port_index(&self, index: FMOD_PORT_INDEX) -> Result<()> {
        unsafe { FMOD_Studio_Bus_SetPortIndex(self.inner, index).to_result() }
    }

    /// Retrieves the port index assigned to the bus.
    pub fn get_port_index(&self) -> Result<FMOD_PORT_INDEX> {
        let mut index = 0;
        unsafe {
            FMOD_Studio_Bus_GetPortIndex(self.inner, &mut index).to_result()?;
        }
        Ok(index)
    }
}

impl Bus {
    /// Retrieves the core [`ChannelGroup`].
    ///
    /// By default the [`ChannelGroup`] will only exist when it is needed; see Signal Paths in the FMOD documentation for details.
    /// If the [`ChannelGroup`] does not exist, this function will return [`FMOD_RESULT::FMOD_ERR_STUDIO_NOT_LOADED`].
    pub fn get_channel_group(&self) -> Result<ChannelGroup> {
        let mut channel_group = std::ptr::null_mut();
        unsafe {
            FMOD_Studio_Bus_GetChannelGroup(self.inner, &mut channel_group).to_result()?;
        }
        Ok(channel_group.into())
    }

    /// Locks the core [`ChannelGroup`].
    ///
    /// This function forces the system to create the [`ChannelGroup`] and keep it available until [`Bus::unlock_channel_group`] is called.
    /// See Signal Paths in the FMOD documentation for details.
    ///
    /// The [`ChannelGroup`] may not be available immediately after calling this function.
    /// When Studio has been initialized in asynchronous mode, the [`ChannelGroup`] will not be created until the command has been executed in the async thread.
    /// When Studio has been initialized with [`super::InitFlags::SYNCHRONOUS_UPDATE`], the [`ChannelGroup`] will be created in the next [`super::System::update`] call.
    ///
    /// You can call [`super::System::flush_commands`] to ensure the [`ChannelGroup`] has been created.
    /// Alternatively you can keep trying to obtain the [`ChannelGroup`] via [`Bus::get_channel_group`] until it is ready.
    pub fn lock_channel_group(&self) -> Result<()> {
        unsafe { FMOD_Studio_Bus_LockChannelGroup(self.inner).to_result() }
    }

    /// Unlocks the core [`ChannelGroup`].
    ///
    /// This function allows the system to destroy the [`ChannelGroup`] when it is not needed.
    /// See Signal Paths in the FMOD documentation for details.
    pub fn unlock_channel_group(&self) -> Result<()> {
        unsafe { FMOD_Studio_Bus_UnlockChannelGroup(self.inner).to_result() }
    }
}

impl Bus {
    /// Retrieves the bus CPU usage data.
    ///
    /// The first tuple field is the CPU time spent processing the events of this bus, in microseconds.
    ///
    /// The second tuple field is the CPU time spent processing the events and all input buses of this bus, in microseconds.
    ///
    /// [`crate::InitFlags::PROFILE_ENABLE`] with [`crate::SystemBuilder::build`] is required to call this function.
    pub fn get_cpu_usage(&self) -> Result<(c_uint, c_uint)> {
        let mut exclusive = 0;
        let mut inclusive = 0;
        unsafe {
            FMOD_Studio_Bus_GetCPUUsage(self.inner, &mut exclusive, &mut inclusive).to_result()?;
        }
        Ok((exclusive, inclusive))
    }

    /// Retrieves memory usage statistics.
    ///
    /// 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.
    pub fn get_memory_usage(&self) -> Result<MemoryUsage> {
        let mut memory_usage = MaybeUninit::zeroed();
        unsafe {
            FMOD_Studio_Bus_GetMemoryUsage(self.inner, memory_usage.as_mut_ptr()).to_result()?;

            let memory_usage = memory_usage.assume_init().into();
            Ok(memory_usage)
        }
    }
}

impl Bus {
    /// Retrieves the GUID.
    pub fn get_id(&self) -> Result<Guid> {
        let mut guid = MaybeUninit::zeroed();
        unsafe {
            FMOD_Studio_Bus_GetID(self.inner, guid.as_mut_ptr()).to_result()?;

            let guid = guid.assume_init().into();

            Ok(guid)
        }
    }

    /// Retrieves the path.
    ///
    /// The strings bank must be loaded prior to calling this function, otherwise [`FMOD_RESULT::FMOD_ERR_EVENT_NOTFOUND`] is returned.
    // TODO: convert into possible macro for the sake of reusing code
    pub fn get_path(&self) -> Result<Utf8CString> {
        let mut string_len = 0;

        // retrieve the length of the string.
        // this includes the null terminator, so we don't need to account for that.
        unsafe {
            let error =
                FMOD_Studio_Bus_GetPath(self.inner, std::ptr::null_mut(), 0, &mut string_len)
                    .to_error();

            // we expect the error to be fmod_err_truncated.
            // if it isn't, we return the error.
            match error {
                Some(error) if error != FMOD_RESULT::FMOD_ERR_TRUNCATED => return Err(error),
                _ => {}
            }
        };

        let mut path = vec![0u8; string_len as usize];
        let mut expected_string_len = 0;

        unsafe {
            FMOD_Studio_Bus_GetPath(
                self.inner,
                // u8 and i8 have the same layout, so this is ok
                path.as_mut_ptr().cast(),
                string_len,
                &mut expected_string_len,
            )
            .to_result()?;

            debug_assert_eq!(string_len, expected_string_len);

            // all public fmod apis return UTF-8 strings. this should be safe.
            // if i turn out to be wrong, perhaps we should add extra error types?
            let path = Utf8CString::from_utf8_with_nul_unchecked(path);

            Ok(path)
        }
    }

    /// Checks that the [`Bus`] reference is valid.
    pub fn is_valid(&self) -> bool {
        unsafe { FMOD_Studio_Bus_IsValid(self.inner).into() }
    }
}