device 0.0.4

A generative engine
use super::*;

const DEFAULT_FPS: NonZeroU32 = NonZeroU32::new(60).unwrap();

const DEFAULT_SAMPLE_RATE: u32 = 48_000;

const PROGRESS_CHARS: &str = "█▉▊▋▌▍▎▏ ";

const TICK_CHARS: &str = concat!(
  "⠀⠁⠂⠃⠄⠅⠆⠇⡀⡁⡂⡃⡄⡅⡆⡇",
  "⠈⠉⠊⠋⠌⠍⠎⠏⡈⡉⡊⡋⡌⡍⡎⡏",
  "⠐⠑⠒⠓⠔⠕⠖⠗⡐⡑⡒⡓⡔⡕⡖⡗",
  "⠘⠙⠚⠛⠜⠝⠞⠟⡘⡙⡚⡛⡜⡝⡞⡟",
  "⠠⠡⠢⠣⠤⠥⠦⠧⡠⡡⡢⡣⡤⡥⡦⡧",
  "⠨⠩⠪⠫⠬⠭⠮⠯⡨⡩⡪⡫⡬⡭⡮⡯",
  "⠰⠱⠲⠳⠴⠵⠶⠷⡰⡱⡲⡳⡴⡵⡶⡷",
  "⠸⠹⠺⠻⠼⠽⠾⠿⡸⡹⡺⡻⡼⡽⡾⡿",
  "⢀⢁⢂⢃⢄⢅⢆⢇⣀⣁⣂⣃⣄⣅⣆⣇",
  "⢈⢉⢊⢋⢌⢍⢎⢏⣈⣉⣊⣋⣌⣍⣎⣏",
  "⢐⢑⢒⢓⢔⢕⢖⢗⣐⣑⣒⣓⣔⣕⣖⣗",
  "⢘⢙⢚⢛⢜⢝⢞⢟⣘⣙⣚⣛⣜⣝⣞⣟",
  "⢠⢡⢢⢣⢤⢥⢦⢧⣠⣡⣢⣣⣤⣥⣦⣧",
  "⢨⢩⢪⢫⢬⢭⢮⢯⣨⣩⣪⣫⣬⣭⣮⣯",
  "⢰⢱⢲⢳⢴⢵⢶⢷⣰⣱⣲⣳⣴⣵⣶⣷",
  "⢸⢹⢺⢻⢼⢽⢾⢿⣸⣹⣺⣻⣼⣽⣾⣿",
);

#[derive(Parser)]
pub(crate) struct Capture {
  #[arg(long)]
  duration: Option<NonZeroU32>,
}

impl Capture {
  pub(crate) fn run(self, options: Options, config: Config) -> Result {
    let sound_format = SoundFormat {
      sample_rate: DEFAULT_SAMPLE_RATE,
      channels: Tap::CHANNELS,
    };

    let mut tap = Tap::new(&options, sound_format.sample_rate);

    options.add_source(&config, &mut tap)?;

    let mut analyzer = Analyzer::new();

    let mut state = options.state();

    let resolution = options.resolution.unwrap_or(DEFAULT_RESOLUTION);

    let size = Size::new(
      options.width.unwrap_or(resolution),
      options.height.unwrap_or(resolution),
    );

    let (size, resolution) = options.size(size);

    let mut renderer = pollster::block_on(Renderer::new(
      options.image_format(),
      None,
      resolution,
      size,
      None,
    ))?;

    let fps = options.fps.unwrap_or(DEFAULT_FPS.try_into().unwrap());

    let spf = fps.spf(tap.format())?;

    let (tx, rx) = mpsc::channel();

    let frames = self
      .duration
      .map(|duration| u64::from(duration.get()) * u64::from(fps.fps().get()));

    let progress = if options.verbose {
      ProgressBar::hidden()
    } else if let Some(frames) = frames {
      ProgressBar::new(frames)
        .with_style(ProgressStyle::default_bar().progress_chars(PROGRESS_CHARS))
    } else {
      ProgressBar::new_spinner().with_style(ProgressStyle::default_spinner().tick_chars(TICK_CHARS))
    };

    let mut samples = vec![0.0; spf];

    let mut recorder = Recorder::new(fps, &options, renderer.size(), sound_format)?;

    let mut done = false;
    for frame in 0.. {
      if frames.map_or(done, |frames| frame == frames) {
        break;
      }

      progress.inc(1);

      done = tap.is_done();

      tap.write(&mut samples);

      let sound = tap.drain();
      analyzer.update(&sound, done, &state);
      renderer.render(&analyzer, &state, Instant::now())?;

      let tx = tx.clone();
      renderer.capture(move |image| {
        if let Err(err) = tx.send(image) {
          eprintln!("failed to send captured frame: {err}");
        }
      })?;

      renderer.poll()?;

      let image = rx.recv().unwrap();

      recorder.frame(frame, image, sound)?;

      state.tick(fps.duration());
    }

    progress.finish();

    recorder.finish(&options, &config)?;

    Ok(())
  }
}