diff --git a/.gitignore b/.gitignore index 7ae0522..ec6d44a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,6 @@ *.dll *.so *.dylib -pitchdetector -doughscraper # Test binary, built with `go test -c` *.test diff --git a/rust/pitchdetector/Cargo.lock b/rust/pitchdetector/Cargo.lock new file mode 100644 index 0000000..fc28131 --- /dev/null +++ b/rust/pitchdetector/Cargo.lock @@ -0,0 +1,270 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "apodize" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca387cdc0a1f9c7a7c26556d584aa2d07fc529843082e4861003cde4ab914ed" + +[[package]] +name = "approx" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "fitting" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25717d23402e00197e700694ffbc5fb005cdfe1edbbca1c0db544a2435b29719" +dependencies = [ + "approx", + "ndarray", + "thiserror", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "hound" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "libc" +version = "0.2.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" + +[[package]] +name = "matrixmultiply" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916806ba0031cd542105d916a97c8572e1fa6dd79c9c51e7eb43a09ec2dd84c1" +dependencies = [ + "rawpointer", +] + +[[package]] +name = "ndarray" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac06db03ec2f46ee0ecdca1a1c34a99c0d188a0d83439b84bf0cb4b386e4ab09" +dependencies = [ + "approx", + "matrixmultiply", + "num-complex 0.2.4", + "num-integer", + "num-traits", + "rawpointer", +] + +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "pitch-detector" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c15e6d8f938411a08f18ecca4f309e7214df30b708c51cd07f9b6479b1cb07b" +dependencies = [ + "anyhow", + "apodize", + "fitting", + "itertools", + "num-traits", + "rustfft", +] + +[[package]] +name = "pitchdetector" +version = "0.1.0" +dependencies = [ + "anyhow", + "float-cmp", + "hound", + "libc", + "pitch-detector", +] + +[[package]] +name = "primal-check" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df7f93fd637f083201473dab4fee2db4c429d32e55e3299980ab3957ab916a0" +dependencies = [ + "num-integer", +] + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rustfft" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d4f6cbdb180c9f4b2a26bbf01c4e647f1e1dea22fe8eb9db54198b32f9434" +dependencies = [ + "num-complex 0.4.4", + "num-integer", + "num-traits", + "primal-check", + "strength_reduce", + "transpose", + "version_check", +] + +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + +[[package]] +name = "syn" +version = "2.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "transpose" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6522d49d03727ffb138ae4cbc1283d3774f0d10aa7f9bf52e6784c45daf9b23" +dependencies = [ + "num-integer", + "strength_reduce", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" diff --git a/rust/pitchdetector/Cargo.toml b/rust/pitchdetector/Cargo.toml new file mode 100644 index 0000000..34ec122 --- /dev/null +++ b/rust/pitchdetector/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "pitchdetector" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +pitch-detector = "0.3.1" +anyhow = "1.0.75" +float-cmp = "0.9.0" +hound = "3.5.1" +libc = "0.2.149" diff --git a/rust/pitchdetector/src/main.rs b/rust/pitchdetector/src/main.rs new file mode 100644 index 0000000..331c7db --- /dev/null +++ b/rust/pitchdetector/src/main.rs @@ -0,0 +1,87 @@ +use std::path::PathBuf; +use pitch_detector::{ + note::{detect_note_in_range, NoteDetectionResult}, + pitch::HannedFftDetector +}; +use anyhow::Result; +use hound; +use std::{env, fs}; + +const SAMPLE_RATE: f64 = 44100.0; +const MAX_FREQ: f64 = 1046.50; // C6 +const MIN_FREQ: f64 = 32.7; // C1 + +fn detect_note(sample: Vec) -> Result { + let mut detector = HannedFftDetector::default(); + let note = detect_note_in_range(&sample, &mut detector, SAMPLE_RATE, MIN_FREQ..MAX_FREQ) + .ok_or(anyhow::anyhow!("Did not get note"))?; + Ok(note) +} + +fn note(path: PathBuf) -> NoteDetectionResult { + let mut reader = hound::WavReader::open(path).unwrap(); + let mut f64_samples = Vec::new(); + for result in reader.samples::() { + let sample = result.unwrap(); + let normalized_sample = (sample << 8) as f64 / (i32::MAX as f64); + f64_samples.push(normalized_sample); + } + let pitch = detect_note(f64_samples).unwrap(); + pitch +} + +fn main() { + if let Some(dir_path) = env::args().nth(1) { + // Read the directory content. + for entry in fs::read_dir(dir_path).unwrap() { + let entry = entry.unwrap(); + let path = entry.path(); + + // Process only files (exclude directories). + if path.is_file() { + let old_name = path.file_name().ok_or("File name error").unwrap().to_string_lossy().to_string(); + + // Skip non-wave files. + if old_name.ends_with(".wav") || old_name.ends_with(".mp3") { + // Detect the pitch. + let pitch = note(path.clone()); + + // Generate a new name. + let mut new_name = String::new(); + match pitch.note_name.to_string().as_str() { + "A" => new_name = format!("a{}", pitch.octave.to_string()), + "A#" => new_name = format!("bb{}", pitch.octave.to_string()), + "B" => new_name = format!("b{}", pitch.octave.to_string()), + "C" => new_name = format!("c{}", pitch.octave.to_string()), + "C#" => new_name = format!("db{}", pitch.octave.to_string()), + "D" => new_name = format!("d{}", pitch.octave.to_string()), + "D#" => new_name = format!("eb{}", pitch.octave.to_string()), + "E" => new_name = format!("e{}", pitch.octave.to_string()), + "F" => new_name = format!("f{}", pitch.octave.to_string()), + "F#" => new_name = format!("gb{}", pitch.octave.to_string()), + "G" => new_name = format!("g{}", pitch.octave.to_string()), + "G#" => new_name = format!("ab{}", pitch.octave.to_string()), + _ => {} + } + let mut result = String::new(); + if old_name.ends_with(".wav") { + result = format!("{}-{}.wav", old_name.trim_end_matches(".wav"), new_name); + } else if old_name.ends_with(".mp3") { + result = format!("{}-{}.mp3", old_name.trim_end_matches(".mp3"), new_name); + } + + // Create a new path for the file. + let parent = path.parent().ok_or("Parent error").unwrap(); + let new_path = parent.join(result); + + // Rename the file. + fs::rename(path, new_path).unwrap(); + } + } + } + } else { + println!("Failed to get the directory path."); + } + +} +