commit 51ec796bc07ae3316c9f9cbfc51308b778e47056 from: mtmn date: Mon Jul 27 23:32:22 2026 UTC nix: eigen3 missing in devshell commit - 9876be8bdab1540cabcb8f42dcb02859b07d6d6d commit + 51ec796bc07ae3316c9f9cbfc51308b778e47056 blob - 26b5ae38f83806f1db264ca3b268ff8626f1e967 blob + fc922f8701396116387c17d046553d3473839865 --- README.md +++ README.md @@ -2,7 +2,7 @@ [![builds.sr.ht status](https://builds.sr.ht/~mtmn/virittaa.svg)](https://builds.sr.ht/~mtmn/virittaa?) Command-line BPM and key detection for audio files, backed by -[Essentia](https://github.com/MTG/essentia): tempo from `RhythmExtractor2013` +[Essentia](https://essentia.upf.edu/): tempo from `RhythmExtractor2013` (multifeature), key from `KeyExtractor` (EDMA profile). The reported BPM is Essentia's tempo estimate, not folded into a fixed octave range. blob - 311b515d8803c3d76dec78dca970f3426df5f8fd blob + 4d3c9b3c15e836787259a020586c378f460e2109 --- flake.nix +++ flake.nix @@ -138,8 +138,11 @@ }; devShells.default = pkgs.mkShell { - inherit (commonArgs) USE_TENSORFLOW LIBCLANG_PATH BINDGEN_EXTRA_CLANG_ARGS; + inherit (commonArgs) USE_TENSORFLOW LIBCLANG_PATH; + NIX_CFLAGS_COMPILE = "-isystem ${pkgs.eigen}/include/eigen3"; + BINDGEN_EXTRA_CLANG_ARGS = "${commonArgs.BINDGEN_EXTRA_CLANG_ARGS} -isystem ${pkgs.eigen}/include/eigen3"; + LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath runtimeLibs; nativeBuildInputs = [ blob - 3950f683f9d47dbfa23f5facb38b79ad33f2c251 blob + 137726ca6b40f7b5702c2637008f55d5e75ce63a --- src/audio_processing.rs +++ src/audio_processing.rs @@ -1,4 +1,4 @@ -//! BPM and key detection, backed by Essentia (https://github.com/MTG/essentia) +//! BPM and key detection, backed by Essentia () //! via the `essentia` bindings crate (essentia-rs). //! //! `MonoLoader` decodes and downmixes to mono at 44.1 kHz, `RhythmExtractor2013` blob - b7561f41ce77e6d5135cc8bc7bba22c53990b1ad blob + 7a6afa54673ae435d010952f53e26ee3cb896004 --- src/file_io.rs +++ src/file_io.rs @@ -144,17 +144,17 @@ fn read_tags(path: &Path) -> (FileMetadata, Option<(f3 } /// Write BPM and key metadata to a file. +/// +/// # Errors +/// +/// Returns an error if the file cannot be read, parsed, or written back. pub fn write_metadata(path: &Path, bpm: f32, key: &str) -> Result<()> { - let is_aiff = path - .extension() - .and_then(|e| e.to_str()) - .map(|e| { - matches!( - e.to_ascii_lowercase().as_str(), - "aiff" | "aif" | "aifc" | "afc" - ) - }) - .unwrap_or(false); + let is_aiff = path.extension().and_then(|e| e.to_str()).is_some_and(|e| { + matches!( + e.to_ascii_lowercase().as_str(), + "aiff" | "aif" | "aifc" | "afc" + ) + }); if is_aiff { write_aiff_id3(path, bpm, key) @@ -198,7 +198,7 @@ fn write_aiff_id3(path: &Path, bpm: f32, key: &str) -> anyhow::bail!("not a valid IFF/AIFF file"); } - for fourcc in [b"ID3 ", b"id3 "] { + for fourcc in [*b"ID3 ", *b"id3 "] { strip_chunks(&mut file_bytes, fourcc); } @@ -242,7 +242,8 @@ fn id3_tag(bpm: &str, key: &str) -> Vec { let bpm_frame = text_frame("TBPM", bpm); let key_frame = text_frame("TKEY", key); - let size = syncsafe(bpm_frame.len() + key_frame.len()); + let size = + syncsafe(u32::try_from(bpm_frame.len() + key_frame.len()).expect("ID3 tag too large")); let mut tag = Vec::with_capacity(10 + bpm_frame.len() + key_frame.len()); tag.extend_from_slice(b"ID3"); @@ -261,16 +262,22 @@ fn text_frame(id: &str, value: &str) -> Vec { let mut frame = Vec::with_capacity(10 + value.len()); frame.extend_from_slice(id.as_bytes()); - frame.extend_from_slice(&((1 + value.len()) as u32).to_be_bytes()); + frame.extend_from_slice( + &u32::try_from(1 + value.len()) + .expect("frame too large") + .to_be_bytes(), + ); frame.extend_from_slice(&TEXT_FLAGS); frame.push(TEXT_ENCODING_LATIN1); frame.extend_from_slice(value.as_bytes()); frame } -fn syncsafe(n: usize) -> u32 { - (((n & 0x0FE00000) << 3) | ((n & 0x001FC000) << 2) | ((n & 0x00003F80) << 1) | (n & 0x0000007F)) - as u32 +fn syncsafe(n: u32) -> u32 { + ((n & 0x0FE0_0000) << 3) + | ((n & 0x001F_C000) << 2) + | ((n & 0x0000_3F80) << 1) + | (n & 0x0000_007F) } fn comm_end(bytes: &[u8]) -> Option { @@ -282,9 +289,9 @@ fn comm_end(bytes: &[u8]) -> Option { None } -fn strip_chunks(bytes: &mut Vec, fourcc: &[u8; 4]) { +fn strip_chunks(bytes: &mut Vec, fourcc: [u8; 4]) { let ranges: Vec<_> = iff_chunks(bytes) - .filter(|(_, id, _)| *id == fourcc) + .filter(|&(_, id, _)| *id == fourcc) .map(|(off, _, size)| (off, off + IFF_HEADER_LEN + pad(size))) .collect(); for (s, e) in ranges.into_iter().rev() {