Compare commits

...

13 Commits

Author SHA1 Message Date
6d9489d3a6 Merge branch 'master' into github-mirror-master 2024-02-16 17:57:14 +02:00
Polochon-street
44f79153ea
Merge pull request #71 from Polochon-street/bump-ffmpeg-rpi
Some checks failed
Rust / build-test-lint-linux (pull_request) Has been cancelled
Rust / Windows - build, test and lint (https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full-shared.7z, latest) (pull_request) Has been cancelled
Bump rust-ffmpeg to hopefully support rpis.
2024-01-03 21:57:10 +01:00
Polochon_street
6b747e224c Rollback testing changes 2024-01-03 21:50:15 +01:00
Polochon_street
8d3a3c6433 Bump rust-ffmpeg to hopefully support rpis. 2024-01-03 21:40:33 +01:00
Polochon_street
3fd91bbfae Exclude data/ folder when publishing crate 2023-12-28 19:24:10 +01:00
Polochon-street
ed2dd72c6b
Merge pull request #70 from Polochon-street/cleanup-repo
Cleanup deps
2023-12-28 19:14:37 +01:00
Polochon_street
dc3592147c Cleanup deps 2023-12-28 19:09:07 +01:00
Polochon-street
bf35f643fd
Merge pull request #69 from Polochon-street/cleanup-repo
Cleanup repo from audio data
2023-12-28 14:08:33 +01:00
Polochon_street
479fbc7b89 Cleanup repo from audio data 2023-12-28 13:58:26 +01:00
Polochon-street
1445939bf8
Merge pull request #68 from Polochon-street/add-ci-check
Add a local CI check script
2023-12-27 23:37:02 +01:00
Polochon_street
daee149271 Add a local CI check script 2023-12-27 23:30:40 +01:00
Polochon-street
d10a1cf526
Merge pull request #67 from Polochon-street/library-fix-utf8
Fix utf8 panic in the library module
2023-12-27 19:31:30 +01:00
Polochon_street
f82c98a12e Fix utf8 panic in the library module 2023-12-27 12:58:28 +01:00
24 changed files with 86 additions and 111 deletions

View File

@ -1,9 +1,13 @@
#Changelog
## bliss 0.6.11
* Bump rust-ffmpeg to 6.1.1 to fix build for raspberry pis.
## bliss 0.6.10
* Make the `analyze` function public, for people who don't want to use
ffmpeg
* Run `cargo update`
* Run `cargo update`, bump ffmpeg to 6.1
* Fix the library module erroring when wrong UTF-8 ends up in the database.
## bliss 0.6.9
* Add a feature flag for compilation on raspberry pis.

19
Cargo.lock generated
View File

