#![deny(
clippy::all,
clippy::cargo,
clippy::nursery,
// clippy::restriction,
// clippy::pedantic
)]
#![allow(
clippy::suboptimal_flops,
clippy::redundant_pub_crate,
clippy::fallible_impl_from
)]
#![deny(missing_debug_implementations)]
#![deny(rustdoc::all)]
use audio_visualizer::spectrum::plotters_png_file::spectrum_static_plotters_png_visualize;
use spectrum_analyzer::scaling::scale_to_zero_to_one;
use spectrum_analyzer::windows::{
blackman_harris_4term, blackman_harris_7term, hamming_window, hann_window,
};
use spectrum_analyzer::{FrequencyLimit, samples_fft_to_spectrum};
use std::fs::File;
use std::path::{Path, PathBuf};
use std::time::Instant;
use symphonia::core::codecs::audio::AudioDecoderOptions;
use symphonia::core::errors::Error;
use symphonia::core::formats::probe::Hint;
use symphonia::core::formats::{FormatOptions, TrackType};
use symphonia::core::io::MediaSourceStream;
use symphonia::core::meta::MetadataOptions;
use symphonia::default::{get_codecs, get_probe};
fn test_out_dir() -> PathBuf {
let path = std::env::var("CARGO_TARGET_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| {
let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let dir = PathBuf::from(dir);
dir.join("target")
});
let path = path.join("test_generated");
if !path.exists() {
let _ = std::fs::create_dir(path.clone());
}
path
}
fn main() {
println!("bass drum example:");
example__bass_drum_sample();
println!("============================");
println!("clap beat example:");
example__clap_beat_sample();
println!("============================");
println!("high hat example:");
example__high_hat_sample();
}
#[allow(non_snake_case)]
fn example__bass_drum_sample() {
let (samples, sampling_rate) =
read_mp3_to_mono("test/samples/bass_drum_with_high-hat_at_end-sample.mp3");
let samples = samples.into_iter().map(|x| x as f32).collect::<Vec<f32>>();
to_spectrum_and_plot(
&samples[0..4096],
sampling_rate,
"example__mp3-samples__bass_drum__spectrum",
FrequencyLimit::Max(5000.0),
)
}
#[allow(non_snake_case)]
fn example__clap_beat_sample() {
let (samples, sampling_rate) = read_mp3_to_mono("test/samples/clap-beat-sample.mp3");
let samples = samples.into_iter().map(|x| x as f32).collect::<Vec<f32>>();
to_spectrum_and_plot(
&samples[0..4096],
sampling_rate,
"example__mp3-samples__clap_beat__spectrum",
FrequencyLimit::Max(5000.0),
)
}
#[allow(non_snake_case)]
fn example__high_hat_sample() {
let (samples, sampling_rate) = read_mp3_to_mono("test/samples/high-hat-sample.mp3");
let samples = samples.into_iter().map(|x| x as f32).collect::<Vec<f32>>();
to_spectrum_and_plot(
&samples[0..4096],
sampling_rate,
"example__mp3-samples__high-hat__spectrum",
FrequencyLimit::All,
)
}
fn to_spectrum_and_plot(
samples: &[f32],
sampling_rate: u32,
filename: &str,
frequency_limit: FrequencyLimit,
) {
let no_window = samples;
let now = Instant::now();
let hann_window = hann_window(no_window);
println!(
"[Measurement]: Hann-Window with {} samples took: {}µs",
samples.len(),
now.elapsed().as_micros()
);
let now = Instant::now();
let hamming_window = hamming_window(no_window);
println!(
"[Measurement]: Hamming-Window with {} samples took: {}µs",
samples.len(),
now.elapsed().as_micros()
);
let blackman_harris_4term_window = blackman_harris_4term(no_window);
println!(
"[Measurement]: Blackmann-Harris-4-term-Window with {} samples took: {}µs",
samples.len(),
now.elapsed().as_micros()
);
let blackman_harris_7term_window = blackman_harris_7term(no_window);
println!(
"[Measurement]: Blackmann-Harris-7-term-Window with {} samples took: {}µs",
samples.len(),
now.elapsed().as_micros()
);
let now = Instant::now();
let spectrum_no_window = samples_fft_to_spectrum(
no_window,
sampling_rate,
frequency_limit,
Some(&scale_to_zero_to_one),
)
.unwrap();
println!(
"[Measurement]: FFT to Spectrum with no window with {} samples took: {}µs",
samples.len(),
now.elapsed().as_micros()
);
let now = Instant::now();
let spectrum_hamming_window = samples_fft_to_spectrum(
&hamming_window,
sampling_rate,
frequency_limit,
Some(&scale_to_zero_to_one),
)
.unwrap();
println!(
"[Measurement]: FFT to Spectrum with Hamming window with {} samples took: {}µs",
samples.len(),
now.elapsed().as_micros()
);
let now = Instant::now();
let spectrum_hann_window = samples_fft_to_spectrum(
&hann_window,
sampling_rate,
frequency_limit,
Some(&scale_to_zero_to_one),
)
.unwrap();
println!(
"[Measurement]: FFT to Spectrum with Hann window with {} samples took: {}µs",
samples.len(),
now.elapsed().as_micros()
);
let now = Instant::now();
let spectrum_blackman_harris_4term_window = samples_fft_to_spectrum(
&blackman_harris_4term_window,
sampling_rate,
frequency_limit,
Some(&scale_to_zero_to_one),
)
.unwrap();
println!(
"[Measurement]: FFT to Spectrum with Blackmann Harris 4-term window with {} samples took: {}µs",
samples.len(),
now.elapsed().as_micros()
);
let now = Instant::now();
let spectrum_blackman_harris_7term_window = samples_fft_to_spectrum(
&blackman_harris_7term_window,
sampling_rate,
frequency_limit,
Some(&scale_to_zero_to_one),
)
.unwrap();
println!(
"[Measurement]: FFT to Spectrum with Blackmann Harris 7-term window with {} samples took: {}µs",
samples.len(),
now.elapsed().as_micros()
);
spectrum_static_plotters_png_visualize(
&spectrum_no_window.to_map(),
test_out_dir().to_str().unwrap(),
&format!("{filename}--no-window.png"),
);
spectrum_static_plotters_png_visualize(
&spectrum_hamming_window.to_map(),
test_out_dir().to_str().unwrap(),
&format!("{filename}--hamming-window.png"),
);
spectrum_static_plotters_png_visualize(
&spectrum_hann_window.to_map(),
test_out_dir().to_str().unwrap(),
&format!("{filename}--hann-window.png"),
);
spectrum_static_plotters_png_visualize(
&spectrum_blackman_harris_4term_window.to_map(),
test_out_dir().to_str().unwrap(),
&format!("{filename}--blackman-harris-4-term-window.png"),
);
spectrum_static_plotters_png_visualize(
&spectrum_blackman_harris_7term_window.to_map(),
test_out_dir().to_str().unwrap(),
&format!("{filename}--blackman-harris-7-term-window.png"),
);
}
fn read_mp3_to_mono<P: AsRef<Path>>(file: P) -> (Vec<i16>, u32) {
let file = File::open(file).unwrap();
let mss = MediaSourceStream::new(Box::new(file), Default::default());
let mut hint = Hint::new();
hint.with_extension("mp3");
let mut format_reader = get_probe()
.probe(
&hint,
mss,
FormatOptions::default(),
MetadataOptions::default(),
)
.unwrap();
let track = format_reader.default_track(TrackType::Audio).unwrap();
let track_id = track.id;
let mut decoder = get_codecs()
.make_audio_decoder(
track.codec_params.as_ref().unwrap().audio().unwrap(),
&AudioDecoderOptions::default(),
)
.unwrap();
let mut audio_data_lrlr = Vec::new();
let mut sampling_rate = None;
while let Some(packet) = format_reader.next_packet().unwrap() {
if packet.track_id != track_id {
continue;
}
match decoder.decode(&packet) {
Ok(audio_buf_ref) => {
let audio_spec = audio_buf_ref.spec();
if sampling_rate.is_none() {
sampling_rate.replace(audio_spec.rate());
}
let mut samples_interleaved = vec![0i16; audio_buf_ref.samples_interleaved()];
audio_buf_ref.copy_to_slice_interleaved(&mut samples_interleaved);
match audio_spec.channels().count() {
1 => audio_data_lrlr.extend(samples_interleaved),
2 => {
let iter = samples_interleaved
.chunks_exact(2)
.map(|lr| ((lr[0] as i32 + lr[1] as i32) / 2) as i16);
audio_data_lrlr.extend(iter);
}
n => panic!("Unsupported amount of channels: {n}"),
}
}
Err(Error::DecodeError(_)) => continue,
Err(err) => panic!("{err}"),
}
}
(audio_data_lrlr, sampling_rate.unwrap())
}