use std::path::Path;
use maudio_sys::ffi as sys;
use crate::{
audio::{channels::MonoExpansionMode, math::vec3::Vec3},
data_source::{private_data_source, AsSourcePtr, DataSourceRef},
engine::{
node_graph::nodes::{private_node, AsNodePtr},
Engine, EngineOps,
},
sound::{
notifier::EndNotifier, sound_flags::SoundFlags, sound_group::SoundGroup, Sound, SoundSource,
},
util::fence::Fence,
AsRawRef, Binding, MaResult,
};
pub struct SoundBuilder<'a, 'b> {
inner: sys::ma_sound_config,
engine: &'a Engine,
source: SoundSource<'a>,
owned_path: OwnedPathBuf,
pub(crate) fence: Option<Fence>, flags: SoundFlags,
group: Option<&'b SoundGroup<'b>>,
pub(crate) end_notifier: Option<EndNotifier>,
sound_state: SoundState,
}
impl<'a, 'b> AsRawRef for SoundBuilder<'a, 'b> {
type Raw = sys::ma_sound_config;
fn as_raw(&self) -> &Self::Raw {
&self.inner
}
}
#[derive(Default)]
pub(crate) struct SoundState {
pub(crate) min_distance: Option<f32>,
pub(crate) max_distance: Option<f32>,
pub(crate) rolloff: Option<f32>,
pub(crate) position: Option<Vec3>,
pub(crate) velocity: Option<Vec3>,
pub(crate) direction: Option<Vec3>,
pub(crate) start_playing: bool,
}
#[derive(Default)]
pub(crate) enum OwnedPathBuf {
#[default]
None,
#[cfg(unix)]
Utf8(std::ffi::CString),
#[cfg(windows)]
Wide(Vec<u16>),
}
impl<'a, 'b> SoundBuilder<'a, 'b> {
pub fn new(engine: &'a Engine) -> Self {
SoundBuilder::init(engine)
}
fn set_end_notifier(&mut self) -> EndNotifier {
let notifier = EndNotifier::new();
self.end_notifier = Some(notifier.clone());
self.inner.pEndCallbackUserData = notifier.as_user_data_ptr();
self.inner.endCallback = Some(crate::sound::notifier::on_end_callback);
notifier
}
pub fn mono_expansion_mode(&mut self, mode: MonoExpansionMode) -> &mut Self {
self.inner.monoExpansionMode = mode.into();
self
}
pub fn with_end_notifier(&'a mut self) -> MaResult<(Sound<'a>, EndNotifier)> {
self.set_source()?;
let notifier = self.set_end_notifier();
let sound = self.start_sound()?;
Ok((sound, notifier))
}
fn start_sound(&mut self) -> MaResult<Sound<'a>> {
if let Some(fence) = self.fence.clone() {
self.inner.pDoneFence = fence.to_raw()
};
let mut sound = match self.source {
SoundSource::DataSource(_) => {
if self.fence.is_some() {
return Err(crate::MaudioError::from_ma_result(
sys::ma_result_MA_INVALID_ARGS,
));
}
self.engine.new_sound_with_config_internal(Some(self))?
}
#[cfg(unix)]
SoundSource::FileUtf8(_) => self.engine.new_sound_with_config_internal(Some(self))?,
#[cfg(windows)]
SoundSource::FileWide(_) => self.engine.new_sound_with_config_internal(Some(self))?,
SoundSource::None => {
self.check_flags_without_source()?;
if self.fence.is_some() || self.sound_state.start_playing {
return Err(crate::MaudioError::from_ma_result(
sys::ma_result_MA_INVALID_ARGS,
));
}
self.engine.new_sound_with_config_internal(Some(self))?
}
};
self.configure_sound(&mut sound);
if self.source.is_valid() && self.sound_state.start_playing {
sound.play_sound()?;
}
Ok(sound)
}
pub fn build(&mut self) -> MaResult<Sound<'a>> {
self.set_source()?;
self.start_sound()
}
pub fn no_source(&mut self) -> &mut Self {
self.source = SoundSource::None;
self
}
pub fn file_path(&mut self, path: &Path) -> &mut Self {
self.source = SoundSource::None;
#[cfg(unix)]
{
self.source = SoundSource::FileUtf8(path.to_path_buf());
}
#[cfg(windows)]
{
self.source = SoundSource::FileWide(path.to_path_buf());
}
self
}
pub fn data_source<S: AsSourcePtr + ?Sized>(&mut self, source: &'a S) -> &mut Self {
self.source = SoundSource::DataSource(DataSourceRef::from_ptr(
private_data_source::source_ptr(source),
));
self
}
pub fn sound_group(&mut self, group: &'b SoundGroup) -> &mut Self {
self.inner.pInitialAttachment = private_node::node_ptr(&group.as_node());
self.group = Some(group);
self
}
pub fn fence(&mut self, fence: &Fence) -> &mut Self {
self.fence = Some(fence.clone());
self.async_load(true)
}
pub fn initial_attachment<N: AsNodePtr + ?Sized>(
&mut self,
node: &N,
input_bus: u32,
) -> &mut Self {
self.inner.pInitialAttachment = private_node::node_ptr(node);
self.inner.initialAttachmentInputBusIndex = input_bus;
self
}
pub fn channels_in(&mut self, ch: u32) -> &mut Self {
self.inner.channelsIn = ch;
self
}
pub fn channels_out(&mut self, ch: u32) -> &mut Self {
self.inner.channelsOut = ch;
self
}
pub fn flags(&mut self, flags: SoundFlags) -> &mut Self {
self.inner.flags = flags.bits();
self.flags = flags;
self
}
pub fn volume_smooth_frames(&mut self, pcm_frames: u32) -> &mut Self {
self.inner.volumeSmoothTimeInPCMFrames = pcm_frames;
self
}
pub fn range_begin_frames(&mut self, pcm_frames: u64) -> &mut Self {
self.inner.rangeBegInPCMFrames = pcm_frames;
self
}
pub fn range_end_frames(&mut self, pcm_frames: u64) -> &mut Self {
self.inner.rangeEndInPCMFrames = pcm_frames;
self
}
pub fn loop_begin_frames(&mut self, pcm_frames: u64) -> &mut Self {
self.inner.loopPointBegInPCMFrames = pcm_frames;
self
}
pub fn loop_end_frames(&mut self, pcm_frames: u64) -> &mut Self {
self.inner.loopPointEndInPCMFrames = pcm_frames;
self
}
pub fn seek_point_frames(&mut self, pcm_frames: u64) -> &mut Self {
self.inner.initialSeekPointInPCMFrames = pcm_frames;
self
}
pub fn volume_smooth_millis(&mut self, millis: f64) -> &mut Self {
self.inner.volumeSmoothTimeInPCMFrames = self.millis_to_frames(millis) as u32;
self
}
pub fn range_begin_millis(&mut self, millis: f64) -> &mut Self {
self.inner.rangeBegInPCMFrames = self.millis_to_frames(millis);
self
}
pub fn range_end_millis(&mut self, millis: f64) -> &mut Self {
self.inner.rangeEndInPCMFrames = self.millis_to_frames(millis);
self
}
pub fn loop_begin_millis(&mut self, millis: f64) -> &mut Self {
self.inner.loopPointBegInPCMFrames = self.millis_to_frames(millis);
self
}
pub fn loop_end_millis(&mut self, millis: f64) -> &mut Self {
self.inner.loopPointEndInPCMFrames = self.millis_to_frames(millis);
self
}
pub fn seek_point_millis(&mut self, millis: f64) -> &mut Self {
self.inner.initialSeekPointInPCMFrames = self.millis_to_frames(millis);
self
}
pub fn range_frames(&mut self, begin: u64, end: u64) -> &mut Self {
self.inner.rangeBegInPCMFrames = begin;
self.inner.rangeEndInPCMFrames = end;
self
}
pub fn range_millis(&mut self, begin: f64, end: f64) -> &mut Self {
self.inner.rangeBegInPCMFrames = self.millis_to_frames(begin);
self.inner.rangeEndInPCMFrames = self.millis_to_frames(end);
self
}
pub fn loop_frames(&mut self, begin: u64, end: u64) -> &mut Self {
self.inner.loopPointBegInPCMFrames = begin;
self.inner.loopPointEndInPCMFrames = end;
self
}
pub fn loop_millis(&mut self, begin: f64, end: f64) -> &mut Self {
self.inner.loopPointBegInPCMFrames = self.millis_to_frames(begin);
self.inner.loopPointEndInPCMFrames = self.millis_to_frames(end);
self
}
pub fn looping(&mut self, yes: bool) -> &mut Self {
let mut flags = SoundFlags::from_bits(self.inner.flags);
if yes {
flags.insert(SoundFlags::LOOPING);
} else {
flags.remove(SoundFlags::LOOPING);
}
self.inner.flags = flags.bits();
self.flags = flags;
self
}
pub fn streaming(&mut self, yes: bool) -> &mut Self {
let mut flags = SoundFlags::from_bits(self.inner.flags);
if yes {
flags.insert(SoundFlags::STREAM);
} else {
flags.remove(SoundFlags::STREAM);
}
self.inner.flags = flags.bits();
self.flags = flags;
self
}
pub fn decode(&mut self, yes: bool) -> &mut Self {
let mut flags = SoundFlags::from_bits(self.inner.flags);
if yes {
flags.insert(SoundFlags::DECODE);
} else {
flags.remove(SoundFlags::DECODE);
}
self.inner.flags = flags.bits();
self.flags = flags;
self
}
pub fn async_load(&mut self, yes: bool) -> &mut Self {
let mut flags = SoundFlags::from_bits(self.inner.flags);
if yes {
flags.insert(SoundFlags::ASYNC);
} else {
flags.remove(SoundFlags::ASYNC);
}
self.inner.flags = flags.bits();
self.flags = flags;
self
}
pub fn min_distance(&mut self, d: f32) -> &mut Self {
self.sound_state.min_distance = Some(d);
self
}
pub fn max_distance(&mut self, d: f32) -> &mut Self {
self.sound_state.max_distance = Some(d);
self
}
pub fn rolloff(&mut self, r: f32) -> &mut Self {
self.sound_state.rolloff = Some(r);
self
}
pub fn position(&mut self, position: Vec3) -> &mut Self {
self.sound_state.position = Some(position);
self
}
pub fn velocity(&mut self, velocity: Vec3) -> &mut Self {
self.sound_state.velocity = Some(velocity);
self
}
pub fn direction(&mut self, direction: Vec3) -> &mut Self {
self.sound_state.direction = Some(direction);
self
}
pub fn start_playing(&mut self, yes: bool) -> &mut Self {
self.sound_state.start_playing = yes;
self
}
#[inline]
pub(crate) fn millis_to_frames(&self, millis: f64) -> u64 {
if !millis.is_finite() || millis <= 0.0 {
return 0;
}
let sr = self.engine.sample_rate() as f64;
(millis.max(0.0) * sr / 1000.0).round() as u64
}
#[inline]
pub(crate) fn seconds_to_frames(&self, seconds: f64) -> u64 {
if !seconds.is_finite() || seconds <= 0.0 {
return 0;
}
let sr = self.engine.sample_rate() as f64;
(seconds.max(0.0) * sr).round() as u64
}
fn configure_sound(&self, sound: &mut Sound) {
if let Some(min_d) = self.sound_state.min_distance {
sound.set_min_distance(min_d)
};
if let Some(max_d) = self.sound_state.max_distance {
sound.set_max_distance(max_d)
};
if let Some(r) = self.sound_state.rolloff {
sound.set_rolloff(r);
}
if let Some(p) = self.sound_state.position {
sound.set_position(p);
}
if let Some(v) = self.sound_state.velocity {
sound.set_velocity(v);
}
if let Some(d) = self.sound_state.direction {
sound.set_direction(d);
}
}
fn check_flags_without_source(&self) -> MaResult<()> {
let invalid_flags: SoundFlags =
SoundFlags::STREAM | SoundFlags::DECODE | SoundFlags::ASYNC | SoundFlags::WAIT_INIT;
if self.flags.intersects(invalid_flags) {
return Err(crate::MaudioError::from_ma_result(
sys::ma_result_MA_INVALID_ARGS,
));
}
Ok(())
}
fn set_source(&mut self) -> MaResult<()> {
let null_fields = |cfg: &mut SoundBuilder| {
cfg.inner.pDataSource = core::ptr::null_mut();
cfg.inner.pFilePath = core::ptr::null();
cfg.inner.pFilePathW = core::ptr::null();
};
match self.source {
SoundSource::None => null_fields(self),
SoundSource::DataSource(src) => {
null_fields(self);
self.inner.pDataSource = private_data_source::source_ptr(&src);
}
#[cfg(unix)]
SoundSource::FileUtf8(ref p) => {
let cstring = crate::engine::cstring_from_path(p)?;
null_fields(self);
self.inner.pFilePath = cstring.as_ptr();
self.owned_path = OwnedPathBuf::Utf8(cstring); }
#[cfg(windows)]
SoundSource::FileWide(ref p) => {
let wide_path = crate::engine::wide_null_terminated(p);
null_fields(self);
self.inner.pFilePathW = wide_path.as_ptr();
self.owned_path = OwnedPathBuf::Wide(wide_path); }
}
Ok(())
}
}
impl<'a, 'b> SoundBuilder<'a, 'b> {
pub(crate) fn init(inner: &'a Engine) -> Self {
let ptr = unsafe { sys::ma_sound_config_init_2(inner.to_raw()) };
let state = SoundState::default();
Self {
inner: ptr,
engine: inner,
source: SoundSource::None,
owned_path: OwnedPathBuf::None,
group: None,
fence: None,
flags: SoundFlags::NONE,
end_notifier: None,
sound_state: state,
}
}
}
#[cfg(test)]
mod test {
use crate::engine::Engine;
#[test]
fn sound_builder_test_basic() {
let engine = Engine::new_for_tests().unwrap();
let _sound = engine.sound_config().channels_in(1).build().unwrap();
}
}