Skip to main content

fmod/
sound.rs

1use std;
2use std::rc::Rc;
3use std::ops::{Deref, DerefMut};
4use log;
5use num_derive::FromPrimitive;
6use crate::{ll, fmod_result, channel, Channel, ChannelGroup, Error, Guid, Mode,
7  SoundGroup, System, Timeunit};
8
9/// A reference counted sound. When the last reference is dropped, the sound
10/// will be released.
11#[derive(Clone, Debug, PartialEq)]
12pub struct Sound {
13  inner : Rc <Inner>,
14  pub (crate) system : System
15}
16
17/// A weak reference to a sound.
18#[derive(Clone, Debug, PartialEq)]
19pub struct SoundRef {
20  pub (crate) sound : Sound
21}
22
23#[derive(PartialEq)]
24struct Inner {
25  raw   : *mut ll::FMOD_SOUND,
26  /// Only release sound if owned
27  owned : bool
28}
29
30// TODO: provide a macro to help define these low level callbacks
31#[derive(Default)]
32pub struct Createsoundexinfo {
33  pub length              : u32,
34  pub fileoffset          : u32,
35  pub numchannels         : i32,
36  pub defaultfrequency    : i32,
37  pub format              : Format,
38  pub decodebuffersize    : u32,
39  pub initialsubsound     : i32,
40  pub numsubsounds        : i32,
41  pub inclusionlist       : Vec <i32>,
42  pub pcmreadcallback     : ll::FMOD_SOUND_PCMREAD_CALLBACK,
43  pub pcmsetposcallback   : ll::FMOD_SOUND_PCMSETPOS_CALLBACK,
44  pub nonblockcallback    : ll::FMOD_SOUND_NONBLOCK_CALLBACK,
45  pub dlsname             : std::ffi::CString,
46  pub encryptionkey       : std::ffi::CString,
47  pub maxpolyphony        : i32,
48  pub userdata            : Vec <u8>, // TODO
49  pub suggestedsoundtype  : Type,
50  pub fileuseropen        : ll::FMOD_FILE_OPEN_CALLBACK,
51  pub fileuserclose       : ll::FMOD_FILE_CLOSE_CALLBACK,
52  pub fileuserread        : ll::FMOD_FILE_READ_CALLBACK,
53  pub fileuserseek        : ll::FMOD_FILE_SEEK_CALLBACK,
54  pub fileuserasyncread   : ll::FMOD_FILE_ASYNCREAD_CALLBACK,
55  pub fileuserasynccancel : ll::FMOD_FILE_ASYNCCANCEL_CALLBACK,
56  pub fileuserdata        : Vec <u8>, // TODO
57  pub filebuffersize      : i32,
58  pub channelorder        : channel::Channelorder,
59  pub channelmask         : channel::Channelmask,
60  pub initialsoundgroup   : Option <SoundGroup>,
61  pub initialseekposition : u32,
62  pub initialseekpostype  : Timeunit,
63  pub ignoresetfilesystem : i32,
64  pub audioqueuepolicy    : u32,
65  pub minmidigranularity  : u32,
66  pub nonblockthreadid    : i32,
67  pub fsbguid             : Option <Guid>,
68}
69
70#[derive(Copy, Clone, Debug, Eq, PartialEq, FromPrimitive)]
71#[derive(Default)]
72pub enum Format {
73  #[default]
74  None      = ll::FMOD_SOUND_FORMAT_FMOD_SOUND_FORMAT_NONE      as isize,
75  Pcm8      = ll::FMOD_SOUND_FORMAT_FMOD_SOUND_FORMAT_PCM8      as isize,
76  Pcm16     = ll::FMOD_SOUND_FORMAT_FMOD_SOUND_FORMAT_PCM16     as isize,
77  Pcm24     = ll::FMOD_SOUND_FORMAT_FMOD_SOUND_FORMAT_PCM24     as isize,
78  Pcm32     = ll::FMOD_SOUND_FORMAT_FMOD_SOUND_FORMAT_PCM32     as isize,
79  PcmFloat  = ll::FMOD_SOUND_FORMAT_FMOD_SOUND_FORMAT_PCMFLOAT  as isize,
80  Bitstream = ll::FMOD_SOUND_FORMAT_FMOD_SOUND_FORMAT_BITSTREAM as isize,
81  MAX       = ll::FMOD_SOUND_FORMAT_FMOD_SOUND_FORMAT_MAX       as isize
82}
83
84#[derive(Copy, Clone, Debug, Eq, PartialEq, FromPrimitive)]
85#[derive(Default)]
86pub enum Type {
87  #[default]
88  Unknown          = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_UNKNOWN as isize,
89  Aiff             = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_AIFF as isize,
90  Asf              = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_ASF as isize,
91  Dls              = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_DLS as isize,
92  Flac             = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_FLAC as isize,
93  Fsb              = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_FSB as isize,
94  It               = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_IT as isize,
95  Midi             = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_MIDI as isize,
96  Mod              = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_MOD as isize,
97  Mpeg             = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_MPEG as isize,
98  Oggvorbis        = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_OGGVORBIS as isize,
99  Playlist         = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_PLAYLIST as isize,
100  Raw              = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_RAW as isize,
101  S3m              = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_S3M as isize,
102  User             = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_USER as isize,
103  Wav              = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_WAV as isize,
104  Xm               = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_XM as isize,
105  Xma              = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_XMA as isize,
106  Audioqueue       = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_AUDIOQUEUE as isize,
107  At9              = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_AT9 as isize,
108  Vorbis           = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_VORBIS as isize,
109  MediaFoundation  = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_MEDIA_FOUNDATION as isize,
110  Mediacodec       = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_MEDIACODEC as isize,
111  Fadpcm           = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_FADPCM as isize,
112  MAX              = ll::FMOD_SOUND_TYPE_FMOD_SOUND_TYPE_MAX as isize
113}
114
115#[derive(Copy, Clone, Debug, Eq, PartialEq, FromPrimitive)]
116pub enum Openstate {
117  Ready       = ll::FMOD_OPENSTATE_FMOD_OPENSTATE_READY as isize,
118  Loading     = ll::FMOD_OPENSTATE_FMOD_OPENSTATE_LOADING as isize,
119  Error       = ll::FMOD_OPENSTATE_FMOD_OPENSTATE_ERROR as isize,
120  Connecting  = ll::FMOD_OPENSTATE_FMOD_OPENSTATE_CONNECTING as isize,
121  Buffering   = ll::FMOD_OPENSTATE_FMOD_OPENSTATE_BUFFERING as isize,
122  Seeking     = ll::FMOD_OPENSTATE_FMOD_OPENSTATE_SEEKING as isize,
123  Playing     = ll::FMOD_OPENSTATE_FMOD_OPENSTATE_PLAYING as isize,
124  Setposition = ll::FMOD_OPENSTATE_FMOD_OPENSTATE_SETPOSITION as isize,
125  MAX         = ll::FMOD_OPENSTATE_FMOD_OPENSTATE_MAX as isize
126}
127
128impl Sound {
129  #[inline]
130  pub fn from_raw_parts (
131    raw     : *mut ll::FMOD_SOUND,
132    owned   : bool,
133    system  : System
134  ) -> Self {
135    let inner = Rc::new (Inner { raw, owned });
136    Sound { inner, system }
137  }
138
139  #[inline]
140  fn raw (&self) -> *mut ll::FMOD_SOUND {
141    self.inner.raw
142  }
143
144  #[inline]
145  pub fn sound_ref (&self) -> SoundRef {
146    let sound = {
147      let inner = Rc::new (Inner {
148        raw:   self.raw(),
149        owned: false
150      });
151      let system = self.system.clone();
152      Sound { inner, system }
153    };
154    SoundRef { sound }
155  }
156
157  /// Retrieves the inside and outside angles of the sound projection cone.
158  ///
159  /// Returns `(insideconeangle, outsideconeangle, outsidevolume)`.
160  #[inline]
161  pub fn get_3d_cone_settings (&self) -> Result <(f32, f32, f32), Error> {
162    let mut insideconeangle  = 0.0;
163    let mut outsideconeangle = 0.0;
164    let mut outsidevolume    = 0.0;
165    unsafe {
166      fmod_result!(ll::FMOD_Sound_Get3DConeSettings (self.raw(),
167        &mut insideconeangle, &mut outsideconeangle, &mut outsidevolume
168      ))?;
169    }
170    Ok ((insideconeangle, outsideconeangle, outsidevolume))
171  }
172
173  /// Retrieve the minimum and maximum audible distance for a sound.
174  #[inline]
175  pub fn get_3d_min_max_distance (&self) -> Result <(f32, f32), Error> {
176    let mut min = 0.0;
177    let mut max = 0.0;
178    unsafe {
179      fmod_result!(
180        ll::FMOD_Sound_Get3DMinMaxDistance (self.raw(), &mut min, &mut max)
181      )?;
182    }
183    Ok ((min, max))
184  }
185
186  /// Retrieves a sound's default attributes for when it is played on a channel
187  /// with `Sound::play()`.
188  ///
189  /// Returns `(frequency, priority)`.
190  #[inline]
191  pub fn get_defaults (&self) -> Result <(f32, i32), Error> {
192    let mut frequency = 0.0;
193    let mut priority  = 0;
194    unsafe {
195      fmod_result!(
196        ll::FMOD_Sound_GetDefaults (self.raw(), &mut frequency, &mut priority)
197      )?;
198    }
199    Ok ((frequency, priority))
200  }
201
202  /// Returns format information about the sound.
203  ///
204  /// Returns `(type, format, channels, bits)`.
205  #[inline]
206  pub fn get_format (&self) -> Result <(Type, Format, i32, i32), Error> {
207    let mut type_    = 0;
208    let mut format   = 0;
209    let mut channels = 0;
210    let mut bits     = 0;
211    unsafe {
212      fmod_result!(
213        ll::FMOD_Sound_GetFormat (self.raw(),
214          &mut type_, &mut format, &mut channels, &mut bits)
215      )?;
216    }
217    let type_  = Type::from_ll (type_);
218    let format = Format::from_ll (format);
219    Ok ((type_, format, channels, bits))
220  }
221
222  #[inline]
223  pub fn get_length (&self, timeunit : Timeunit) -> Result <u32, Error> {
224    let mut length = 0;
225    unsafe {
226      fmod_result!(
227        ll::FMOD_Sound_GetLength (self.raw(), &mut length, timeunit.bits())
228      )?;
229    }
230    Ok (length)
231  }
232
233  #[inline]
234  pub fn get_loop_count (&self) -> Result <i32, Error> {
235    let mut loopcount = 0;
236    unsafe {
237      fmod_result!(ll::FMOD_Sound_GetLoopCount (self.raw(), &mut loopcount))?;
238    }
239    Ok (loopcount)
240  }
241
242  #[inline]
243  pub fn get_loop_points (&self,
244    loopstarttype : Timeunit, loopendtype : Timeunit
245  ) -> Result <(u32, u32), Error> {
246    let mut loopstart = 0;
247    let mut loopend   = 0;
248    unsafe {
249      fmod_result!(ll::FMOD_Sound_GetLoopPoints (self.raw(),
250        &mut loopstart, loopstarttype.bits(),
251        &mut loopend, loopendtype.bits())
252      )?;
253    }
254    Ok ((loopstart, loopend))
255  }
256
257  #[inline]
258  pub fn get_mode (&self) -> Result <Mode, Error> {
259    let mut mode = 0;
260    unsafe {
261      fmod_result!(ll::FMOD_Sound_GetMode (self.raw(), &mut mode))?;
262    }
263    Mode::from_bits (mode).ok_or_else (||{
264      log::error!("sound get mode bits not valid: {mode:032b}");
265      Error::InvalidParam
266    })
267  }
268
269  /// Retrieves the state a sound is in after `Mode::NONBLOCKING` has been used
270  /// to open it, or the state of the streaming buffer.
271  ///
272  /// Returns `(openstate, percentbuffered, starving, diskbusy)`.
273  #[inline]
274  pub fn get_open_state (&self) -> Result <(Openstate, u32, bool, bool), Error> {
275    let mut openstate       = 0;
276    let mut percentbuffered = 0;
277    let mut starving        = 0;
278    let mut diskbusy        = 0;
279    unsafe {
280      fmod_result!(
281        ll::FMOD_Sound_GetOpenState (self.raw(),
282          &mut openstate, &mut percentbuffered, &mut starving, &mut diskbusy)
283      )?;
284    }
285    let openstate = Openstate::from_ll (openstate);
286    let starving  = starving != 0;
287    let diskbusy  = diskbusy != 0;
288    Ok ((openstate, percentbuffered, starving, diskbusy))
289  }
290
291  #[inline]
292  pub fn get_name (&self) -> Result <String, Error> {
293    // NB: assuming names are not longer than 256 bytes
294    let namelen = 256i32;
295    #[expect(clippy::cast_sign_loss)]
296    let mut name = vec![0; namelen as usize];
297    unsafe {
298      fmod_result!(
299        ll::FMOD_Sound_GetName (self.raw(), name.as_mut_ptr() as *mut i8, namelen)
300      )?;
301    }
302    name.retain (|c| *c != 0x0);
303    String::from_utf8 (name).map_err (|e|{
304      log::error!("sound get name string invalid utf8: {e}");
305      Error::InvalidString
306    })
307  }
308
309  #[inline]
310  pub fn get_num_sub_sounds (&self) -> Result <i32, Error> {
311    let mut numsubsounds = 0;
312    unsafe {
313      fmod_result!(
314        ll::FMOD_Sound_GetNumSubSounds (self.raw(), &mut numsubsounds)
315      )?;
316    }
317    Ok (numsubsounds)
318  }
319
320  #[inline]
321  pub fn get_num_sync_points (&self) -> Result <i32, Error> {
322    let mut numsyncpoints = 0;
323    unsafe {
324      fmod_result!(
325        ll::FMOD_Sound_GetNumSyncPoints (self.raw(), &mut numsyncpoints)
326      )?;
327    }
328    Ok (numsyncpoints)
329  }
330
331  /// Retrieves the number of tags belonging to a sound.
332  ///
333  /// Returns `(numtags, numtagsupdated)`:
334  ///
335  /// - `numtags` -- Number of tags in the sound.
336  /// - `numtagsupdated` -- Number of tags updated since this function was last
337  ///   called.
338  #[inline]
339  pub fn get_num_tags (&self) -> Result <(i32, i32), Error> {
340    let mut numtags = 0;
341    let mut numtagsupdated = 0;
342    unsafe {
343      fmod_result!(
344        ll::FMOD_Sound_GetNumTags (self.raw(), &mut numtags, &mut numtagsupdated)
345      )?;
346    }
347    Ok ((numtags, numtagsupdated))
348  }
349
350  /// Start playing this sound, returning a handle to the virtual channel that
351  /// was assigned.
352  ///
353  /// If another sound is played and there are no free channels, this channel
354  /// handle may be *stolen*, invalidating it and causing all subsequent
355  /// `Channel` method calls to return `Error::ChannelStolen`. The channel
356  /// handle will also be invalidated by the next `system.update()` if
357  /// (non-looping) playback reaches the end of the sound, or if
358  /// `channel.stop()` is called.
359  ///
360  /// The Channel object will hold a reference to this sound, preventing it from
361  /// being dropped until the channel is also dropped. Note that this reference
362  /// will remain active until the channel object is dropped, even if the
363  /// channel handle has been invalidated.
364  ///
365  /// If the sound is dropped and it is currently playing on some channel(s)
366  /// (for which no channel object(s) are alive), those channels will cease
367  /// playback.
368  #[inline]
369  pub fn play (&mut self, channel_group : Option <&mut ChannelGroup>, paused : bool)
370    -> Result <Channel, Error>
371  {
372    let channel_group = channel_group
373      .map_or (std::ptr::null_mut(), ChannelGroup::raw_mut);
374    let mut channel   = std::ptr::null_mut();
375    unsafe {
376      fmod_result!(ll::FMOD_System_PlaySound (
377        self.system.raw_mut(), self.raw(), channel_group, paused as i32,
378          &mut channel)
379      )?;
380    }
381    Ok (Channel::from_raw_parts (channel, self.sound_ref()))
382  }
383
384  /// Set default frequency and priority with calls to `play()`.
385  ///
386  /// `priority` is 0-255 with '0' = high priority, '128' = default; note the
387  /// low-level FMOD uses an `i32` for this argument and accepts a range of
388  /// `0-256`, here we use a `u8` to constrain the range of inputs which also
389  /// makes the priority `256` unusable.
390  #[inline]
391  pub fn set_defaults (&self, frequency : f32, priority : u8)
392    -> Result <(), Error>
393  {
394    unsafe {
395      fmod_result!(
396        ll::FMOD_Sound_SetDefaults (self.raw(), frequency, priority as i32)
397      )
398    }
399  }
400
401  #[inline]
402  pub fn set_mode (&mut self, mode : Mode) -> Result <(), Error> {
403    unsafe {
404      fmod_result!(ll::FMOD_Sound_SetMode (self.raw(), mode.bits()))
405    }
406  }
407}
408
409impl Deref for SoundRef {
410  type Target = Sound;
411  fn deref (&self) -> &Sound {
412    &self.sound
413  }
414}
415
416impl DerefMut for SoundRef {
417  fn deref_mut (&mut self) -> &mut Sound {
418    &mut self.sound
419  }
420}
421
422impl std::fmt::Debug for Inner {
423  fn fmt (&self, f : &mut std::fmt::Formatter) -> std::fmt::Result {
424    write!(f, "Inner {{ raw: {:p}, owned: {} }}", self.raw, self.owned)
425  }
426}
427
428impl Drop for Inner {
429  fn drop (&mut self) {
430    if self.owned {
431      unsafe {
432        let _ = fmod_result!(ll::FMOD_Sound_Release (self.raw)).map_err (|err|
433          log::error!("error releasing FMOD Sound@{self:?}: {err:?}"));
434      }
435    }
436  }
437}
438
439impl Createsoundexinfo {
440  pub fn to_ll (&mut self) -> ll::FMOD_CREATESOUNDEXINFO {
441    #[expect(clippy::cast_possible_truncation)]
442    ll::FMOD_CREATESOUNDEXINFO {
443      cbsize:               size_of::<ll::FMOD_CREATESOUNDEXINFO>() as i32,
444      length:               self.length,
445      fileoffset:           self.fileoffset,
446      numchannels:          self.numchannels,
447      defaultfrequency:     self.defaultfrequency,
448      format:               self.format as ll::FMOD_SOUND_FORMAT,
449      decodebuffersize:     self.decodebuffersize,
450      initialsubsound:      self.initialsubsound,
451      numsubsounds:         self.numsubsounds,
452      inclusionlist:        if !self.inclusionlist.is_empty() {
453        self.inclusionlist.as_mut_ptr()
454      } else {
455        std::ptr::null_mut()
456      },
457      inclusionlistnum:     self.inclusionlist.len() as i32,
458      pcmreadcallback:      self.pcmreadcallback,
459      pcmsetposcallback:    self.pcmsetposcallback,
460      nonblockcallback:     self.nonblockcallback,
461      dlsname:              if !self.dlsname.as_bytes().is_empty() {
462        self.dlsname.as_c_str().as_ptr() as *mut std::os::raw::c_char
463      } else {
464        std::ptr::null_mut()
465      },
466      encryptionkey:        if !self.encryptionkey.as_bytes().is_empty() {
467        self.encryptionkey.as_c_str().as_ptr() as *mut std::os::raw::c_char
468      } else {
469        std::ptr::null_mut()
470      },
471      maxpolyphony:         self.maxpolyphony,
472      userdata:             std::ptr::null_mut(), // TODO
473      suggestedsoundtype:   self.suggestedsoundtype as ll::FMOD_SOUND_TYPE,
474      fileuseropen:         self.fileuseropen,
475      fileuserclose:        self.fileuserclose,
476      fileuserread:         self.fileuserread,
477      fileuserseek:         self.fileuserseek,
478      fileuserasyncread:    self.fileuserasyncread,
479      fileuserasynccancel:  self.fileuserasynccancel,
480      fileuserdata:         std::ptr::null_mut(), // TODO
481      filebuffersize:       self.filebuffersize,
482      channelorder:         self.channelorder as ll::FMOD_CHANNELORDER,
483      channelmask:          self.channelmask.bits(),
484      initialsoundgroup:    self.initialsoundgroup.as_mut()
485        .map_or (std::ptr::null_mut(), SoundGroup::raw_mut),
486      initialseekposition:  self.initialseekposition,
487      initialseekpostype:   self.initialseekpostype.bits(),
488      ignoresetfilesystem:  self.ignoresetfilesystem,
489      audioqueuepolicy:     self.audioqueuepolicy,
490      minmidigranularity:   self.minmidigranularity,
491      nonblockthreadid:     self.nonblockthreadid,
492      fsbguid:              self.fsbguid.as_mut().map_or (
493        std::ptr::null_mut(),
494        |guid| std::ptr::from_mut::<ll::FMOD_GUID> (guid.as_mut_ll()))
495    }
496  }
497}
498
499impl Format {
500  /// # Panics
501  ///
502  /// Panics if `ll` is not a valid sound format
503  pub fn from_ll (ll : ll::FMOD_SOUND_FORMAT) -> Self {
504    use num_traits::FromPrimitive;
505    // ll is i32 on windows and u32 on linux
506    #[allow(clippy::allow_attributes, clippy::unnecessary_cast, trivial_numeric_casts)]
507    Self::from_u32 (ll as u32).unwrap()
508  }
509}
510
511
512impl Type {
513  /// # Panics
514  ///
515  /// Panics if `ll` is not a valid sound type
516  pub fn from_ll (ll : ll::FMOD_SOUND_TYPE) -> Self {
517    use num_traits::FromPrimitive;
518    // ll is i32 on windows and u32 on linux
519    #[allow(clippy::allow_attributes, clippy::unnecessary_cast, trivial_numeric_casts)]
520    Self::from_u32 (ll as u32).unwrap()
521  }
522}
523
524
525impl Openstate {
526  /// # Panics
527  ///
528  /// Panics if `ll` is not a valid open state
529  pub fn from_ll (ll : ll::FMOD_OPENSTATE) -> Self {
530    use num_traits::FromPrimitive;
531    // ll is i32 on windows and u32 on linux
532    #[allow(clippy::allow_attributes, clippy::unnecessary_cast, trivial_numeric_casts)]
533    Self::from_u32 (ll as u32).unwrap()
534  }
535}