pub struct WaveformAnalyzer { /* private fields */ }Expand description
Computes peak and RMS amplitude per time interval for an audio file.
Decodes audio via AudioDecoder (requesting packed f32 output so that
per-sample arithmetic needs no format dispatch) and computes, for each
configurable interval, the peak and RMS amplitudes in dBFS. The resulting
Vec<WaveformSample> is designed for waveform display rendering.
§Examples
use ff_analysis::WaveformAnalyzer;
use std::time::Duration;
let samples = WaveformAnalyzer::new("audio.mp3")
.interval(Duration::from_millis(50))
.run()?;
for s in &samples {
println!("{:?}: peak={:.1} dBFS rms={:.1} dBFS",
s.timestamp, s.peak_db, s.rms_db);
}Implementations§
Source§impl WaveformAnalyzer
impl WaveformAnalyzer
Sourcepub fn new(input: impl AsRef<Path>) -> Self
pub fn new(input: impl AsRef<Path>) -> Self
Creates a new analyzer for the given audio file.
The default sampling interval is 100 ms. Call
interval to override it.
Examples found in repository?
27fn main() {
28 let mut args = std::env::args().skip(1);
29 let mut input = None::<String>;
30 let mut interval_ms = 100u64;
31
32 while let Some(flag) = args.next() {
33 match flag.as_str() {
34 "--input" | "-i" => input = Some(args.next().unwrap_or_default()),
35 "--interval-ms" => {
36 let raw = args.next().unwrap_or_default();
37 interval_ms = raw.parse().unwrap_or_else(|_| {
38 eprintln!("Invalid interval-ms: {raw}");
39 process::exit(1);
40 });
41 }
42 other => {
43 eprintln!("Unknown flag: {other}");
44 process::exit(1);
45 }
46 }
47 }
48
49 let input = input.unwrap_or_else(|| {
50 eprintln!("Usage: waveform --input <audio> [--interval-ms <ms>]");
51 process::exit(1);
52 });
53
54 println!("Analyzing waveform: {input}");
55 println!("Interval: {interval_ms} ms");
56 println!();
57
58 let samples: Vec<WaveformSample> = WaveformAnalyzer::new(&input)
59 .interval(Duration::from_millis(interval_ms))
60 .run()
61 .unwrap_or_else(|e| {
62 eprintln!("Error: {e}");
63 process::exit(1);
64 });
65
66 println!("{} interval(s) analyzed.", samples.len());
67 println!();
68
69 // Print first 20 samples to avoid flooding the terminal.
70 let display_count = samples.len().min(20);
71 println!(
72 "{:<12} {:>10} {:>10}",
73 "Time (s)", "Peak dBFS", "RMS dBFS"
74 );
75 println!("{}", "-".repeat(38));
76 for s in samples.iter().take(display_count) {
77 let secs = s.timestamp.as_secs_f64();
78 println!(
79 "{secs:<12.3} {:>10} {:>10}",
80 fmt_db(s.peak_db),
81 fmt_db(s.rms_db)
82 );
83 }
84 if samples.len() > display_count {
85 println!(" … ({} more intervals)", samples.len() - display_count);
86 }
87}Sourcepub fn interval(self, d: Duration) -> Self
pub fn interval(self, d: Duration) -> Self
Sets the sampling interval.
Peak and RMS are computed independently for each interval of this
length. Passing Duration::ZERO causes run to
return AnalysisError::Failed.
Default: 100 ms.
Examples found in repository?
27fn main() {
28 let mut args = std::env::args().skip(1);
29 let mut input = None::<String>;
30 let mut interval_ms = 100u64;
31
32 while let Some(flag) = args.next() {
33 match flag.as_str() {
34 "--input" | "-i" => input = Some(args.next().unwrap_or_default()),
35 "--interval-ms" => {
36 let raw = args.next().unwrap_or_default();
37 interval_ms = raw.parse().unwrap_or_else(|_| {
38 eprintln!("Invalid interval-ms: {raw}");
39 process::exit(1);
40 });
41 }
42 other => {
43 eprintln!("Unknown flag: {other}");
44 process::exit(1);
45 }
46 }
47 }
48
49 let input = input.unwrap_or_else(|| {
50 eprintln!("Usage: waveform --input <audio> [--interval-ms <ms>]");
51 process::exit(1);
52 });
53
54 println!("Analyzing waveform: {input}");
55 println!("Interval: {interval_ms} ms");
56 println!();
57
58 let samples: Vec<WaveformSample> = WaveformAnalyzer::new(&input)
59 .interval(Duration::from_millis(interval_ms))
60 .run()
61 .unwrap_or_else(|e| {
62 eprintln!("Error: {e}");
63 process::exit(1);
64 });
65
66 println!("{} interval(s) analyzed.", samples.len());
67 println!();
68
69 // Print first 20 samples to avoid flooding the terminal.
70 let display_count = samples.len().min(20);
71 println!(
72 "{:<12} {:>10} {:>10}",
73 "Time (s)", "Peak dBFS", "RMS dBFS"
74 );
75 println!("{}", "-".repeat(38));
76 for s in samples.iter().take(display_count) {
77 let secs = s.timestamp.as_secs_f64();
78 println!(
79 "{secs:<12.3} {:>10} {:>10}",
80 fmt_db(s.peak_db),
81 fmt_db(s.rms_db)
82 );
83 }
84 if samples.len() > display_count {
85 println!(" … ({} more intervals)", samples.len() - display_count);
86 }
87}Sourcepub fn run(self) -> Result<Vec<WaveformSample>, AnalysisError>
pub fn run(self) -> Result<Vec<WaveformSample>, AnalysisError>
Runs the waveform analysis and returns one WaveformSample per interval.
The timestamp of each sample is the start of its interval. Audio
is decoded as packed f32 samples; the decoder performs any necessary
format conversion automatically.
§Errors
AnalysisError::Failed— interval isDuration::ZERO.ff_decode::DecodeError::FileNotFound— input path does not exist.- Any other
ff_decode::DecodeErrorpropagated fromAudioDecoder, wrapped inAnalysisError::Decode.
Examples found in repository?
27fn main() {
28 let mut args = std::env::args().skip(1);
29 let mut input = None::<String>;
30 let mut interval_ms = 100u64;
31
32 while let Some(flag) = args.next() {
33 match flag.as_str() {
34 "--input" | "-i" => input = Some(args.next().unwrap_or_default()),
35 "--interval-ms" => {
36 let raw = args.next().unwrap_or_default();
37 interval_ms = raw.parse().unwrap_or_else(|_| {
38 eprintln!("Invalid interval-ms: {raw}");
39 process::exit(1);
40 });
41 }
42 other => {
43 eprintln!("Unknown flag: {other}");
44 process::exit(1);
45 }
46 }
47 }
48
49 let input = input.unwrap_or_else(|| {
50 eprintln!("Usage: waveform --input <audio> [--interval-ms <ms>]");
51 process::exit(1);
52 });
53
54 println!("Analyzing waveform: {input}");
55 println!("Interval: {interval_ms} ms");
56 println!();
57
58 let samples: Vec<WaveformSample> = WaveformAnalyzer::new(&input)
59 .interval(Duration::from_millis(interval_ms))
60 .run()
61 .unwrap_or_else(|e| {
62 eprintln!("Error: {e}");
63 process::exit(1);
64 });
65
66 println!("{} interval(s) analyzed.", samples.len());
67 println!();
68
69 // Print first 20 samples to avoid flooding the terminal.
70 let display_count = samples.len().min(20);
71 println!(
72 "{:<12} {:>10} {:>10}",
73 "Time (s)", "Peak dBFS", "RMS dBFS"
74 );
75 println!("{}", "-".repeat(38));
76 for s in samples.iter().take(display_count) {
77 let secs = s.timestamp.as_secs_f64();
78 println!(
79 "{secs:<12.3} {:>10} {:>10}",
80 fmt_db(s.peak_db),
81 fmt_db(s.rms_db)
82 );
83 }
84 if samples.len() > display_count {
85 println!(" … ({} more intervals)", samples.len() - display_count);
86 }
87}