@ -2,6 +2,12 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler32"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]]
name = "aho-corasick"
version = "1.1.2"
@ -89,8 +95,9 @@ dependencies = [
[[package]]
name = "bliss-rs"
version = "0.6.9"
version = "0.6.11"
dependencies = [
"adler32",
"anyhow",
"bliss-audio-aubio-rs",
"clap",
@ -105,7 +112,6 @@ dependencies = [
"neon",
"noisy_float",
"pretty_assertions",
"ripemd",
"rustfft",
"serde",
"serde_ini",
@ -823,15 +829,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "194d8e591e405d1eecf28819740abed6d719d1a2db87fc0bcdedee9a26d55560"
[[package]]
name = "ripemd"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f"
dependencies = [
"digest",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"

View File

@ -1,6 +1,6 @@
[package]
name = "bliss-rs"
version = "0.6.9"
version = "0.6.11"
build = "build.rs"
authors = ["Polochon-street <polochonstreet@gmx.fr>"]
edition = "2021"
@ -10,7 +10,7 @@ homepage = "https://lelele.io/bliss.html"
repository = "https://github.com/Polochon-street/bliss-rs"
keywords = ["audio", "analysis", "MIR", "playlist", "similarity"]
readme = "README.md"
exclude = ["index.node"]
exclude = ["data/", "index.node"]
[lib]
crate-type = ["rlib", "cdylib"]
@ -24,12 +24,14 @@ no-default-features = true
# Hopefully we'll be able to use the official aubio-rs at some point.
bliss-audio-aubio-rs = { version = "0.2.1", features = ["static"] }
crossbeam = "0.8.2"
ffmpeg-next = { version = "6.0.0", features = ["static"] }
ffmpeg-next = { version = "6.1.1", features = ["static"] }
log = "0.4.17"
# `rayon` is used only by `par_mapv_inplace` in chroma.rs.
# TODO: is the speed gain that substantial?
ndarray = { version = "0.15.6", features = ["rayon"] }
ndarray-stats = "0.5.1"
noisy_float = "0.2.0"
ripemd = "0.1.3"
adler32 = "1.0.2"
rustfft = "6.1.0"
thiserror = "1.0.40"
strum = "0.24.1"

1
ci_check.sh Executable file
View File

@ -0,0 +1 @@
cargo fmt -- --check && cargo clippy --examples --features=serde -- -D warnings && cargo build --verbose && cargo test --verbose && cargo test --verbose --examples && cargo +nightly-2023-02-16 bench --verbose --features=bench --no-run && cargo build --examples --verbose --features=serde

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 826 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 826 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Binary file not shown.

BIN
data/white_noise.mp3 Normal file

Binary file not shown.

View File

@ -13,8 +13,8 @@ fn main() -> Result<(), String> {
let first_path = paths.next().ok_or("Help: ./distance <song1> <song2>")?;
let second_path = paths.next().ok_or("Help: ./distance <song1> <song2>")?;
let song1 = Song::from_path(first_path).map_err(|x| x.to_string())?;
let song2 = Song::from_path(second_path).map_err(|x| x.to_string())?;
let song1 = Song::from_path(&first_path).map_err(|x| x.to_string())?;
let song2 = Song::from_path(&second_path).map_err(|x| x.to_string())?;
let mut distance_squared: f64 = 0.0;
let analysis1 = song1.analysis.as_bytes();
@ -25,8 +25,8 @@ fn main() -> Result<(), String> {
println!(
"d({:?}, {:?}) = {}",
song1.path,
song2.path,
&first_path,
&second_path,
distance_squared.sqrt(),
);
Ok(())

View File

@ -437,7 +437,7 @@ mod test {
fn test_chroma_desc() {
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
let mut chroma_desc = ChromaDesc::new(SAMPLE_RATE, 12);
chroma_desc.do_(&song.sample_array).unwrap();
chroma_desc.do_(&song).unwrap();
let expected_values = vec![
-0.35661936,
-0.63578653,
@ -457,9 +457,7 @@ mod test {
#[test]
fn test_chroma_stft_decode() {
let signal = Song::decode(Path::new("data/s16_mono_22_5kHz.flac"))
.unwrap()
.sample_array;
let signal = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
let mut stft = stft(&signal, 8192, 2205);
let file = File::open("data/chroma.npy").unwrap();
@ -490,9 +488,7 @@ mod test {
#[test]
fn test_estimate_tuning_decode() {
let signal = Song::decode(Path::new("data/s16_mono_22_5kHz.flac"))
.unwrap()
.sample_array;
let signal = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
let stft = stft(&signal, 8192, 2205);
let tuning = estimate_tuning(22050, &stft, 8192, 0.01, 12).unwrap();

View File

@ -7,8 +7,8 @@ mod timbral;
mod utils;
use neon::{prelude::*, types::buffer::TypedArray};
// use song::Song;
// use bliss_lib::BlissResult;
use song::Song;
use bliss_lib::BlissResult;
#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
@ -59,13 +59,9 @@ fn analyze(mut cx: FunctionContext) -> JsResult<JsUint8Array> {
Ok(buffer_handle)
}
// fn analyze_raw(path: &str) -> BlissResult<([u8; 2], [u8; 80])> {
// let song = Song::from_path(path)?;
// let version_bytes = song.features_version.to_le_bytes();
// let analysis_bytes = song.analysis.as_bytes();
// Ok((version_bytes, analysis_bytes))
// }
fn analyze_raw(path: &str) -> Result<([u8; 2], [u8; 80]), u8> {
return Ok(([0; 2], [0; 80]));
fn analyze_raw(path: &str) -> BlissResult<([u8; 2], [u8; 80])> {
let song = Song::from_path(path)?;
let version_bytes = song.features_version.to_le_bytes();
let analysis_bytes = song.analysis.as_bytes();
Ok((version_bytes, analysis_bytes))
}

View File

@ -70,7 +70,7 @@ mod tests {
fn test_loudness() {
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
let mut loudness_desc = LoudnessDesc::default();
for chunk in song.sample_array.chunks_exact(LoudnessDesc::WINDOW_SIZE) {
for chunk in song.chunks_exact(LoudnessDesc::WINDOW_SIZE) {
loudness_desc.do_(&chunk);
}
let expected_values = vec![0.271263, 0.2577181];

View File

@ -7,7 +7,6 @@
//! For implementation of plug-ins for already existing audio players,
//! a look at Library is instead recommended.
extern crate crossbeam;
extern crate ffmpeg_next as ffmpeg;
extern crate ndarray;
@ -19,7 +18,6 @@ use crate::bliss_lib::{BlissError, BlissResult, SAMPLE_RATE};
use crate::bliss_lib::{CHANNELS, FEATURES_VERSION};
use ::log::warn;
use core::ops::Index;
use crossbeam::thread;
use ffmpeg_next::codec::threading::{Config, Type as ThreadingType};
use ffmpeg_next::util::channel_layout::ChannelLayout;
use ffmpeg_next::util::error::Error;
@ -35,7 +33,7 @@ use std::fmt;
use std::path::Path;
use std::sync::mpsc;
use std::sync::mpsc::Receiver;
use std::thread as std_thread;
use std::thread;
use strum::{EnumCount, IntoEnumIterator};
use strum_macros::{EnumCount, EnumIter};
@ -234,16 +232,16 @@ impl Song {
*
* If you *do* want to use this with a song already decoded by yourself,
* the sample format of `sample_array` should be f32le, one channel, and
* the sampling rate 22050 Hz. Anything other thant that will yield aberrant
* the sampling rate 22050 Hz. Anything other than that will yield aberrant
* results.
* To double-check that your sample array has the right format, you could run
* `ffmpeg -i path_to_your_song.flac -ar 22050 -ac 1 -c:a pcm_f32le -f hash -hash ripemd160 -`,
* which will give you the ripemd160 hash of the sample array if the song
* has been decoded properly. You can then compute the ripemd160 hash of your sample
* array (see `_test_decode` in the tests) and make sure both hashes are the same.
* `ffmpeg -i path_to_your_song.flac -ar 22050 -ac 1 -c:a pcm_f32le -f hash -hash addler32 -`,
* which will give you the addler32 checksum of the sample array if the song
* has been decoded properly. You can then compute the addler32 checksum of your sample
* array (see `_test_decode` in the tests) and make sure both are the same.
*
* (Running `ffmpeg -i path_to_your_song.flac -ar 22050 -ac 1 -c:a pcm_f32le` will simply give
* you the raw sample array as it should look like, if you're not into computing hashes)
* you the raw sample array as it should look like, if you're not into computing checksums)
**/
pub fn analyze(sample_array: &[f32]) -> BlissResult<Analysis> {
let largest_window = vec![
@ -261,8 +259,8 @@ impl Song {
)));
}
thread::scope(|s| {
let child_tempo: thread::ScopedJoinHandle<'_, BlissResult<f32>> = s.spawn(|_| {
thread::scope(|s| -> BlissResult<Analysis> {
let child_tempo = s.spawn(|| {
let mut tempo_desc = BPMDesc::new(SAMPLE_RATE)?;
let windows = sample_array
.windows(BPMDesc::WINDOW_SIZE)
@ -274,17 +272,14 @@ impl Song {
Ok(tempo_desc.get_value())
});
let child_chroma: thread::ScopedJoinHandle<'_, BlissResult<Vec<f32>>> = s.spawn(|_| {
let child_chroma = s.spawn(|| {
let mut chroma_desc = ChromaDesc::new(SAMPLE_RATE, 12);
chroma_desc.do_(sample_array)?;
Ok(chroma_desc.get_values())
});
#[allow(clippy::type_complexity)]
let child_timbral: thread::ScopedJoinHandle<
'_,
BlissResult<(Vec<f32>, Vec<f32>, Vec<f32>)>,
> = s.spawn(|_| {
let child_timbral = s.spawn(|| {
let mut spectral_desc = SpectralDesc::new(SAMPLE_RATE)?;
let windows = sample_array
.windows(SpectralDesc::WINDOW_SIZE)
@ -298,14 +293,13 @@ impl Song {
Ok((centroid, rolloff, flatness))
});
let child_zcr: thread::ScopedJoinHandle<'_, BlissResult<f32>> = s.spawn(|_| {
let child_zcr = s.spawn(|| {
let mut zcr_desc = ZeroCrossingRateDesc::default();
zcr_desc.do_(sample_array);
Ok(zcr_desc.get_value())
});
let child_loudness: thread::ScopedJoinHandle<'_, BlissResult<Vec<f32>>> =
s.spawn(|_| {
let child_loudness = s.spawn(|| {
let mut loudness_desc = LoudnessDesc::default();
let windows = sample_array.chunks(LoudnessDesc::WINDOW_SIZE);
@ -337,7 +331,6 @@ impl Song {
})?;
Ok(Analysis::new(array))
})
.unwrap()
}
pub(crate) fn decode(path: &Path) -> BlissResult<Vec<f32>> {
@ -412,7 +405,7 @@ impl Song {
let (tx, rx) = mpsc::channel();
let in_codec_format = decoder.format();
let in_codec_rate = decoder.rate();
let child = std_thread::spawn(move || {
let child = thread::spawn(move || {
resample_frame(
rx,
in_codec_format,
@ -598,8 +591,8 @@ fn push_to_sample_array(frame: &ffmpeg::frame::Audio, sample_array: &mut Vec<f32
#[cfg(test)]
mod tests {
use super::*;
use adler32::RollingAdler32;
use pretty_assertions::assert_eq;
use ripemd::{Digest, Ripemd160};
use std::path::Path;
#[test]
@ -648,34 +641,28 @@ mod tests {
assert_eq!(FEATURES_VERSION, song.features_version);
}
fn _test_decode(path: &Path, expected_hash: &[u8]) {
fn _test_decode(path: &Path, expected_hash: u32) {
let samples = Song::decode(path).unwrap();
let mut hasher = Ripemd160::new();
let mut hasher = RollingAdler32::new();
for sample in samples.iter() {
hasher.update(sample.to_le_bytes().to_vec());
hasher.update_buffer(&sample.to_le_bytes());
}
assert_eq!(expected_hash, hasher.finalize().as_slice());
assert_eq!(expected_hash, hasher.hash());
}
#[test]
fn test_resample_multi() {
let path = Path::new("data/s32_stereo_44_1_kHz.flac");
let expected_hash = [
0xc5, 0xf8, 0x23, 0xce, 0x63, 0x2c, 0xf4, 0xa0, 0x72, 0x66, 0xbb, 0x49, 0xad, 0x84,
0xb6, 0xea, 0x48, 0x48, 0x9c, 0x50,
];
_test_decode(&path, &expected_hash);
let expected_hash = 0xbbcba1cf;
_test_decode(&path, expected_hash);
}
#[test]
fn test_resample_stereo() {
let path = Path::new("data/s16_stereo_22_5kHz.flac");
let expected_hash = [
0x24, 0xed, 0x45, 0x58, 0x06, 0xbf, 0xfb, 0x05, 0x57, 0x5f, 0xdc, 0x4d, 0xb4, 0x9b,
0xa5, 0x2b, 0x05, 0x56, 0x10, 0x4f,
];
_test_decode(&path, &expected_hash);
let expected_hash = 0x1d7b2d6d;
_test_decode(&path, expected_hash);
}
#[test]
@ -683,12 +670,9 @@ mod tests {
let path = Path::new("data/s16_mono_22_5kHz.flac");
// Obtained through
// ffmpeg -i data/s16_mono_22_5kHz.flac -ar 22050 -ac 1 -c:a pcm_f32le
// -f hash -hash ripemd160 -
let expected_hash = [
0x9d, 0x95, 0xa5, 0xf2, 0xd2, 0x9c, 0x68, 0xe8, 0x8a, 0x70, 0xcd, 0xf3, 0x54, 0x2c,
0x5b, 0x45, 0x98, 0xb4, 0xf3, 0xb4,
];
_test_decode(&path, &expected_hash);
// -f hash -hash addler32 -
let expected_hash = 0x5e01930b;
_test_decode(&path, expected_hash);
}
#[test]
@ -696,12 +680,9 @@ mod tests {
let path = Path::new("data/s32_stereo_44_1_kHz.mp3");
// Obtained through
// ffmpeg -i data/s16_mono_22_5kHz.mp3 -ar 22050 -ac 1 -c:a pcm_f32le
// -f hash -hash ripemd160 -
let expected_hash = [
0x28, 0x25, 0x6b, 0x7b, 0x6e, 0x37, 0x1c, 0xcf, 0xc7, 0x06, 0xdf, 0x62, 0x8c, 0x0e,
0x91, 0xf7, 0xd6, 0x1f, 0xac, 0x5b,
];
_test_decode(&path, &expected_hash);
// -f hash -hash addler32 -
let expected_hash = 0x69ca6906;
_test_decode(&path, expected_hash);
}
#[test]
@ -757,11 +738,8 @@ mod tests {
#[test]
fn test_decode_wav() {
let expected_hash = [
0xf0, 0xe0, 0x85, 0x4e, 0xf6, 0x53, 0x76, 0xfa, 0x7a, 0xa5, 0x65, 0x76, 0xf9, 0xe1,
0xe8, 0xe0, 0x81, 0xc8, 0xdc, 0x61,
];
_test_decode(Path::new("data/piano.wav"), &expected_hash);
let expected_hash = 0xde831e82;
_test_decode(Path::new("data/piano.wav"), expected_hash);
}
#[test]

View File

@ -101,7 +101,7 @@ mod tests {
fn test_tempo_real() {
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
let mut tempo_desc = BPMDesc::new(SAMPLE_RATE).unwrap();
for chunk in song.sample_array.chunks_exact(BPMDesc::HOP_SIZE) {
for chunk in song.chunks_exact(BPMDesc::HOP_SIZE) {
tempo_desc.do_(&chunk).unwrap();
}
assert!(0.01 > (0.378605 - tempo_desc.get_value()).abs());

View File

@ -283,7 +283,7 @@ mod tests {
fn test_zcr() {
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
let mut zcr_desc = ZeroCrossingRateDesc::default();
for chunk in song.sample_array.chunks_exact(SpectralDesc::HOP_SIZE) {
for chunk in song.chunks_exact(SpectralDesc::HOP_SIZE) {
zcr_desc.do_(&chunk);
}
assert!(0.001 > (-0.85036 - zcr_desc.get_value()).abs());
@ -303,13 +303,14 @@ mod tests {
assert!(0.0000001 > (expected - actual).abs());
}
let song = Song::decode(Path::new("data/white_noise.flac")).unwrap();
let song = Song::decode(Path::new("data/white_noise.mp3")).unwrap();
let mut spectral_desc = SpectralDesc::new(22050).unwrap();
for chunk in song.sample_array.chunks_exact(SpectralDesc::HOP_SIZE) {
for chunk in song.chunks_exact(SpectralDesc::HOP_SIZE) {
spectral_desc.do_(&chunk).unwrap();
}
println!("{:?}", spectral_desc.get_flatness());
// White noise - as close to 1 as possible
let expected_values = vec![0.6706717, -0.9685736];
let expected_values = vec![0.5785303, -0.9426308];
for (expected, actual) in expected_values
.iter()
.zip(spectral_desc.get_flatness().iter())
@ -322,7 +323,7 @@ mod tests {
fn test_spectral_flatness() {
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
let mut spectral_desc = SpectralDesc::new(SAMPLE_RATE).unwrap();
for chunk in song.sample_array.chunks_exact(SpectralDesc::HOP_SIZE) {
for chunk in song.chunks_exact(SpectralDesc::HOP_SIZE) {
spectral_desc.do_(&chunk).unwrap();
}
// Spectral flatness mean value computed here with phase vocoder before normalization: 0.111949615
@ -352,7 +353,7 @@ mod tests {
let song = Song::decode(Path::new("data/tone_11080Hz.flac")).unwrap();
let mut spectral_desc = SpectralDesc::new(SAMPLE_RATE).unwrap();
for chunk in song.sample_array.chunks_exact(SpectralDesc::HOP_SIZE) {
for chunk in song.chunks_exact(SpectralDesc::HOP_SIZE) {
spectral_desc.do_(&chunk).unwrap();
}
let expected_values = vec![0.9967681, -0.99615175];
@ -368,7 +369,7 @@ mod tests {
fn test_spectral_roll_off() {
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
let mut spectral_desc = SpectralDesc::new(SAMPLE_RATE).unwrap();
for chunk in song.sample_array.chunks_exact(SpectralDesc::HOP_SIZE) {
for chunk in song.chunks_exact(SpectralDesc::HOP_SIZE) {
spectral_desc.do_(&chunk).unwrap();
}
let expected_values = vec![-0.6326486, -0.7260933];
@ -386,7 +387,7 @@ mod tests {
fn test_spectral_centroid() {
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
let mut spectral_desc = SpectralDesc::new(SAMPLE_RATE).unwrap();
for chunk in song.sample_array.chunks_exact(SpectralDesc::HOP_SIZE) {
for chunk in song.chunks_exact(SpectralDesc::HOP_SIZE) {
spectral_desc.do_(&chunk).unwrap();
}
// Spectral centroid mean value computed here with phase vocoder before normalization: 1354.2273
@ -415,7 +416,7 @@ mod tests {
}
let song = Song::decode(Path::new("data/tone_11080Hz.flac")).unwrap();
let mut spectral_desc = SpectralDesc::new(SAMPLE_RATE).unwrap();
for chunk in song.sample_array.chunks_exact(SpectralDesc::HOP_SIZE) {
for chunk in song.chunks_exact(SpectralDesc::HOP_SIZE) {
spectral_desc.do_(&chunk).unwrap();
}
let expected_values = vec![0.97266, -0.9609926];

View File

@ -498,7 +498,7 @@ mod tests {
let song = Song::decode(Path::new("data/piano.flac")).unwrap();
let stft = stft(&song.sample_array, 2048, 512);
let stft = stft(&song, 2048, 512);
assert!(!stft.is_empty() && !expected_stft.is_empty());
for (expected, actual) in expected_stft.iter().zip(stft.iter()) {