device 0.0.4

A generative engine
use super::*;

#[derive(Debug, Snafu)]
#[snafu(context(suffix(false)), visibility(pub(crate)))]
pub(crate) enum Error {
  #[snafu(display(
    "app exited with errors{}",
    Self::additional_error_message(additional.len(), " "),
  ))]
  AppExit {
    backtrace: Option<Backtrace>,
    source: Box<Error>,
    additional: Vec<Error>,
  },
  #[snafu(display("failed to run app"))]
  AppRun {
    backtrace: Option<Backtrace>,
    source: winit::error::EventLoopError,
  },
  #[snafu(display("failed to build audio input stream"))]
  AudioBuildInputStream {
    backtrace: Option<Backtrace>,
    source: cpal::BuildStreamError,
  },
  #[snafu(display("failed to get default audio output stream"))]
  AudioBuildOutputStream {
    backtrace: Option<Backtrace>,
    source: cpal::BuildStreamError,
  },
  #[snafu(display("failed to get default audio input device"))]
  AudioDefaultInputDevice { backtrace: Option<Backtrace> },
  #[snafu(display("failed to get default audio output device"))]
  AudioDefaultOutputDevice { backtrace: Option<Backtrace> },
  #[snafu(display("failed to get audio device name"))]
  AudioDeviceName {
    backtrace: Option<Backtrace>,
    source: cpal::DeviceNameError,
  },
  #[snafu(display("failed to enumerate audio devices"))]
  AudioDevices {
    backtrace: Option<Backtrace>,
    source: cpal::DevicesError,
  },
  #[snafu(display("failed to play audio input stream"))]
  AudioPlayStream {
    backtrace: Option<Backtrace>,
    source: cpal::PlayStreamError,
  },
  #[snafu(display("failed to get supported stream config"))]
  AudioSupportedStreamConfig { backtrace: Option<Backtrace> },
  #[snafu(display("failed to get supported stream configs"))]
  AudioSupportedStreamConfigs {
    backtrace: Option<Backtrace>,
    source: cpal::SupportedStreamConfigsError,
  },
  #[snafu(display("failed to deserialize config file at `{path}`"))]
  ConfigDeserialize {
    backtrace: Option<Backtrace>,
    path: Utf8PathBuf,
    source: serde_yaml::Error,
  },
  #[snafu(display("failed to create overlay renderer"))]
  CreateOverlayRenderer {
    backtrace: Option<Backtrace>,
    source: vello::Error,
  },
  #[snafu(display("failed to create surface"))]
  CreateSurface {
    backtrace: Option<Backtrace>,
    source: wgpu::CreateSurfaceError,
  },
  #[snafu(display("failed to create window"))]
  CreateWindow {
    backtrace: Option<Backtrace>,
    source: winit::error::OsError,
  },
  #[snafu(display("failed to get current texture"))]
  CurrentTexture {
    backtrace: Option<Backtrace>,
    source: wgpu::SurfaceError,
  },
  #[snafu(display("failed to get default config"))]
  DefaultConfig { backtrace: Option<Backtrace> },
  #[snafu(display("failed to build event loop"))]
  EventLoopBuild {
    backtrace: Option<Backtrace>,
    source: winit::error::EventLoopError,
  },
  #[snafu(display("I/O error at `{path}`"))]
  FilesystemIo {
    path: Utf8PathBuf,
    backtrace: Option<Backtrace>,
    source: io::Error,
  },
  #[snafu(display("could not get home directory"))]
  Home { backtrace: Option<Backtrace> },
  #[snafu(display("internal error: {message}"))]
  Internal {
    backtrace: Option<Backtrace>,
    message: String,
  },
  #[snafu(display("failed to initialize MIDI input"))]
  MidiInputInit {
    backtrace: Option<Backtrace>,
    source: midir::InitError,
  },
  #[snafu(display("failed to connect to MIDI port"))]
  MidiInputPortConnect {
    backtrace: Option<Backtrace>,
    source: midir::ConnectError<midir::MidiInput>,
  },
  #[snafu(display("failed to initialize MIDI output"))]
  MidiOutputInit {
    backtrace: Option<Backtrace>,
    source: midir::InitError,
  },
  #[snafu(display("failed to get MIDI port info"))]
  MidiPortInfo {
    backtrace: Option<Backtrace>,
    source: midir::PortInfoError,
  },
  #[snafu(display("no music directory configured"))]
  Music { backtrace: Option<Backtrace> },
  #[snafu(display("path not valid unicode: `{}`", path.display()))]
  PathUnicode {
    backtrace: Option<Backtrace>,
    path: std::path::PathBuf,
    source: camino::FromPathError,
  },
  #[snafu(display("failed to decode PNG at `{path}`"))]
  PngDecode {
    backtrace: Option<Backtrace>,
    path: Utf8PathBuf,
    source: png::DecodingError,
  },
  #[snafu(display("PNG has unsupported format {color_type:?} {bit_depth:?}: `{path}`"))]
  PngDecodeFormat {
    backtrace: Option<Backtrace>,
    bit_depth: png::BitDepth,
    color_type: png::ColorType,
    path: Utf8PathBuf,
  },
  #[snafu(display("PNG too large to fit in memory: `{path}`"))]
  PngDecodeSize {
    backtrace: Option<Backtrace>,
    path: Utf8PathBuf,
  },
  #[snafu(display("failed to encode PNG: `{path}`"))]
  PngEncode {
    backtrace: Option<Backtrace>,
    path: Utf8PathBuf,
    source: png::EncodingError,
  },
  #[snafu(display("`--record` requires `--fps`"))]
  RecordRequiresFps { backtrace: Option<Backtrace> },
  #[snafu(display("failed to flush recorded video"))]
  RecordingFlush {
    backtrace: Option<Backtrace>,
    source: io::Error,
  },
  #[snafu(display("failed to invoke recording command"))]
  RecordingInvoke {
    backtrace: Option<Backtrace>,
    source: io::Error,
  },
  #[snafu(display("failed to join recording thread"))]
  RecordingJoin {
    backtrace: Option<Backtrace>,
    panic_value: Box<dyn Any + Send + 'static>,
  },
  #[snafu(display("recording command failed"))]
  RecordingStatus {
    backtrace: Option<Backtrace>,
    status: ExitStatus,
  },
  #[snafu(display("failed to wait for recording command"))]
  RecordingWait {
    backtrace: Option<Backtrace>,
    source: io::Error,
  },
  #[snafu(display("failed to write recorded video"))]
  RecordingWrite {
    backtrace: Option<Backtrace>,
    source: io::Error,
  },
  #[snafu(display(
    "rendering failed: {error}{}",
    Self::additional_error_message(additional.len(), ""),
  ))]
  Render {
    backtrace: Option<Backtrace>,
    error: wgpu::Error,
    additional: Vec<wgpu::Error>,
  },
  #[snafu(display("failed to render overlay"))]
  RenderOverlay {
    backtrace: Option<Backtrace>,
    source: vello::Error,
  },
  #[snafu(display("failed to poll renderer"))]
  RenderPoll {
    backtrace: Option<Backtrace>,
    source: wgpu::PollError,
  },
  #[snafu(display("failed to get adapter"))]
  RequestAdapter {
    backtrace: Option<Backtrace>,
    source: wgpu::RequestAdapterError,
  },
  #[snafu(display("failed to get device"))]
  RequestDevice {
    backtrace: Option<Backtrace>,
    source: wgpu::RequestDeviceError,
  },
  #[snafu(display("sample rate {sample_rate} is not divisible by fps {fps}"))]
  SamplesPerFrame {
    backtrace: Option<Backtrace>,
    fps: Fps,
    sample_rate: u32,
  },
  #[snafu(display("failed to reload shader template from `{path}`"))]
  ShaderReload {
    backtrace: Option<Backtrace>,
    path: Utf8PathBuf,
    source: boilerplate::Error,
  },
  #[snafu(
    display(
      "more than one match for song: {}",
      matches.iter().map(ToString::to_string).collect::<Vec<String>>().join(", ")
    )
  )]
  SongAmbiguous {
    backtrace: Option<Backtrace>,
    matches: Vec<Utf8PathBuf>,
  },
  #[snafu(display("could not match song `{song}`"))]
  SongMatch {
    backtrace: Option<Backtrace>,
    song: Regex,
  },
  #[snafu(display("invalid song regex"))]
  SongRegex {
    backtrace: Option<Backtrace>,
    source: regex::Error,
  },
  #[snafu(display("I/O error finding song"))]
  SongWalk {
    backtrace: Option<Backtrace>,
    source: walkdir::Error,
  },
  #[snafu(display("I/O error creating tempdir"))]
  TempdirIo {
    backtrace: Option<Backtrace>,
    source: io::Error,
  },
  #[snafu(display("failed to spawn thread `{name}`"))]
  ThreadSpawn {
    backtrace: Option<Backtrace>,
    name: String,
    source: io::Error,
  },
  #[snafu(display("Surface not compatible with adapter"))]
  UnsupportedSurfaceAdapter { backtrace: Option<Backtrace> },
  #[snafu(display("Unsupported surface format `{format}`"))]
  UnsupportedSurfaceFormat {
    backtrace: Option<Backtrace>,
    format: ImageFormat,
  },
  #[snafu(display("Unsupported surface present mode `{present_mode}`"))]
  UnsupportedSurfacePresentMode {
    backtrace: Option<Backtrace>,
    present_mode: PresentMode,
  },
  #[snafu(display("default texture format {texture_format:?} not supported"))]
  UnsupportedTextureFormat {
    backtrace: Option<Backtrace>,
    texture_format: TextureFormat,
  },
  #[snafu(display("failed to create wave writer"))]
  WaveCreate {
    backtrace: Option<Backtrace>,
    path: Utf8PathBuf,
    source: hound::Error,
  },
  #[snafu(display("failed to finalize wave writer"))]
  WaveFinalize {
    backtrace: Option<Backtrace>,
    path: Utf8PathBuf,
    source: hound::Error,
  },
  #[snafu(display("failed to load audio track"))]
  WaveLoad {
    backtrace: Option<Backtrace>,
    source: fundsp::read::WaveError,
  },
  #[snafu(display("failed to resample audio"))]
  WaveResample {
    backtrace: Option<Backtrace>,
    source: rubato::ResampleError,
  },
  #[snafu(display("failed to construct audio resampler"))]
  WaveResamplerConstruction {
    backtrace: Option<Backtrace>,
    source: rubato::ResamplerConstructionError,
  },
  #[snafu(display("wave has fractional sample rate: {sample_rate}"))]
  WaveSampleRate {
    backtrace: Option<Backtrace>,
    sample_rate: f64,
  },
  #[snafu(display("failed to write wave samples"))]
  WaveWrite {
    backtrace: Option<Backtrace>,
    path: Utf8PathBuf,
    source: hound::Error,
  },
}

impl Error {
  fn additional_error_message(additional: usize, prefix: &str) -> String {
    if additional == 0 {
      return String::new();
    }

    let plural = if additional == 1 { "" } else { "s" };

    format!("{prefix}{additional} additional error{plural} suppressed")
  }

  pub(crate) fn internal(message: impl Into<String>) -> Self {
    Internal { message }.build()
  }
}