mirage-engine 0.2.0

Mirage, an immediate-mode 3D engine for simple games on desktop and the browser
Documentation
//! The mix's destination once it leaves the engine: the machine's audio
//! device, or nothing at all.

use crate::sound::data::SampleRate;
use crate::sound::mixer::Command;

#[cfg(not(target_arch = "wasm32"))]
use native as sys;
#[cfg(target_arch = "wasm32")]
use web as sys;

/// Device a game's sounds are played through.
///
/// A machine with no device to play to is silent: sound never stops a game
/// from starting.
pub(crate) struct Output(Option<sys::Backend>);

impl Output {
    /// Opens the machine's audio device, or plays nothing where it has
    /// none.
    pub(crate) fn open() -> Self {
        sys::open().map_or_else(Self::silent, |backend| Self(Some(backend)))
    }

    /// An output that plays nothing, for a game running with no device at
    /// all.
    pub(crate) const fn silent() -> Self {
        Self(None)
    }

    /// Rate every clip is resampled to at decode, or nothing where the
    /// output takes a clip at the clip's own rate and resamples for itself.
    pub(crate) fn rate(&self) -> Option<MixRate> {
        self.0.as_ref().and_then(sys::Backend::rate)
    }

    /// Passes the frame's commands to the mix, and does whatever else this
    /// frame needs to keep the mix fed, such as more of a streamed clip
    /// to schedule.
    pub(crate) fn frame(&mut self, commands: impl Iterator<Item = Command>) {
        let Some(backend) = &mut self.0 else {
            return;
        };
        backend.frame(commands);
    }

    /// Allows sound to start, which a browser does only once the player has
    /// done something.
    pub(crate) fn unlock(&mut self) {
        if let Some(backend) = &mut self.0 {
            backend.unlock();
        }
    }

    /// Whether the platform allows sound to start right now.
    ///
    /// A machine with no device to play to still reports true: what holds
    /// this false is the browser, not the device.
    pub(crate) fn unlocked(&self) -> bool {
        self.0.as_ref().is_none_or(sys::Backend::unlocked)
    }
}

/// The rate a mix runs at, which every clip it plays is resampled to.
///
/// Its rate is private to this module, so only an output states one: the rate
/// a clip is resampled to at decode and the rate the mix advances at are the
/// same value.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct MixRate(SampleRate);

impl MixRate {
    /// The rate a test mixes at, in place of a device's.
    #[cfg(test)]
    pub(crate) const fn chosen(rate: SampleRate) -> Self {
        Self(rate)
    }

    /// The rate itself, for the arithmetic frames and fades are paced by.
    pub(crate) const fn get(self) -> SampleRate {
        self.0
    }
}

#[cfg(not(target_arch = "wasm32"))]
mod native;
#[cfg(target_arch = "wasm32")]
mod web;