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}