Skip to content

Commit 3826fd1

Browse files
authored
Merge pull request #2 from fetlife/andrii/fix-review-findings
Fix waveform validation and rendering edge cases
2 parents dd66279 + 732db81 commit 3826fd1

7 files changed

Lines changed: 280 additions & 35 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
build/
22
target/
33
audiowaveform-rs/
4+
.cargo-home/

crates/audiowaveform-cli/src/main.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ fn run(cli: Cli) -> Result<(), String> {
424424
split_channels: cli.split_channels,
425425
amplitude_scale: match amplitude {
426426
ParsedAmplitudeScale::Auto => Some(AmplitudeScale::Auto),
427-
ParsedAmplitudeScale::Fixed(_) => None,
427+
ParsedAmplitudeScale::Fixed(value) => Some(AmplitudeScale::Fixed(value)),
428428
},
429429
},
430430
)?;
@@ -556,7 +556,7 @@ fn parse_amplitude_scale(value: &str) -> Result<ParsedAmplitudeScale, String> {
556556
let parsed = value
557557
.parse::<f64>()
558558
.map_err(|_| "Error: Invalid amplitude scale: must be a number".to_string())?;
559-
if parsed < 0.0 {
559+
if !parsed.is_finite() || parsed < 0.0 {
560560
Err("Error: Invalid amplitude scale: must be a positive number".to_string())
561561
} else {
562562
Ok(ParsedAmplitudeScale::Fixed(parsed))
@@ -666,8 +666,10 @@ fn resolve_raw_audio_config(cli: &Cli) -> Result<RawAudioConfig, String> {
666666
if channels <= 0 {
667667
return Err("Invalid number of input channels: must be greater than zero".to_string());
668668
}
669+
let channels = u16::try_from(channels)
670+
.map_err(|_| "Invalid number of input channels: maximum 65535".to_string())?;
669671

670-
RawAudioConfig::new(sample_rate as u32, channels as u16, sample_format).map_err(stringify_error)
672+
RawAudioConfig::new(sample_rate as u32, channels, sample_format).map_err(stringify_error)
671673
}
672674

673675
fn generate_waveform_from_input(

crates/audiowaveform-cli/tests/cli.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
mod support;
22

33
use assert_cmd::Command;
4+
use audiowaveform::Waveform;
45
use predicates::prelude::*;
56

67
use self::support::{
@@ -55,6 +56,68 @@ fn rejects_invalid_enum_values_via_clap() {
5556
.stderr(predicate::str::contains("possible values"));
5657
}
5758

59+
#[test]
60+
fn rejects_non_finite_numeric_values() {
61+
let input = fixture_path("test_file_stereo_8bit_64spp_wav.dat");
62+
let input = input.to_str().expect("utf8");
63+
64+
Command::cargo_bin("audiowaveform")
65+
.expect("binary")
66+
.args([
67+
"-q",
68+
"-i",
69+
input,
70+
"--output-format",
71+
"png",
72+
"-z",
73+
"64",
74+
"--start",
75+
"inf",
76+
])
77+
.assert()
78+
.failure()
79+
.stderr("Invalid start time: minimum 0\n");
80+
81+
Command::cargo_bin("audiowaveform")
82+
.expect("binary")
83+
.args([
84+
"-q",
85+
"-i",
86+
input,
87+
"--output-format",
88+
"png",
89+
"-z",
90+
"64",
91+
"--amplitude-scale",
92+
"NaN",
93+
])
94+
.assert()
95+
.failure()
96+
.stderr("Error: Invalid amplitude scale: must be a positive number\n");
97+
}
98+
99+
#[test]
100+
fn rejects_raw_channel_counts_that_do_not_fit_the_library_type() {
101+
Command::cargo_bin("audiowaveform")
102+
.expect("binary")
103+
.args([
104+
"-q",
105+
"--input-format",
106+
"raw",
107+
"--output-format",
108+
"wav",
109+
"--raw-samplerate",
110+
"48000",
111+
"--raw-channels",
112+
"65537",
113+
"--raw-format",
114+
"s16le",
115+
])
116+
.assert()
117+
.failure()
118+
.stderr("Invalid number of input channels: maximum 65535\n");
119+
}
120+
58121
#[test]
59122
fn generates_dat_output_to_file_and_stdout() {
60123
let output = named_temp_file(".dat");
@@ -133,6 +196,40 @@ fn generates_json_and_text_outputs_to_stdout() {
133196
.stderr("Done\n");
134197
}
135198

199+
#[test]
200+
fn applies_fixed_amplitude_scaling_to_waveform_data_output() {
201+
let unscaled_output = named_temp_file(".json");
202+
let scaled_output = named_temp_file(".json");
203+
204+
for (output, amplitude_scale) in [(&unscaled_output, "1.0"), (&scaled_output, "2.0")] {
205+
Command::cargo_bin("audiowaveform")
206+
.expect("binary")
207+
.args([
208+
"-q",
209+
"-i",
210+
fixture_path("test_file_stereo.wav").to_str().expect("utf8"),
211+
"-o",
212+
output.path().to_str().expect("utf8"),
213+
"-z",
214+
"64",
215+
"--amplitude-scale",
216+
amplitude_scale,
217+
])
218+
.assert()
219+
.success();
220+
}
221+
222+
let unscaled = Waveform::load_from_path(unscaled_output.path(), None).expect("unscaled");
223+
let scaled = Waveform::load_from_path(scaled_output.path(), None).expect("scaled");
224+
let expected = unscaled
225+
.interleaved_samples()
226+
.iter()
227+
.map(|value| (i32::from(*value) * 2).clamp(i32::from(i16::MIN), i32::from(i16::MAX)) as i16)
228+
.collect::<Vec<_>>();
229+
230+
assert_eq!(scaled.interleaved_samples(), expected);
231+
}
232+
136233
#[test]
137234
fn generates_png_output_to_file_and_stdout() {
138235
let output = named_temp_file(".png");

crates/audiowaveform/src/audio.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,13 @@ impl ScaleSpec {
168168
));
169169
}
170170
let frames = if let Some((start, end)) = time_range {
171-
if end < start {
171+
if !start.is_finite() || start < 0.0 {
172+
return Err(Error::invalid_argument(
173+
"start time",
174+
"Invalid start time: minimum 0",
175+
));
176+
}
177+
if !end.is_finite() || end < start {
172178
return Err(Error::invalid_argument(
173179
"end time",
174180
format!("Invalid end time, must be greater than {start}"),
@@ -739,6 +745,25 @@ mod tests {
739745
"Invalid end time, must be greater than 5"
740746
);
741747

748+
let error = ScaleSpec::FitWidth {
749+
width_pixels: 400,
750+
time_range: Some((f64::INFINITY, 10.0)),
751+
}
752+
.resolve(48_000, 96_000)
753+
.expect_err("non-finite start time");
754+
assert_eq!(error.to_string(), "Invalid start time: minimum 0");
755+
756+
let error = ScaleSpec::FitWidth {
757+
width_pixels: 400,
758+
time_range: Some((0.0, f64::INFINITY)),
759+
}
760+
.resolve(48_000, 96_000)
761+
.expect_err("non-finite end time");
762+
assert_eq!(
763+
error.to_string(),
764+
"Invalid end time, must be greater than 0"
765+
);
766+
742767
let error = ScaleSpec::FitWidth {
743768
width_pixels: 100_000,
744769
time_range: None,

0 commit comments

Comments
 (0)