diff --git a/Cargo.lock b/Cargo.lock index 9d188b3..3132c66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,6 +73,7 @@ dependencies = [ "hound", "image", "rfd", + "thiserror", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8a7123f..9334143 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,4 @@ hound = "*" image = "0.24.0" eframe = "0.16.0" rfd = "0.7.0" +thiserror = "1.0.30" diff --git a/src/decoder.rs b/src/decoder.rs index 7a36643..6f3ab15 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -2,6 +2,7 @@ use std::path::Path; use amdemod::SquaringAMDemodulator; use aptsyncer::{APTSyncer, SyncedSample}; +use errors::DecoderError; use firfilter::FIRFilter; use resamplers::{Downsampler, Upsampler}; use utils::float_sample_iterator; @@ -75,12 +76,16 @@ const LOWPASS_COEFFS: [f32; 63] = [ -7.383784e-03, ]; -pub fn decode(input_file: &str, output_file: &str, progress_update: T) -> Result<(), String> +pub fn decode( + input_file: &str, + output_file: &str, + progress_update: T, +) -> Result<(), DecoderError> where T: Fn(f32, image::RgbaImage) -> (bool, u32), { - let mut reader = hound::WavReader::open(input_file) - .map_err(|err| format!("Could not open inputfile: {}", err))?; + let mut reader = + hound::WavReader::open(input_file).map_err(|err| DecoderError::InputFileError(err))?; if reader.spec().channels != 1 { panic!("Expected a mono file"); @@ -88,7 +93,7 @@ where let sample_rate = reader.spec().sample_rate; if sample_rate != 48000 { - return Err("Expected a 48kHz sample rate".to_owned()); + return Err(DecoderError::UnexpectedSamplingRate(sample_rate)); } let sample_count = reader.len(); @@ -186,7 +191,7 @@ where progress_update(1.0, img.to_rgba8()); img.save_with_format(&Path::new(output_file), image::ImageFormat::Png) - .map_err(|err| format!("Could not save outputfile: {}", err))?; + .map_err(|err| DecoderError::OutputFileError(err))?; Ok(()) } diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..a8124c8 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,18 @@ +use hound; +use image; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum DecoderError { + #[error("Unable to read input file: {0}")] + InputFileError(#[from] hound::Error), + + #[error("Expected a sampling rate of 48000Hz not {0}Hz")] + UnexpectedSamplingRate(u32), + + #[error("Unable to write output file: {0}")] + OutputFileError(#[from] image::ImageError), + + #[error("FIXME: Unknown decoder error")] + Unknown, +} diff --git a/src/main.rs b/src/main.rs index 54fd5df..6142678 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,10 +4,12 @@ extern crate eframe; extern crate hound; extern crate image; extern crate rfd; +extern crate thiserror; mod amdemod; mod aptsyncer; mod decoder; +mod errors; mod firfilter; mod resamplers; mod ui; diff --git a/src/ui.rs b/src/ui.rs index 7b9c832..950046e 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -6,6 +6,7 @@ use eframe::egui::{Color32, RichText}; use eframe::{egui, epi}; use decoder; +use errors::DecoderError; #[derive(PartialEq)] enum DecoderRunState { @@ -19,6 +20,7 @@ struct DecoderJobState { progress: f32, texture: Option, run_state: DecoderRunState, + error: Option, } impl DecoderJobState { @@ -34,6 +36,7 @@ impl Default for DecoderJobState { progress: 0.0, texture: None, run_state: DecoderRunState::DONE, + error: None, } } } @@ -132,32 +135,43 @@ impl epi::App for DecoderApp { let decoding_state = decoding_state.clone(); let input_path = input_path.clone(); let output_path = output_path.clone(); + + state.error = None; state.run_state = DecoderRunState::RUNNING; + if let Some(old_texture) = state.texture { + frame.free_texture(old_texture); + } + state.texture = None; + std::thread::spawn(move || { - decoder::decode(&input_path, &output_path, |progress, image| { - let mut state = decoding_state.lock().unwrap(); + let decoder_res = + decoder::decode(&input_path, &output_path, |progress, image| { + let mut state = decoding_state.lock().unwrap(); - state.progress = progress; + state.progress = progress; - let size = [image.width() as _, image.height() as _]; - let epi_img = epi::Image::from_rgba_unmultiplied( - size, - image.as_flat_samples().as_slice(), - ); + let size = [image.width() as _, image.height() as _]; + let epi_img = epi::Image::from_rgba_unmultiplied( + size, + image.as_flat_samples().as_slice(), + ); - if let Some(old_texture) = state.texture { - frame.free_texture(old_texture); - } - state.texture = Some(frame.alloc_texture(epi_img)); + if let Some(old_texture) = state.texture { + frame.free_texture(old_texture); + } + state.texture = Some(frame.alloc_texture(epi_img)); - frame.request_repaint(); + frame.request_repaint(); - return (state.is_running(), state.update_steps); - }) - .unwrap(); + return (state.is_running(), state.update_steps); + }); let mut state = decoding_state.lock().unwrap(); state.run_state = DecoderRunState::DONE; + state.error = match decoder_res { + Err(err) => Some(err), + _ => None, + }; frame.request_repaint(); }); @@ -175,6 +189,11 @@ impl epi::App for DecoderApp { let progressbar = ProgressBar::new(state.progress).show_percentage(); ui.add(progressbar); + ui.end_row(); + + if let Some(err) = &state.error { + ui.label(RichText::new(err.to_string()).color(Color32::RED)); + }; ui.separator(); diff --git a/src/utils.rs b/src/utils.rs index 6f718cf..3301ac1 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,15 +4,28 @@ extern crate hound; type FileReader = std::io::BufReader; -pub fn float_sample_iterator<'a>(reader: &'a mut hound::WavReader) - -> Box + 'a> { +pub fn float_sample_iterator<'a>( + reader: &'a mut hound::WavReader, +) -> Box + 'a> { match reader.spec().sample_format { hound::SampleFormat::Float => Box::new(reader.samples::().map(|x| x.unwrap())), hound::SampleFormat::Int => match reader.spec().bits_per_sample { - 8 => Box::new(reader.samples::().map(|x| (x.unwrap() as f32) / (i16::max_value() as f32))), - 16 => Box::new(reader.samples::().map(|x| (x.unwrap() as f32) / (i16::max_value() as f32))), - 32 => Box::new(reader.samples::().map(|x| (x.unwrap() as f32) / (i32::max_value() as f32))), - _ => panic!("Unsupported sample rate") - } + 8 => Box::new( + reader + .samples::() + .map(|x| (x.unwrap() as f32) / (i16::max_value() as f32)), + ), + 16 => Box::new( + reader + .samples::() + .map(|x| (x.unwrap() as f32) / (i16::max_value() as f32)), + ), + 32 => Box::new( + reader + .samples::() + .map(|x| (x.unwrap() as f32) / (i32::max_value() as f32)), + ), + _ => panic!("Unsupported sample format"), + }, } }