maudio 0.1.5

Rust bindings to the miniaudio library
Documentation
//! Resource-managed audio data source
use std::{marker::PhantomData, mem::MaybeUninit, path::Path};

use maudio_sys::ffi as sys;

use crate::{
    data_source::{private_data_source, AsSourcePtr, DataSourceRef, SharedSource},
    engine::resource::{
        resource_ffi, rm_notif::NotificationPipeline, rm_source_flags::RmSourceFlags, AsRmPtr,
        PendingResource,
    },
    sound::sound_builder::OwnedPathBuf,
    AsRawRef, Binding, MaResult,
};

/// Resource-managed audio data source.
///
/// This is a lightweight wrapper around a miniaudio
/// `ma_resource_manager_data_source`.
///
/// A source represents a decoded audio input that can be used by
/// sounds, nodes, or other playback components without fully loading
/// the entire audio into memory.
///
/// # Usage
///
/// Created via [`ResourceManagerSourceBuilder`], typically from a file
/// path or other registered resource.
///
/// ```ignore
/// # let rm = todo!();
/// let source = ResourceManagerSourceBuilder::new(&rm)
///     .file_path("audio.ogg".as_ref()) // with `vorbis` feature
///     .build()?;
/// ```
///
/// # Async loading
///
/// Construction always returns a [`PendingResource`].
/// When async flags are enabled, the source may not be immediately
/// ready and must be polled before use.
///
/// Otherwise, the builder returns once the resource is available and
/// [`PendingResource`] is `Ready`.
///
/// # Notes
///
/// - Decoding is handled internally by the resource manager.
/// - Does not guarantee full data residency in memory.
/// - Suitable for general playback and flexible data access.
pub struct ResourceManagerSource<'a, R: AsRmPtr + ?Sized> {
    inner: *mut sys::ma_resource_manager_data_source,
    pipeline_notif: Option<NotificationPipeline>,
    _format: PhantomData<R::Format>,
    _marker: PhantomData<&'a R>,
}

impl<'a, R: AsRmPtr + ?Sized> Binding for ResourceManagerSource<'a, R> {
    type Raw = *mut sys::ma_resource_manager_data_source;

    /// !!! unimplemented !!!
    fn from_ptr(_raw: Self::Raw) -> Self {
        unimplemented!()
    }

    fn to_raw(&self) -> Self::Raw {
        self.inner
    }
}

impl<'a, R: AsRmPtr> SharedSource for ResourceManagerSource<'a, R> {
    type Format = R::Format;
}

#[doc(hidden)]
impl<'a, R: AsRmPtr> AsSourcePtr for ResourceManagerSource<'a, R> {
    type __PtrProvider = private_data_source::ResourceManagerSourceProvider;
}

impl<'a, R: AsRmPtr> ResourceManagerSource<'a, R> {
    pub fn as_source(&'a self) -> DataSourceRef<'a> {
        debug_assert!(!self.to_raw().is_null());
        let ptr = self.to_raw().cast::<sys::ma_data_source>();
        DataSourceRef::from_ptr(ptr)
    }
}

// Private methods
impl<'a, R: AsRmPtr + ?Sized> ResourceManagerSource<'a, R> {
    fn new_copy_with_config(
        config: &ResourceManagerSourceBuilder<'a, R>,
        existing: &ResourceManagerSource<'a, R>,
    ) -> MaResult<Self> {
        let mut mem: Box<MaybeUninit<sys::ma_resource_manager_data_source>> =
            Box::new(MaybeUninit::uninit());

        resource_ffi::ma_resource_manager_data_source_init_copy(
            config.rm,
            existing,
            mem.as_mut_ptr(),
        )?;

        let inner: *mut sys::ma_resource_manager_data_source =
            Box::into_raw(mem) as *mut sys::ma_resource_manager_data_source;
        Ok(Self {
            inner,
            pipeline_notif: None, // config.pNotifications do not get carried over. PipeNotif will be lost.
            _format: PhantomData,
            _marker: PhantomData,
        })
    }

    fn new_with_config(config: &ResourceManagerSourceBuilder<'a, R>) -> MaResult<Self> {
        let mut mem: Box<MaybeUninit<sys::ma_resource_manager_data_source>> =
            Box::new(MaybeUninit::uninit());

        resource_ffi::ma_resource_manager_data_source_init_ex(config.rm, config, mem.as_mut_ptr())?;

        let inner: *mut sys::ma_resource_manager_data_source =
            Box::into_raw(mem) as *mut sys::ma_resource_manager_data_source;
        Ok(Self {
            inner,
            pipeline_notif: None,
            _format: PhantomData,
            _marker: PhantomData,
        })
    }
}

pub struct ResourceManagerSourceBuilder<'a, R: AsRmPtr + ?Sized> {
    rm: &'a R,
    inner: sys::ma_resource_manager_data_source_config,
    flags: RmSourceFlags,
    source: SourceBufSource<'a>,
    owned_path: OwnedPathBuf,
    pipeline_notif: Option<NotificationPipeline>,
}

impl<'a, R: AsRmPtr + ?Sized> AsRawRef for ResourceManagerSourceBuilder<'a, R> {
    type Raw = sys::ma_resource_manager_data_source_config;

