1use std::ffi::{c_char, c_int, c_uint, c_void};
2use std::marker::PhantomData;
3
4use crate::{FmodResultExt, Guid, Result};
5use fmod_sys::*;
6use lanyard::Utf8CStr;
7
8use crate::{ChannelOrder, Mode, SoundFormat, SoundGroup, SoundType, TimeUnit, panic_wrapper};
9
10use super::{
11 FileSystemAsync, FileSystemSync, Sound, System, async_filesystem_cancel, async_filesystem_read,
12 filesystem_close, filesystem_open, filesystem_read, filesystem_seek,
13};
14
15#[cfg(doc)]
16use crate::Error;
17
18#[derive(Debug)]
20pub struct SoundBuilder<'a> {
21 pub(crate) mode: FMOD_MODE,
22 pub(crate) create_sound_ex_info: FMOD_CREATESOUNDEXINFO,
23 pub(crate) name_or_data: *const c_char,
24 pub(crate) _phantom: PhantomData<&'a ()>,
25}
26
27const EMPTY_EXINFO: FMOD_CREATESOUNDEXINFO = unsafe {
28 FMOD_CREATESOUNDEXINFO {
29 cbsize: std::mem::size_of::<FMOD_CREATESOUNDEXINFO>() as c_int,
30 ..std::mem::MaybeUninit::zeroed().assume_init()
31 }
32};
33
34pub trait PcmCallback {
36 fn read(sound: Sound, data: &mut [u8]) -> Result<()>;
38
39 fn set_position(
41 sound: Sound,
42 subsound: c_int,
43 position: c_uint,
44 position_type: TimeUnit,
45 ) -> Result<()>;
46}
47
48pub unsafe trait NonBlockCallback {
62 fn call(sound: Sound, result: Result<()>) -> Result<()>;
65}
66
67impl<'a> SoundBuilder<'a> {
69 pub const fn open(filename: &'a Utf8CStr) -> Self {
71 Self {
72 mode: 0,
73 create_sound_ex_info: EMPTY_EXINFO,
74 name_or_data: filename.as_ptr(),
75 _phantom: PhantomData,
76 }
77 }
78
79 pub const fn open_user(
81 length: c_uint,
82 channel_count: c_int,
83 default_frequency: c_int,
84 format: SoundFormat,
85 ) -> Self {
86 Self {
87 mode: FMOD_OPENUSER,
88 create_sound_ex_info: FMOD_CREATESOUNDEXINFO {
89 length,
90 numchannels: channel_count,
91 defaultfrequency: default_frequency,
92 format: format as _,
93 ..EMPTY_EXINFO
94 },
95 name_or_data: std::ptr::null(),
96 _phantom: PhantomData,
97 }
98 }
99
100 pub const unsafe fn open_memory(data: &'a [u8]) -> Self {
107 Self {
108 mode: FMOD_OPENMEMORY,
109 create_sound_ex_info: FMOD_CREATESOUNDEXINFO {
110 length: data.len() as c_uint,
111 ..EMPTY_EXINFO
112 },
113 name_or_data: data.as_ptr().cast(),
114 _phantom: PhantomData,
115 }
116 }
117
118 pub const unsafe fn open_memory_point(data: &'a [u8]) -> Self {
125 Self {
126 mode: FMOD_OPENMEMORY_POINT,
127 create_sound_ex_info: FMOD_CREATESOUNDEXINFO {
128 length: data.len() as c_uint,
129 ..EMPTY_EXINFO
130 },
131 name_or_data: data.as_ptr().cast(),
132 _phantom: PhantomData,
133 }
134 }
135
136 #[must_use]
139 pub const fn with_filesystem<F: FileSystemSync + FileSystemAsync>(
140 mut self,
141 userdata: *mut c_void,
142 ) -> Self {
143 self.create_sound_ex_info.fileuseropen = Some(filesystem_open::<F>);
144 self.create_sound_ex_info.fileuserclose = Some(filesystem_close::<F>);
145 self.create_sound_ex_info.fileuserread = Some(filesystem_read::<F>);
146 self.create_sound_ex_info.fileuserseek = Some(filesystem_seek::<F>);
147 self.create_sound_ex_info.fileuserasyncread = Some(async_filesystem_read::<F>);
148 self.create_sound_ex_info.fileuserasynccancel = Some(async_filesystem_cancel::<F>);
149 self.create_sound_ex_info.fileuserdata = userdata;
150 self
151 }
152
153 #[must_use]
155 pub const fn with_filesystem_sync<F: FileSystemSync>(mut self, userdata: *mut c_void) -> Self {
156 self.create_sound_ex_info.fileuseropen = Some(filesystem_open::<F>);
157 self.create_sound_ex_info.fileuserclose = Some(filesystem_close::<F>);
158 self.create_sound_ex_info.fileuserread = Some(filesystem_read::<F>);
159 self.create_sound_ex_info.fileuserseek = Some(filesystem_seek::<F>);
160 self.create_sound_ex_info.fileuserasyncread = None;
161 self.create_sound_ex_info.fileuserasynccancel = None;
162 self.create_sound_ex_info.fileuserdata = userdata;
163 self
164 }
165
166 #[must_use]
168 pub const fn with_filesystem_async<F: FileSystemAsync>(
169 mut self,
170 userdata: *mut c_void,
171 ) -> Self {
172 self.create_sound_ex_info.fileuseropen = Some(filesystem_open::<F>);
173 self.create_sound_ex_info.fileuserclose = Some(filesystem_close::<F>);
174 self.create_sound_ex_info.fileuserasyncread = Some(async_filesystem_read::<F>);
175 self.create_sound_ex_info.fileuserasynccancel = Some(async_filesystem_cancel::<F>);
176 self.create_sound_ex_info.fileuserread = None;
177 self.create_sound_ex_info.fileuserseek = None;
178 self.create_sound_ex_info.fileuserdata = userdata;
179 self
180 }
181
182 #[must_use]
186 pub const unsafe fn with_raw_ex_info(mut self, ex_info: FMOD_CREATESOUNDEXINFO) -> Self {
187 self.create_sound_ex_info = ex_info;
188 self
189 }
190
191 #[must_use]
193 pub const fn with_file_offset(mut self, file_offset: c_uint) -> Self {
194 self.create_sound_ex_info.fileoffset = file_offset;
195 self
196 }
197
198 #[must_use]
200 pub const fn with_open_raw(
201 mut self,
202 channel_count: c_int,
203 default_frequency: c_int,
204 format: SoundFormat,
205 ) -> Self {
206 self.mode |= FMOD_OPENRAW;
207 self.create_sound_ex_info.numchannels = channel_count;
208 self.create_sound_ex_info.defaultfrequency = default_frequency;
209 self.create_sound_ex_info.format = format as _;
210 self
211 }
212
213 #[must_use]
220 pub const fn with_mode(mut self, mode: Mode) -> Self {
221 const DISABLE_MODES: Mode = Mode::OPEN_MEMORY
222 .union(Mode::OPEN_MEMORY_POINT)
223 .union(Mode::OPEN_USER)
224 .union(Mode::OPEN_RAW);
225
226 let mode = mode.difference(DISABLE_MODES); let mode: FMOD_MODE = mode.bits();
228 self.mode |= mode;
229 self
230 }
231
232 #[must_use]
234 pub const fn with_decode_buffer_size(mut self, size: c_uint) -> Self {
235 self.create_sound_ex_info.decodebuffersize = size;
236 self
237 }
238
239 #[must_use]
241 pub const fn with_initial_subsound(mut self, initial_subsound: c_int) -> Self {
242 self.create_sound_ex_info.initialsubsound = initial_subsound;
243 self
244 }
245
246 #[must_use]
248 pub const fn with_subsound_count(mut self, count: c_int) -> Self {
249 self.create_sound_ex_info.numsubsounds = count;
250 self
251 }
252
253 #[must_use]
256 pub const fn with_inclusion_list(mut self, list: &'a [c_int]) -> Self {
257 self.create_sound_ex_info.inclusionlist = list.as_ptr().cast_mut().cast();
258 self.create_sound_ex_info.inclusionlistnum = list.len() as c_int;
259 self
260 }
261
262 #[must_use]
265 pub const fn with_dls_name(mut self, dls_name: &'a Utf8CStr) -> Self {
266 self.create_sound_ex_info.dlsname = dls_name.as_ptr();
267 self
268 }
269
270 #[must_use]
273 pub const fn with_encryption_key(mut self, key: &'a Utf8CStr) -> Self {
274 self.create_sound_ex_info.encryptionkey = key.as_ptr();
275 self
276 }
277
278 #[must_use]
280 pub fn with_max_polyphony(mut self, max_polyphony: c_int) -> Self {
281 self.create_sound_ex_info.maxpolyphony = max_polyphony;
282 self
283 }
284
285 #[must_use]
287 pub const fn with_suggested_sound_type(mut self, sound_type: SoundType) -> Self {
288 self.create_sound_ex_info.suggestedsoundtype = sound_type as _;
289 self
290 }
291
292 #[must_use]
294 pub const fn with_file_buffer_size(mut self, size: c_int) -> Self {
295 self.create_sound_ex_info.filebuffersize = size;
296 self
297 }
298
299 #[must_use]
301 pub const fn with_channel_order(mut self, order: ChannelOrder) -> Self {
302 self.create_sound_ex_info.channelorder = order as _;
303 self
304 }
305
306 #[must_use]
308 pub fn with_initial_sound_group(mut self, group: SoundGroup) -> Self {
309 self.create_sound_ex_info.initialsoundgroup = group.into();
310 self
311 }
312
313 #[must_use]
315 pub const fn with_initial_seek_position(mut self, position: c_uint, unit: TimeUnit) -> Self {
316 self.create_sound_ex_info.initialseekposition = position;
317 self.create_sound_ex_info.initialseekpostype = unit as _;
318 self
319 }
320
321 #[must_use]
323 pub const fn with_ignore_set_filesystem(mut self, ignore: bool) -> Self {
324 self.create_sound_ex_info.ignoresetfilesystem = ignore as _;
325 self
326 }
327
328 #[must_use]
330 pub const fn with_audioqueue_policy(mut self, policy: c_uint) -> Self {
331 self.create_sound_ex_info.audioqueuepolicy = policy;
332 self
333 }
334
335 #[must_use]
337 pub const fn with_min_midi_granularity(mut self, granularity: c_uint) -> Self {
338 self.create_sound_ex_info.minmidigranularity = granularity as _;
339 self
340 }
341
342 #[must_use]
344 pub const fn with_non_block_thread_id(mut self, id: c_int) -> Self {
345 self.create_sound_ex_info.nonblockthreadid = id as _;
346 self
347 }
348
349 #[must_use]
352 pub const fn with_fsb_guid(mut self, guid: &'a Guid) -> Self {
353 self.create_sound_ex_info.fsbguid = std::ptr::from_ref(guid).cast_mut().cast();
354 self
355 }
356
357 #[must_use]
359 pub const fn with_pcm_callback<C: PcmCallback>(mut self) -> Self {
360 unsafe extern "C" fn pcm_read<C: PcmCallback>(
361 sound: *mut FMOD_SOUND,
362 data: *mut c_void,
363 data_len: c_uint,
364 ) -> FMOD_RESULT {
365 panic_wrapper(|| {
366 let result = C::read(unsafe { Sound::from_ffi(sound) }, unsafe {
367 std::slice::from_raw_parts_mut(data.cast(), data_len as _)
368 });
369 FMOD_RESULT::from_result(result)
370 })
371 }
372 unsafe extern "C" fn pcm_set_pos<C: PcmCallback>(
373 sound: *mut FMOD_SOUND,
374 subsound: c_int,
375 position: c_uint,
376 postype: FMOD_TIMEUNIT,
377 ) -> FMOD_RESULT {
378 panic_wrapper(|| {
379 let result = C::set_position(
380 unsafe { Sound::from_ffi(sound) },
381 subsound,
382 position,
383 postype.try_into().unwrap(),
384 );
385 FMOD_RESULT::from_result(result)
386 })
387 }
388
389 self.create_sound_ex_info.pcmreadcallback = Some(pcm_read::<C>);
390 self.create_sound_ex_info.pcmsetposcallback = Some(pcm_set_pos::<C>);
391
392 self
393 }
394
395 #[must_use]
397 pub const fn with_nonblock_callback<C: NonBlockCallback>(mut self) -> Self {
398 unsafe extern "C" fn nonblock_callback<C: NonBlockCallback>(
399 sound: *mut FMOD_SOUND,
400 result: FMOD_RESULT,
401 ) -> FMOD_RESULT {
402 panic_wrapper(|| {
403 let result = C::call(unsafe { Sound::from_ffi(sound) }, result.to_result());
404 FMOD_RESULT::from_result(result)
405 })
406 }
407
408 self.create_sound_ex_info.nonblockcallback = Some(nonblock_callback::<C>);
409
410 self
411 }
412
413 pub(crate) fn ex_info_is_empty(&self) -> bool {
414 self.create_sound_ex_info == EMPTY_EXINFO
415 }
416
417 pub fn build(&self, system: System) -> Result<Sound> {
419 system.create_sound(self)
420 }
421
422 pub fn build_stream(&self, system: System) -> Result<Sound> {
424 system.create_stream(self)
425 }
426}
427
428impl<'a> SoundBuilder<'a> {
430 pub const fn mode(&self) -> Mode {
432 Mode::from_bits_truncate(self.mode)
433 }
434
435 pub const fn raw_ex_info(&self) -> FMOD_CREATESOUNDEXINFO {
437 self.create_sound_ex_info
438 }
439
440 pub const fn raw_name_or_data(&self) -> *const c_char {
442 self.name_or_data
443 }
444
445 pub fn name_or_url(&self) -> Option<&Utf8CStr> {
449 if self
450 .mode()
451 .intersects(Mode::OPEN_MEMORY | Mode::OPEN_MEMORY_POINT | Mode::OPEN_USER)
452 {
453 None
454 } else {
455 Some(unsafe { Utf8CStr::from_ptr_unchecked(self.name_or_data) })
456 }
457 }
458
459 pub fn data(&self) -> Option<&[u8]> {
463 if self
464 .mode()
465 .intersects(Mode::OPEN_MEMORY | Mode::OPEN_MEMORY_POINT)
466 {
467 Some(unsafe {
468 std::slice::from_raw_parts(
469 self.name_or_data.cast(),
470 self.create_sound_ex_info.length as usize,
471 )
472 })
473 } else {
474 None
475 }
476 }
477
478 pub const fn length(&self) -> c_uint {
480 self.create_sound_ex_info.length
481 }
482
483 pub const fn file_offset(&self) -> c_uint {
485 self.create_sound_ex_info.fileoffset
486 }
487
488 pub const fn channel_count(&self) -> c_int {
490 self.create_sound_ex_info.numchannels
491 }
492
493 pub const fn default_frequency(&self) -> c_int {
495 self.create_sound_ex_info.defaultfrequency
496 }
497
498 #[allow(clippy::missing_panics_doc)] pub fn format(&self) -> SoundFormat {
501 self.create_sound_ex_info.format.try_into().unwrap()
502 }
503
504 pub const fn decode_buffer_size(&self) -> c_uint {
506 self.create_sound_ex_info.decodebuffersize
507 }
508
509 pub const fn initial_subsound(&self) -> c_int {
511 self.create_sound_ex_info.initialsubsound
512 }
513
514 pub const fn subsound_count(&self) -> c_int {
516 self.create_sound_ex_info.numsubsounds
517 }
518
519 pub fn inclusion_list(&self) -> Option<&'a [c_int]> {
521 if self.create_sound_ex_info.inclusionlist.is_null() {
522 None
523 } else {
524 Some(unsafe {
525 std::slice::from_raw_parts(
526 self.create_sound_ex_info.inclusionlist.cast(),
527 self.create_sound_ex_info.inclusionlistnum as usize,
528 )
529 })
530 }
531 }
532
533 pub fn dls_name(&self) -> Option<&Utf8CStr> {
535 if self.create_sound_ex_info.dlsname.is_null() {
536 None
537 } else {
538 Some(unsafe { Utf8CStr::from_ptr_unchecked(self.create_sound_ex_info.dlsname) })
539 }
540 }
541
542 pub fn encryption_key(&self) -> Option<&Utf8CStr> {
544 if self.create_sound_ex_info.encryptionkey.is_null() {
545 None
546 } else {
547 Some(unsafe { Utf8CStr::from_ptr_unchecked(self.create_sound_ex_info.encryptionkey) })
548 }
549 }
550
551 pub const fn max_polyphony(&self) -> c_int {
553 self.create_sound_ex_info.maxpolyphony
554 }
555
556 #[allow(clippy::missing_panics_doc)] pub fn suggested_sound_type(&self) -> SoundType {
559 self.create_sound_ex_info
560 .suggestedsoundtype
561 .try_into()
562 .unwrap()
563 }
564
565 pub const fn file_buffer_size(&self) -> c_int {
567 self.create_sound_ex_info.filebuffersize
568 }
569
570 #[allow(clippy::missing_panics_doc)] pub fn channel_order(&self) -> ChannelOrder {
573 self.create_sound_ex_info.channelorder.try_into().unwrap()
574 }
575
576 pub fn initial_sound_group(&self) -> Option<SoundGroup> {
578 if self.create_sound_ex_info.initialsoundgroup.is_null() {
579 None
580 } else {
581 Some(unsafe { SoundGroup::from_ffi(self.create_sound_ex_info.initialsoundgroup) })
582 }
583 }
584
585 #[allow(clippy::missing_panics_doc)] pub fn initial_seek_position(&self) -> (c_uint, TimeUnit) {
588 (
589 self.create_sound_ex_info.initialseekposition,
590 self.create_sound_ex_info
591 .initialseekpostype
592 .try_into()
593 .unwrap(),
594 )
595 }
596
597 pub const fn ignore_set_filesystem(&self) -> bool {
599 self.create_sound_ex_info.ignoresetfilesystem > 0
600 }
601 pub const fn min_midi_granularity(&self) -> c_uint {
603 self.create_sound_ex_info.minmidigranularity
604 }
605
606 pub const fn non_block_thread_id(&self) -> c_int {
608 self.create_sound_ex_info.nonblockthreadid
609 }
610
611 pub const fn fsb_guid(&self) -> Option<Guid> {
613 if self.create_sound_ex_info.fsbguid.is_null() {
614 None
615 } else {
616 Some(unsafe { *(self.create_sound_ex_info.fsbguid.cast()) })
617 }
618 }
619}
620
621impl SoundBuilder<'_> {
622 pub unsafe fn from_ffi(
633 name_or_data: *const c_char,
634 mode: FMOD_MODE,
635 create_sound_ex_info: FMOD_CREATESOUNDEXINFO,
636 ) -> Self {
637 Self {
638 mode,
639 create_sound_ex_info,
640 name_or_data,
641 _phantom: PhantomData,
642 }
643 }
644}