pagurus 0.7.4

Ultra-portable game engine suited for offline 2D games powered by WebAssembly
Documentation
use crate::Result;
use orfail::Failure;

#[derive(Debug, Default)]
pub struct AudioData<B = Vec<u8>> {
    spec: AudioSpec,
    data: B,
}

impl AudioData<Vec<u8>> {
    pub fn new(spec: AudioSpec) -> Self {
        Self {
            spec,
            data: vec![0; spec.data_samples * spec.sample_format.bytes()],
        }
    }

    #[inline]
    pub fn write_sample(&mut self, i: usize, sample: impl Into<Sample>) {
        let sample = sample.into();
        match self.spec.sample_format {
            SampleFormat::I16Be => {
                self.data[i * 2..][..2].copy_from_slice(&sample.to_i16().to_be_bytes());
            }
            SampleFormat::I16Le => {
                self.data[i * 2..][..2].copy_from_slice(&sample.to_i16().to_le_bytes());
            }
            SampleFormat::F32Be => {
                self.data[i * 4..][..4].copy_from_slice(&sample.to_f32().to_be_bytes());
            }
            SampleFormat::F32Le => {
                self.data[i * 4..][..4].copy_from_slice(&sample.to_f32().to_le_bytes());
            }
        }
    }

    pub fn bytes_mut(&mut self) -> &mut [u8] {
        self.data.as_mut()
    }

    pub fn as_ref(&self) -> AudioData<&[u8]> {
        AudioData {
            spec: self.spec,
            data: &self.data,
        }
    }
}

impl<B: AsRef<[u8]>> AudioData<B> {
    pub fn spec(&self) -> AudioSpec {
        self.spec
    }

    pub fn samples(&self) -> Samples {
        Samples {
            spec: self.spec,
            data: self.data.as_ref(),
        }
    }

    pub fn bytes(&self) -> &[u8] {
        self.data.as_ref()
    }
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
pub struct AudioSpec {
    pub sample_format: SampleFormat,
    pub sample_rate: u16,
    pub data_samples: usize,
}

impl AudioSpec {
    pub const CHANNELS: u8 = 1;
}

#[derive(Debug)]
pub struct Samples<'a> {
    spec: AudioSpec,
    data: &'a [u8],
}

impl<'a> Samples<'a> {
    pub fn len(&self) -> usize {
        self.spec.data_samples
    }

    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }
}

impl<'a> Iterator for Samples<'a> {
    type Item = Sample;

    fn next(&mut self) -> Option<Self::Item> {
        if self.data.is_empty() {
            return None;
        }

        match self.spec.sample_format {
            SampleFormat::I16Be => {
                let v = i16::from_be_bytes([self.data[0], self.data[1]]);
                self.data = &self.data[2..];
                Some(Sample::I16(v))
            }
            SampleFormat::I16Le => {
                let v = i16::from_le_bytes([self.data[0], self.data[1]]);
                self.data = &self.data[2..];
                Some(Sample::I16(v))
            }
            SampleFormat::F32Be => {
                let v =
                    f32::from_be_bytes([self.data[0], self.data[1], self.data[2], self.data[3]]);
                self.data = &self.data[4..];
                Some(Sample::F32(v))
            }
            SampleFormat::F32Le => {
                let v =
                    f32::from_le_bytes([self.data[0], self.data[1], self.data[2], self.data[3]]);
                self.data = &self.data[4..];
                Some(Sample::F32(v))
            }
        }
    }
}

#[derive(Debug, Clone, Default, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "UPPERCASE")
)]
pub enum SampleFormat {
    #[default]
    I16Be = 0,
    I16Le = 1,
    F32Be = 2,
    F32Le = 3,
}

impl SampleFormat {
    pub const fn bytes(self) -> usize {
        match self {
            SampleFormat::I16Be => 2,
            SampleFormat::I16Le => 2,
            SampleFormat::F32Be => 4,
            SampleFormat::F32Le => 4,
        }
    }

    pub const fn as_u8(self) -> u8 {
        self as u8
    }

    pub fn from_u8(x: u8) -> Result<Self> {
        match x {
            0 => Ok(Self::I16Be),
            1 => Ok(Self::I16Le),
            2 => Ok(Self::F32Be),
            3 => Ok(Self::F32Le),
            _ => Err(Failure::new(format!("unknown audio sample format: {x}"))),
        }
    }
}

#[derive(Debug, Clone, Copy)]
pub enum Sample {
    I16(i16),
    F32(f32),
}

impl Sample {
    #[inline]
    pub fn to_i16(self) -> i16 {
        match self {
            Sample::I16(v) => v,
            Sample::F32(v) => {
                let v = v.clamp(-1.0, 1.0);
                if v < 0.0 {
                    (-v * f32::from(i16::MIN)) as i16
                } else {
                    (v * f32::from(i16::MAX)) as i16
                }
            }
        }
    }

    #[inline]
    pub fn to_f32(self) -> f32 {
        match self {
            Sample::F32(v) => v,
            Sample::I16(v) => {
                if v < 0 {
                    -f32::from(v) / f32::from(i16::MIN)
                } else {
                    f32::from(v) / f32::from(i16::MAX)
                }
            }
        }
    }
}

impl From<i16> for Sample {
    fn from(value: i16) -> Self {
        Self::I16(value)
    }
}

impl From<f32> for Sample {
    fn from(value: f32) -> Self {
        Self::F32(value)
    }
}