device 0.0.4

A generative engine
use super::*;

pub(crate) struct Analyzer {
  complex_frequencies: Vec<Complex<f32>>,
  frequencies: Vec<f32>,
  planner: FftPlanner<f32>,
  rms: f32,
  samples: Vec<f32>,
  scratch: Vec<Complex<f32>>,
}

impl Analyzer {
  pub(crate) fn frequencies(&self) -> &[f32] {
    &self.frequencies
  }

  pub(crate) fn new() -> Self {
    Self {
      complex_frequencies: Vec::new(),
      frequencies: Vec::new(),
      planner: FftPlanner::new(),
      rms: 0.0,
      samples: Vec::new(),
      scratch: Vec::new(),
    }
  }

  pub(crate) fn rms(&self) -> f32 {
    self.rms
  }

  pub(crate) fn samples(&self) -> &[f32] {
    &self.samples
  }

  pub(crate) fn update(&mut self, sound: &Sound, done: bool, state: &State) {
    if done {
      self.samples.clear();
    } else {
      let old = self.samples.len();
      self.samples.extend(sound.downmix());
      self
        .samples
        .drain(..self.samples.len().saturating_sub(128).min(old));
    }

    let samples = &self.samples[..self.samples.len() & !1];

    self.complex_frequencies.clear();
    self
      .complex_frequencies
      .extend(samples.iter().map(Complex::from));
    let fft = self.planner.plan_fft_forward(samples.len());
    let scratch_len = fft.get_inplace_scratch_len();
    if self.scratch.len() < scratch_len {
      self.scratch.resize(scratch_len, 0.0.into());
    }
    fft.process_with_scratch(
      &mut self.complex_frequencies,
      &mut self.scratch[..scratch_len],
    );

    let n = self.complex_frequencies.len();
    let half = n / 2;
    let spacing = sound.format().sample_rate as f32 / n as f32;
    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
    let threshold = (20.0 / spacing) as usize;
    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
    let cutoff = (15_000.0 / spacing) as usize;

    self.frequencies.clear();
    self.frequencies.extend(
      self
        .complex_frequencies
        .iter()
        .enumerate()
        .skip(threshold)
        .take(cutoff.min(half).saturating_sub(threshold))
        .map(|(i, c)| {
          let weight = if i == 0 || i == half { 1.0 } else { 2.0 };
          c.norm() * weight
        }),
    );
    self.rms = state.alpha
      * (self.frequencies.iter().map(|&f| f * f).sum::<f32>()
        / self.frequencies.len().max(1) as f32)
        .sqrt()
      + (1.0 - state.alpha) * self.rms;
  }
}