device 0.0.4

A generative engine
use super::*;

#[derive(Clone)]
pub(crate) struct Sound {
  format: SoundFormat,
  samples: Vec<f32>,
}

impl Sound {
  pub(crate) fn append(&mut self, mut other: Self) -> Result<(), String> {
    if other.format != self.format {
      return Err(format!(
        "format changed from {} to {}",
        self.format, other.format,
      ));
    }

    self.samples.append(&mut other.samples);

    Ok(())
  }

  pub(crate) fn downmix(&self) -> impl Iterator<Item = f32> {
    self
      .samples
      .chunks(self.format.channels.into())
      .map(|chunk| chunk.iter().sum::<f32>() / self.format.channels as f32)
  }

  pub(crate) fn duration(&self) -> Duration {
    const NANOS_PER_SECOND: u128 = 1_000_000_000;
    let nanoseconds =
      self.frames().into_u128() * NANOS_PER_SECOND / u128::from(self.format.sample_rate);
    Duration::new(
      (nanoseconds / NANOS_PER_SECOND).try_into().unwrap(),
      (nanoseconds % NANOS_PER_SECOND).try_into().unwrap(),
    )
  }

  pub(crate) fn empty(format: SoundFormat) -> Self {
    Self::new(format, Vec::new())
  }

  pub(crate) fn format(&self) -> SoundFormat {
    self.format
  }

  pub(crate) fn frames(&self) -> usize {
    self.samples.len() / self.format.channels.into_usize()
  }

  pub(crate) fn new(format: SoundFormat, samples: Vec<f32>) -> Self {
    Self { format, samples }
  }

  pub(crate) fn save(&self, path: &Utf8Path) -> Result {
    let mut writer = WavWriter::create(
      path,
      WavSpec {
        channels: self.format.channels,
        sample_rate: self.format.sample_rate,
        bits_per_sample: 32,
        sample_format: hound::SampleFormat::Float,
      },
    )
    .context(error::WaveCreate { path })?;

    for sample in &self.samples {
      writer
        .write_sample(*sample)
        .context(error::WaveWrite { path })?;
    }

    writer.finalize().context(error::WaveFinalize { path })?;

    Ok(())
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn duration() {
    let format = SoundFormat {
      channels: 2,
      sample_rate: 10,
    };
    assert_eq!(Sound::empty(format).duration(), Duration::from_secs(0));
    assert_eq!(
      Sound::new(format, vec![0.0; 10]).duration(),
      Duration::from_millis(500)
    );
    assert_eq!(
      Sound::new(format, vec![0.0; 20]).duration(),
      Duration::from_secs(1)
    );
  }
}