1pub(crate) mod callbacks;
21mod fmod_state;
22mod parameter_init;
23pub mod parameter_spec;
24
25use crate::callbacks::{
26 create_callback, get_data_callback, get_float_callback, get_int_callback, process_callback,
27 release_callback, set_data_callback, set_float_callback, set_int_callback,
28 sys_deregister_callback, sys_register_callback,
29};
30use crate::parameter_init::init_parameters;
31use glam::Vec3;
32use libfmod::ffi::{
33 FMOD_DSP_DESCRIPTION, FMOD_DSP_PAN_3D_ROLLOFF_TYPE, FMOD_DSP_PARAMETER_3DATTRIBUTES,
34 FMOD_DSP_PARAMETER_ATTENUATION_RANGE, FMOD_DSP_PARAMETER_OVERALLGAIN, FMOD_PLUGIN_SDK_VERSION,
35};
36use libfmod::DspDescription;
37use phonon::dsp::audio_buffer::{AudioBuffer, AudioSettings};
38use phonon::effects::direct::{
39 DirectApplyFlags, DirectEffect, DirectEffectParameters, TransmissionType,
40};
41use phonon::effects::panning::{PanningEffect, PanningEffectParameters};
42use phonon::simulators::direct::DirectSoundPath;
43use std::ffi::CString;
44use std::os::raw::{c_char, c_int};
45use std::ptr::null_mut;
46
47#[derive(Copy, Clone)]
48enum ParameterApplyType {
49 Disable,
50 SimulationDefined,
51 UserDefined,
52}
53
54impl From<c_int> for ParameterApplyType {
55 fn from(value: c_int) -> Self {
56 match value {
57 0 => ParameterApplyType::Disable,
58 1 => ParameterApplyType::SimulationDefined,
59 2 => ParameterApplyType::UserDefined,
60 _ => ParameterApplyType::Disable,
61 }
62 }
63}
64
65impl Into<c_int> for ParameterApplyType {
66 fn into(self) -> c_int {
67 match self {
68 ParameterApplyType::Disable => 0,
69 ParameterApplyType::SimulationDefined => 1,
70 ParameterApplyType::UserDefined => 2,
71 }
72 }
73}
74
75#[expect(dead_code, reason = "Not everything is implemented yet")]
76pub(crate) struct EffectState {
77 source: FMOD_DSP_PARAMETER_3DATTRIBUTES,
78 overall_gain: FMOD_DSP_PARAMETER_OVERALLGAIN,
79
80 apply_distance_attenuation: ParameterApplyType,
81 apply_air_absorption: ParameterApplyType,
82 apply_directivity: ParameterApplyType,
83 apply_occlusion: ParameterApplyType,
84 apply_transmission: ParameterApplyType,
85
86 distance_attenuation: f32,
87 distance_attenuation_rolloff_type: FMOD_DSP_PAN_3D_ROLLOFF_TYPE,
88 distance_attenuation_min_distance: f32,
89 distance_attenuation_max_distance: f32,
90
91 direct_sound_path: DirectSoundPath,
93
94 air_absorption: [f32; 3],
95 directivity: f32,
96 dipole_weight: f32, dipole_power: f32, occlusion: f32,
99 transmission_type: TransmissionType,
100 transmission: [f32; 3],
101
102 attenuation_range: FMOD_DSP_PARAMETER_ATTENUATION_RANGE,
103 attenuation_range_set: bool, audio_settings: AudioSettings,
106
107 in_buffer_stereo: AudioBuffer<2>,
108 in_buffer_mono: AudioBuffer<1>,
109 out_buffer: AudioBuffer<2>,
110 direct_buffer: AudioBuffer<1>,
111 mono_buffer: AudioBuffer<1>,
112
113 panning_effect: PanningEffect,
114 direct_effect: DirectEffect,
115}
116
117fn apply_flag(
118 apply: ParameterApplyType,
119 target_flag: DirectApplyFlags,
120 current_flags: &mut DirectApplyFlags,
121) {
122 match apply {
123 ParameterApplyType::Disable => current_flags.set(target_flag, false),
124 ParameterApplyType::SimulationDefined => current_flags.set(target_flag, true),
125 ParameterApplyType::UserDefined => current_flags.set(target_flag, true), }
127}
128
129impl EffectState {
130 fn create_flags(&mut self) -> DirectApplyFlags {
131 let mut flags = DirectApplyFlags::empty();
132
133 apply_flag(
134 self.apply_distance_attenuation,
135 DirectApplyFlags::DistanceAttenuation,
136 &mut flags,
137 );
138
139 apply_flag(
140 self.apply_air_absorption,
141 DirectApplyFlags::AirAbsorption,
142 &mut flags,
143 );
144
145 apply_flag(
146 self.apply_directivity,
147 DirectApplyFlags::Directivity,
148 &mut flags,
149 );
150
151 apply_flag(
152 self.apply_occlusion,
153 DirectApplyFlags::Occlusion,
154 &mut flags,
155 );
156
157 flags
158 }
159
160 fn process(
161 &mut self,
162 in_buffer: &[f32],
163 out_buffer: &mut [f32],
164 length: usize,
165 channels: usize,
166 ) {
167 let _num_samples = length * channels;
168
169 let position = self.source.relative.position;
171 let direction = Vec3::new(position.x, position.y, position.z);
172 let panning_params = PanningEffectParameters { direction };
173
174 let direct_params = DirectEffectParameters {
175 direct_sound_path: self.direct_sound_path,
176 flags: self.create_flags(),
177 transmission_type: TransmissionType::FrequencyDependent,
178 };
179
180 self.in_buffer_stereo.read_interleaved(in_buffer);
182 self.in_buffer_stereo.downmix(&mut self.in_buffer_mono);
183
184 self.direct_effect
185 .apply(direct_params, &self.in_buffer_mono, &mut self.direct_buffer);
186
187 self.panning_effect
188 .apply(panning_params, &self.direct_buffer, &mut self.out_buffer);
189
190 self.out_buffer.write_interleaved(out_buffer);
191 }
192}
193
194pub fn create_dsp_description() -> DspDescription {
195 DspDescription {
196 pluginsdkversion: FMOD_PLUGIN_SDK_VERSION,
197 name: str_to_c_char_array("Phonon Spatializer"),
198 version: 1,
199 numinputbuffers: 1,
200 numoutputbuffers: 1,
201 create: Some(create_callback),
202 release: Some(release_callback),
203 reset: None,
204 read: None,
205 process: Some(process_callback),
206 setposition: None,
207 paramdesc: init_parameters(),
208 setparameterfloat: Some(set_float_callback),
209 setparameterint: Some(set_int_callback),
210 setparameterbool: None, setparameterdata: Some(set_data_callback),
212 getparameterfloat: Some(get_float_callback),
213 getparameterint: Some(get_int_callback),
214 getparameterbool: None, getparameterdata: Some(get_data_callback),
216 shouldiprocess: None,
217 userdata: null_mut(),
218 sys_register: Some(sys_register_callback),
219 sys_deregister: Some(sys_deregister_callback),
220 sys_mix: None,
221 }
222}
223
224#[no_mangle]
227extern "C" fn FMODGetDSPDescription() -> *mut FMOD_DSP_DESCRIPTION {
228 let description: FMOD_DSP_DESCRIPTION = create_dsp_description().into();
229 let boxed = Box::new(description);
230 Box::into_raw(boxed)
231}
232
233fn str_to_c_char_array<const LEN: usize>(input: &str) -> [c_char; LEN] {
234 let mut array: [c_char; LEN] = [0; LEN];
235
236 let c_string = CString::new(input).expect("CString::new failed");
238
239 let bytes = c_string.as_bytes();
241
242 if bytes.len() > LEN {
244 panic!("String is too long to fit in [c_char; LEN]");
245 }
246
247 for (i, &byte) in bytes.iter().enumerate() {
249 array[i] = byte as c_char;
250 }
251
252 array
253}