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
// 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 fmod_sys::*;
use std::{ffi::c_int, mem::MaybeUninit};

use crate::{ChannelGroup, PortType, ReverbProperties, System, Vector};

impl System {
    /// Sets the position, velocity and orientation of the specified 3D sound listener.
    ///
    /// The forward and up vectors must be perpendicular and be of unit length (magnitude of each vector should be 1).
    ///
    /// Vectors must be provided in the correct handedness.
    ///
    /// For velocity, remember to use units per second, and not units per frame.
    /// This is a common mistake and will make the doppler effect sound wrong if velocity is based on movement per frame rather than a fixed time period.
    /// If velocity per frame is calculated, it can be converted to velocity per second by dividing it by the time taken between frames as a fraction of a second.
    /// i.e.
    ///
    /// `velocity_units_per_second = (position_currentframe - position_lastframe) / time_taken_since_last_frame_in_seconds`.
    ///
    /// At 60fps the formula would look like `velocity_units_per_second = (position_current_frame - position_last_frame) / 0.0166667`.
    ///
    /// Users of the Studio API should call [`crate::studio::System::set_listener_attributes`] instead of this function.
    pub fn set_3d_listener_attributes(
        &self,
        listener: c_int,
        position: Option<Vector>,
        velocity: Option<Vector>,
        forward: Option<Vector>,
        up: Option<Vector>,
    ) -> Result<()> {
        // these casts are ok as Vector is layout equivalent with FMOD_VECTOR
        let position = position
            .as_ref()
            .map_or(std::ptr::null(), std::ptr::from_ref)
            .cast();
        let velocity = velocity
            .as_ref()
            .map_or(std::ptr::null(), std::ptr::from_ref)
            .cast();
        let forward = forward
            .as_ref()
            .map_or(std::ptr::null(), std::ptr::from_ref)
            .cast();
        let up = up
            .as_ref()
            .map_or(std::ptr::null(), std::ptr::from_ref)
            .cast();
        unsafe {
            FMOD_System_Set3DListenerAttributes(
                self.inner, listener, position, velocity, forward, up,
            )
            .to_result()
        }
    }

    /// Retrieves the position, velocity and orientation of the specified 3D sound listener.
    ///
    /// Users of the Studio API should call [`crate::studio::System::get_listener_attributes`] instead of this function.
    pub fn get_3d_listener_attributes(
        &self,
        listener: c_int,
    ) -> Result<(Vector, Vector, Vector, Vector)> {
        let mut position = MaybeUninit::zeroed();
        let mut velocity = MaybeUninit::zeroed();
        let mut forward = MaybeUninit::zeroed();
        let mut up = MaybeUninit::zeroed();
        unsafe {
            FMOD_System_Get3DListenerAttributes(
                self.inner,
                listener,
                position.as_mut_ptr(),
                velocity.as_mut_ptr(),
                forward.as_mut_ptr(),
                up.as_mut_ptr(),
            )
            .to_result()?;

            let position = position.assume_init();
            let velocity = velocity.assume_init();
            let forward = forward.assume_init();
            let up = up.assume_init();

            Ok((position.into(), velocity.into(), forward.into(), up.into()))
        }
    }

    /// Sets parameters for the global reverb environment.
    ///
    /// To assist in defining reverb properties there are several presets available,
    /// see the associated constants on [`ReverbProperties.`].
    ///
    /// When using each instance for the first time,
    /// FMOD will create an SFX reverb [`Dsp`] unit that takes up several hundred kilobytes of memory and some CPU.
    pub fn set_reverb_properties(
        &self,
        instance: c_int,
        properties: Option<ReverbProperties>,
    ) -> Result<()> {
        let properties = properties
            .as_ref()
            .map_or(std::ptr::null(), std::ptr::from_ref)
            .cast();
        unsafe { FMOD_System_SetReverbProperties(self.inner, instance, properties).to_result() }
    }

    /// Retrieves the current reverb environment for the specified reverb instance.
    pub fn get_reverb_properties(&self, instance: c_int) -> Result<ReverbProperties> {
        let mut properties = MaybeUninit::zeroed();
        unsafe {
            FMOD_System_GetReverbProperties(self.inner, instance, properties.as_mut_ptr())
                .to_result()?;
            let properties = properties.assume_init().into();
            Ok(properties)
        }
    }

    /// Connect the output of the specified [`ChannelGroup`] to an audio port on the output driver.
    ///
    /// Ports are additional outputs supported by some [`OutputType`] plugins and can include things like controller headsets or dedicated background music streams.
    /// See the Port Support section (where applicable) of each platform's getting started guide found in the platform details chapter.
    pub fn attach_channel_group_to_port(
        &self,
        kind: PortType,
        index: Option<FMOD_PORT_INDEX>,
        channel_group: ChannelGroup,
        pass_through: bool,
    ) -> Result<()> {
        unsafe {
            FMOD_System_AttachChannelGroupToPort(
                self.inner,
                kind.into(),
                index.unwrap_or(FMOD_PORT_INDEX_NONE as FMOD_PORT_INDEX),
                channel_group.into(),
                pass_through.into(),
            )
            .to_result()
        }
    }

    /// Disconnect the output of the specified [`ChannelGroup`] from an audio port on the output driver.
    ///
    /// Removing a [`ChannelGroup`] from a port will reroute the audio back to the main mix.
    pub fn detach_channel_group_from_port(&self, channel_group: ChannelGroup) -> Result<()> {
        unsafe {
            FMOD_System_DetachChannelGroupFromPort(self.inner, channel_group.into()).to_result()
        }
    }
}