    fn as_raw(&self) -> &Self::Raw {
        &self.inner
    }
}

#[derive(PartialEq)]
pub enum SourceBufSource<'a> {
    None,
    #[cfg(unix)]
    FileUtf8(&'a Path),
    #[cfg(windows)]
    FileWide(&'a Path),
}

// TODO:
// initialSeekPointInPCMFrames;
// rangeBegInPCMFrames;
// rangeEndInPCMFrames;
// loopPointBegInPCMFrames;
// loopPointEndInPCMFrames;
impl<'a, R: AsRmPtr> ResourceManagerSourceBuilder<'a, R> {
    pub fn new(rm: &'a R) -> Self {
        let inner = unsafe { sys::ma_resource_manager_data_source_config_init() };
        Self {
            rm,
            inner,
            flags: RmSourceFlags::NONE,
            source: SourceBufSource::None,
            owned_path: OwnedPathBuf::None,
            pipeline_notif: None,
        }
    }

    pub fn flags(&mut self, flags: RmSourceFlags) -> &mut Self {
        self.inner.flags = flags.bits();
        self.flags = flags;
        self
    }

    pub fn file_path(&mut self, path: &'a Path) -> &mut Self {
        self.source = SourceBufSource::None;
        #[cfg(unix)]
        {
            self.source = SourceBufSource::FileUtf8(path);
        }
        #[cfg(windows)]
        {
            self.source = SourceBufSource::FileWide(path);
        }
        self
    }

    pub fn notification(&mut self, notif: NotificationPipeline) -> &mut Self {
        self.inner.pNotifications = notif.as_raw_ptr();
        self.pipeline_notif = Some(notif);
        self
    }

    fn set_source(&mut self) -> MaResult<()> {
        let null_fields = |cfg: &mut ResourceManagerSourceBuilder<'a, R>| {
            cfg.inner.pFilePath = core::ptr::null();
            cfg.inner.pFilePathW = core::ptr::null();
        };
        match self.source {
            SourceBufSource::None => null_fields(self),
            #[cfg(unix)]
            SourceBufSource::FileUtf8(p) => {
                use crate::sound::sound_builder::OwnedPathBuf;

                null_fields(self);
                let cstring = crate::engine::cstring_from_path(p)?;
                self.inner.pFilePath = cstring.as_ptr();
                self.owned_path = OwnedPathBuf::Utf8(cstring); // keep the pointer alive
            }
            #[cfg(windows)]
            SourceBufSource::FileWide(p) => {
                null_fields(self);
                let wide_path = crate::engine::wide_null_terminated(p);
                self.inner.pFilePathW = wide_path.as_ptr();
                self.owned_path = OwnedPathBuf::Wide(wide_path); // keep the pointer alive
            }
        }
        Ok(())
    }

    pub fn async_load(&mut self, yes: bool) -> &mut Self {
        let mut flags = RmSourceFlags::from_bits(self.inner.flags);
        if yes {
            flags.insert(RmSourceFlags::ASYNC);
        } else {
            flags.remove(RmSourceFlags::ASYNC);
        }
        self.inner.flags = flags.bits();
        self.flags = flags;
        self
    }

    pub(crate) fn build_internal(&mut self) -> MaResult<ResourceManagerSource<'a, R>> {
        self.set_source()?;
        ResourceManagerSource::<R>::new_with_config(self)
    }

    pub fn build(&mut self) -> MaResult<PendingResource<ResourceManagerSource<'a, R>>> {
        let mut buf = self.build_internal()?;
        if self.flags.intersects(RmSourceFlags::ASYNC) {
            return Ok(PendingResource::Pending { inner: Some(buf) });
        }
        // Clone the pipeline notifications to prevent them from getting dropped
        buf.pipeline_notif = self.pipeline_notif.clone();
        Ok(PendingResource::Ready { inner: buf })
    }

    pub fn build_copy(
        &mut self,
        existing: &ResourceManagerSource<'a, R>,
    ) -> MaResult<ResourceManagerSource<'a, R>> {
        self.set_source()?;
        ResourceManagerSource::new_copy_with_config(self, existing)
    }
}

impl<'a, R: AsRmPtr + ?Sized> Drop for ResourceManagerSource<'a, R> {
    fn drop(&mut self) {
        let _ = resource_ffi::ma_resource_manager_data_source_uninit(self);
        drop(unsafe { Box::from_raw(self.to_raw()) });
    }
}

#[cfg(test)]
mod test {
    use crate::{
        engine::resource::{
            rm_builder::ResourceManagerBuilder, rm_source::ResourceManagerSourceBuilder,
            tiny_test_wav_mono,
        },
        test_assets::temp_file::{unique_tmp_path, TempFileGuard},
    };

    #[test]
    fn test_res_man_data_source_builder_basic_init() {
        let rm = ResourceManagerBuilder::new().build_f32().unwrap();

        let wav = tiny_test_wav_mono(20);
        let path_guard = TempFileGuard::new(unique_tmp_path("wav"));
        let path = path_guard.path().to_path_buf();
        std::fs::write(&path, &wav).unwrap();

        let _ = ResourceManagerSourceBuilder::new(&rm)
            .file_path(&path)
            .build()
            .unwrap();
    }
}