fmod/core/system/
builder.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 crate::{FmodResultExt, Result};
8use crate::{InitFlags, OutputType, SpeakerMode, System};
9use fmod_sys::*;
10use std::ffi::{c_int, c_uint, c_void};
11
12/// A builder for creating and initializing a [`System`].
13///
14/// Handles setting values that can only be set before initialization for you.
15#[derive(Debug)]
16pub struct SystemBuilder {
17    pub(crate) system: *mut FMOD_SYSTEM,
18    pub(crate) thread_unsafe: bool,
19}
20
21#[cfg(not(feature = "thread-unsafe"))]
22unsafe impl Send for SystemBuilder {}
23#[cfg(not(feature = "thread-unsafe"))]
24unsafe impl Sync for SystemBuilder {}
25
26#[cfg(doc)]
27use crate::{debug, memory};
28
29impl SystemBuilder {
30    /// Creates a new [`SystemBuilder`].
31    ///
32    /// # Safety
33    ///
34    /// This must be called first to create an FMOD System object before any other API calls (except for [`memory::initialize`] and [`debug::initialize`]).
35    /// Use this function to create 1 or multiple instances of FMOD System objects.
36    ///
37    /// Calls to [`SystemBuilder::new`] and [`System::release`] are not thread-safe.
38    /// Do not call these functions simultaneously from multiple threads at once.
39    pub unsafe fn new() -> Result<Self> {
40        let mut system = std::ptr::null_mut();
41        unsafe { FMOD_System_Create(&raw mut system, FMOD_VERSION).to_result()? };
42
43        Ok(SystemBuilder {
44            system,
45            thread_unsafe: false,
46        })
47    }
48
49    /// # Safety
50    ///
51    /// This function intializes FMOD to be thread unsafe, which makes *EVERY* Struct in this crate `!Send` and `!Sync` *without* marking them as `!Send` and `!Sync`.
52    /// This means that there are no handrails preventing you from using FMOD across multiple threads, and you *must* ensure this yourself!
53    #[cfg(not(feature = "thread-unsafe"))]
54    pub unsafe fn thread_unsafe(&mut self) {
55        self.thread_unsafe = true;
56    }
57
58    #[cfg(feature = "thread-unsafe")]
59    pub fn thread_unsafe(&mut self) {
60        self.thread_unsafe = true;
61    }
62
63    /// Sets the output format for the software mixer.
64    ///
65    /// If loading banks made in FMOD Studio, this must be called with speakermode corresponding to the project output format
66    /// if there is a possibility of the output audio device not matching the project format.
67    /// Any differences between the project format and speakermode will cause the mix to sound wrong.
68    ///
69    /// By default speakermode will assume the setup the OS / output prefers.
70    ///
71    /// Altering the samplerate from the OS / output preferred rate may incur extra latency.
72    /// Altering the speakermode from the OS / output preferred mode may cause an upmix/downmix which can alter the sound.
73    ///
74    /// On lower power platforms such as mobile samplerate will default to 24 kHz to reduce CPU cost.
75    pub fn software_format(
76        &mut self,
77        sample_rate: c_int,
78        speaker_mode: SpeakerMode,
79        raw_speakers: c_int,
80    ) -> Result<&mut Self> {
81        unsafe {
82            FMOD_System_SetSoftwareFormat(
83                self.system,
84                sample_rate,
85                speaker_mode.into(),
86                raw_speakers,
87            )
88            .to_result()?;
89        };
90        Ok(self)
91    }
92
93    /// Sets the output format for the software mixer.
94    ///
95    /// If loading banks made in FMOD Studio, this must be called with speakermode corresponding to the project output format
96    /// if there is a possibility of the output audio device not matching the project format.
97    /// Any differences between the project format and speakermode will cause the mix to sound wrong.
98    ///
99    /// By default speakermode will assume the setup the OS / output prefers.
100    ///
101    /// Altering the samplerate from the OS / output preferred rate may incur extra latency.
102    /// Altering the speakermode from the OS / output preferred mode may cause an upmix/downmix which can alter the sound.
103    ///
104    /// On lower power platforms such as mobile samplerate will default to 24 kHz to reduce CPU cost.
105    pub fn software_channels(&mut self, software_channels: c_int) -> Result<&mut Self> {
106        unsafe {
107            FMOD_System_SetSoftwareChannels(self.system, software_channels).to_result()?;
108        };
109        Ok(self)
110    }
111
112    /// Sets the buffer size for the FMOD software mixing engine.
113    ///
114    /// This function is used if you need to control mixer latency or granularity.
115    /// Smaller buffersizes lead to smaller latency, but can lead to stuttering/skipping/unstable sound on slower machines or soundcards with bad drivers.
116    ///
117    /// To get the `buffer_size` in milliseconds, divide it by the output rate and multiply the result by 1000.
118    /// For a `buffer_size` of 1024 and an output rate of 48khz (see [`Self::software_format`]), milliseconds = 1024 / 48000 * 1000 = 21.33ms.
119    /// This means the mixer updates every 21.33ms.
120    ///
121    /// To get the total buffer size multiply the `buffer_size` by the numbuffers value.
122    /// By default this would be 4 * 1024 = 4096 samples, or 4 * 21.33ms = 85.33ms.
123    /// This would generally be the total latency of the software mixer,
124    /// but in reality due to one of the buffers being written to constantly,
125    /// and the cursor position of the buffer that is audible,
126    /// the latency is typically more like the (number of buffers - 1.5) multiplied by the buffer length.
127    ///
128    /// To convert from milliseconds back to 'samples',
129    /// simply multiply the value in milliseconds by the sample rate of the output (ie 48000 if that is what it is set to),
130    /// then divide by 1000.
131    ///
132    /// The FMOD software mixer mixes to a ringbuffer.
133    /// The size of this ringbuffer is determined here.
134    /// It mixes a block of sound data every '`buffer_size`' number of samples,
135    /// and there are 'numbuffers' number of these blocks that make up the entire ringbuffer.
136    /// Adjusting these values can lead to extremely low latency performance (smaller values),
137    /// or greater stability in sound output (larger values).
138    ///
139    /// ### Warning! The '`buffer_size`' is generally best left alone.
140    ///
141    /// Making the granularity smaller will just increase CPU usage (cache misses and DSP graph overhead).
142    /// Making it larger affects how often you hear commands update such as volume/pitch/pan changes.
143    /// Anything above 20ms will be noticeable and sound parameter changes will be obvious instead of smooth.
144    ///
145    /// FMOD chooses the most optimal size by default for best stability,
146    /// depending on the output type. It is not recommended changing this value unless you really need to.
147    /// You may get worse performance than the default settings chosen by FMOD.
148    /// If you do set the size manually, the `buffer_size` argument must be a multiple of four,
149    /// typically 256, 480, 512, 1024 or 2048 depedning on your latency requirements.
150    pub fn dsp_buffer_size(
151        &mut self,
152        buffer_size: c_uint,
153        buffer_count: c_int,
154    ) -> Result<&mut Self> {
155        unsafe {
156            FMOD_System_SetDSPBufferSize(self.system, buffer_size, buffer_count).to_result()?;
157        };
158        Ok(self)
159    }
160
161    /// Sets the type of output interface used to run the mixer.
162    ///
163    /// This function is typically used to select between different OS specific audio APIs which may have different features.
164    ///
165    /// It is only necessary to call this function if you want to specifically switch away from the default output mode for the operating system.
166    /// The most optimal mode is selected by default for the operating system.
167    pub fn output(&mut self, kind: OutputType) -> Result<&mut Self> {
168        unsafe {
169            FMOD_System_SetOutput(self.system, kind.into()).to_result()?;
170        };
171        Ok(self)
172    }
173
174    /// Selects an output type given a plug-in handle.
175    pub fn output_by_plugin(&mut self, handle: c_uint) -> Result<&mut Self> {
176        unsafe {
177            FMOD_System_SetOutputByPlugin(self.system, handle).to_result()?;
178        };
179        Ok(self)
180    }
181
182    /// Initialize the system object and prepare FMOD for playback.
183    pub fn build(self, max_channels: c_int, flags: InitFlags) -> Result<System> {
184        unsafe { self.build_with_extra_driver_data(max_channels, flags, std::ptr::null_mut()) }
185    }
186
187    /// # Safety
188    ///
189    /// See the FMOD docs explaining driver data for more safety information.
190    pub unsafe fn build_with_extra_driver_data(
191        self,
192        max_channels: c_int,
193        mut flags: InitFlags,
194        driver_data: *mut c_void,
195    ) -> Result<System> {
196        if self.thread_unsafe {
197            flags.insert(InitFlags::THREAD_UNSAFE);
198        } else {
199            #[cfg(not(feature = "thread-unsafe"))]
200            flags.remove(InitFlags::THREAD_UNSAFE);
201        }
202        unsafe {
203            FMOD_System_Init(self.system, max_channels, flags.bits(), driver_data).to_result()?;
204            Ok(System::from_ffi(self.system))
205        }
206    }
207}