commit - 9876be8bdab1540cabcb8f42dcb02859b07d6d6d
commit + 51ec796bc07ae3316c9f9cbfc51308b778e47056
blob - 26b5ae38f83806f1db264ca3b268ff8626f1e967
blob + fc922f8701396116387c17d046553d3473839865
--- README.md
+++ README.md
[](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
};
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
-//! BPM and key detection, backed by Essentia (https://github.com/MTG/essentia)
+//! BPM and key detection, backed by Essentia (<https://essentia.upf.edu/>)
//! 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
}
/// 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)
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);
}
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");
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<usize> {
None
}
-fn strip_chunks(bytes: &mut Vec<u8>, fourcc: &[u8; 4]) {
+fn strip_chunks(bytes: &mut Vec<u8>, 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() {