Compare commits
13 Commits
f446e4ade7
...
6d9489d3a6
Author | SHA1 | Date | |
---|---|---|---|
6d9489d3a6 | |||
|
44f79153ea | ||
|
6b747e224c | ||
|
8d3a3c6433 | ||
|
3fd91bbfae | ||
|
ed2dd72c6b | ||
|
dc3592147c | ||
|
bf35f643fd | ||
|
479fbc7b89 | ||
|
1445939bf8 | ||
|
daee149271 | ||
|
d10a1cf526 | ||
|
f82c98a12e |
|
@ -1,9 +1,13 @@
|
||||||
#Changelog
|
#Changelog
|
||||||
|
|
||||||
|
## bliss 0.6.11
|
||||||
|
* Bump rust-ffmpeg to 6.1.1 to fix build for raspberry pis.
|
||||||
|
|
||||||
## bliss 0.6.10
|
## bliss 0.6.10
|
||||||
* Make the `analyze` function public, for people who don't want to use
|
* Make the `analyze` function public, for people who don't want to use
|
||||||
ffmpeg
|
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
|
## bliss 0.6.9
|
||||||
* Add a feature flag for compilation on raspberry pis.
|
* Add a feature flag for compilation on raspberry pis.
|
||||||
|
|
19
Cargo.lock
generated
19
Cargo.lock
generated
|
@ -2,6 +2,12 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler32"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
|
@ -89,8 +95,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bliss-rs"
|
name = "bliss-rs"
|
||||||
version = "0.6.9"
|
version = "0.6.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"adler32",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bliss-audio-aubio-rs",
|
"bliss-audio-aubio-rs",
|
||||||
"clap",
|
"clap",
|
||||||
|
@ -105,7 +112,6 @@ dependencies = [
|
||||||
"neon",
|
"neon",
|
||||||
"noisy_float",
|
"noisy_float",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"ripemd",
|
|
||||||
"rustfft",
|
"rustfft",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_ini",
|
"serde_ini",
|
||||||
|
@ -823,15 +829,6 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "194d8e591e405d1eecf28819740abed6d719d1a2db87fc0bcdedee9a26d55560"
|
checksum = "194d8e591e405d1eecf28819740abed6d719d1a2db87fc0bcdedee9a26d55560"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ripemd"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f"
|
|
||||||
dependencies = [
|
|
||||||
"digest",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-hash"
|
name = "rustc-hash"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "bliss-rs"
|
name = "bliss-rs"
|
||||||
version = "0.6.9"
|
version = "0.6.11"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
authors = ["Polochon-street <polochonstreet@gmx.fr>"]
|
authors = ["Polochon-street <polochonstreet@gmx.fr>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
@ -10,7 +10,7 @@ homepage = "https://lelele.io/bliss.html"
|
||||||
repository = "https://github.com/Polochon-street/bliss-rs"
|
repository = "https://github.com/Polochon-street/bliss-rs"
|
||||||
keywords = ["audio", "analysis", "MIR", "playlist", "similarity"]
|
keywords = ["audio", "analysis", "MIR", "playlist", "similarity"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
exclude = ["index.node"]
|
exclude = ["data/", "index.node"]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["rlib", "cdylib"]
|
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.
|
# Hopefully we'll be able to use the official aubio-rs at some point.
|
||||||
bliss-audio-aubio-rs = { version = "0.2.1", features = ["static"] }
|
bliss-audio-aubio-rs = { version = "0.2.1", features = ["static"] }
|
||||||
crossbeam = "0.8.2"
|
crossbeam = "0.8.2"
|
||||||
ffmpeg-next = { version = "6.0.0", features = ["static"] }
|
ffmpeg-next = { version = "6.1.1", features = ["static"] }
|
||||||
log = "0.4.17"
|
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 = { version = "0.15.6", features = ["rayon"] }
|
||||||
ndarray-stats = "0.5.1"
|
ndarray-stats = "0.5.1"
|
||||||
noisy_float = "0.2.0"
|
noisy_float = "0.2.0"
|
||||||
ripemd = "0.1.3"
|
adler32 = "1.0.2"
|
||||||
rustfft = "6.1.0"
|
rustfft = "6.1.0"
|
||||||
thiserror = "1.0.40"
|
thiserror = "1.0.40"
|
||||||
strum = "0.24.1"
|
strum = "0.24.1"
|
||||||
|
|
1
ci_check.sh
Executable file
1
ci_check.sh
Executable 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.
BIN
data/picture.jpg
BIN
data/picture.jpg
Binary file not shown.
Before Width: | Height: | Size: 826 KiB |
BIN
data/picture.png
BIN
data/picture.png
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
BIN
data/white_noise.mp3
Normal file
Binary file not shown.
|
@ -13,8 +13,8 @@ fn main() -> Result<(), String> {
|
||||||
let first_path = paths.next().ok_or("Help: ./distance <song1> <song2>")?;
|
let first_path = paths.next().ok_or("Help: ./distance <song1> <song2>")?;
|
||||||
let second_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 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 song2 = Song::from_path(&second_path).map_err(|x| x.to_string())?;
|
||||||
|
|
||||||
let mut distance_squared: f64 = 0.0;
|
let mut distance_squared: f64 = 0.0;
|
||||||
let analysis1 = song1.analysis.as_bytes();
|
let analysis1 = song1.analysis.as_bytes();
|
||||||
|
@ -25,8 +25,8 @@ fn main() -> Result<(), String> {
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"d({:?}, {:?}) = {}",
|
"d({:?}, {:?}) = {}",
|
||||||
song1.path,
|
&first_path,
|
||||||
song2.path,
|
&second_path,
|
||||||
distance_squared.sqrt(),
|
distance_squared.sqrt(),
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -437,7 +437,7 @@ mod test {
|
||||||
fn test_chroma_desc() {
|
fn test_chroma_desc() {
|
||||||
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||||
let mut chroma_desc = ChromaDesc::new(SAMPLE_RATE, 12);
|
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![
|
let expected_values = vec![
|
||||||
-0.35661936,
|
-0.35661936,
|
||||||
-0.63578653,
|
-0.63578653,
|
||||||
|
@ -457,9 +457,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_chroma_stft_decode() {
|
fn test_chroma_stft_decode() {
|
||||||
let signal = Song::decode(Path::new("data/s16_mono_22_5kHz.flac"))
|
let signal = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||||
.unwrap()
|
|
||||||
.sample_array;
|
|
||||||
let mut stft = stft(&signal, 8192, 2205);
|
let mut stft = stft(&signal, 8192, 2205);
|
||||||
|
|
||||||
let file = File::open("data/chroma.npy").unwrap();
|
let file = File::open("data/chroma.npy").unwrap();
|
||||||
|
@ -490,9 +488,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_estimate_tuning_decode() {
|
fn test_estimate_tuning_decode() {
|
||||||
let signal = Song::decode(Path::new("data/s16_mono_22_5kHz.flac"))
|
let signal = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||||
.unwrap()
|
|
||||||
.sample_array;
|
|
||||||
let stft = stft(&signal, 8192, 2205);
|
let stft = stft(&signal, 8192, 2205);
|
||||||
|
|
||||||
let tuning = estimate_tuning(22050, &stft, 8192, 0.01, 12).unwrap();
|
let tuning = estimate_tuning(22050, &stft, 8192, 0.01, 12).unwrap();
|
||||||
|
|
18
src/lib.rs
18
src/lib.rs
|
@ -7,8 +7,8 @@ mod timbral;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use neon::{prelude::*, types::buffer::TypedArray};
|
use neon::{prelude::*, types::buffer::TypedArray};
|
||||||
// use song::Song;
|
use song::Song;
|
||||||
// use bliss_lib::BlissResult;
|
use bliss_lib::BlissResult;
|
||||||
|
|
||||||
#[neon::main]
|
#[neon::main]
|
||||||
fn main(mut cx: ModuleContext) -> NeonResult<()> {
|
fn main(mut cx: ModuleContext) -> NeonResult<()> {
|
||||||
|
@ -59,13 +59,9 @@ fn analyze(mut cx: FunctionContext) -> JsResult<JsUint8Array> {
|
||||||
Ok(buffer_handle)
|
Ok(buffer_handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn analyze_raw(path: &str) -> BlissResult<([u8; 2], [u8; 80])> {
|
fn analyze_raw(path: &str) -> BlissResult<([u8; 2], [u8; 80])> {
|
||||||
// let song = Song::from_path(path)?;
|
let song = Song::from_path(path)?;
|
||||||
// let version_bytes = song.features_version.to_le_bytes();
|
let version_bytes = song.features_version.to_le_bytes();
|
||||||
// let analysis_bytes = song.analysis.as_bytes();
|
let analysis_bytes = song.analysis.as_bytes();
|
||||||
// Ok((version_bytes, analysis_bytes))
|
Ok((version_bytes, analysis_bytes))
|
||||||
// }
|
|
||||||
|
|
||||||
fn analyze_raw(path: &str) -> Result<([u8; 2], [u8; 80]), u8> {
|
|
||||||
return Ok(([0; 2], [0; 80]));
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ mod tests {
|
||||||
fn test_loudness() {
|
fn test_loudness() {
|
||||||
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||||
let mut loudness_desc = LoudnessDesc::default();
|
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);
|
loudness_desc.do_(&chunk);
|
||||||
}
|
}
|
||||||
let expected_values = vec![0.271263, 0.2577181];
|
let expected_values = vec![0.271263, 0.2577181];
|
||||||
|
|
84
src/song.rs
84
src/song.rs
|
@ -7,7 +7,6 @@
|
||||||
//! For implementation of plug-ins for already existing audio players,
|
//! For implementation of plug-ins for already existing audio players,
|
||||||
//! a look at Library is instead recommended.
|
//! a look at Library is instead recommended.
|
||||||
|
|
||||||
extern crate crossbeam;
|
|
||||||
extern crate ffmpeg_next as ffmpeg;
|
extern crate ffmpeg_next as ffmpeg;
|
||||||
extern crate ndarray;
|
extern crate ndarray;
|
||||||
|
|
||||||
|
@ -19,7 +18,6 @@ use crate::bliss_lib::{BlissError, BlissResult, SAMPLE_RATE};
|
||||||
use crate::bliss_lib::{CHANNELS, FEATURES_VERSION};
|
use crate::bliss_lib::{CHANNELS, FEATURES_VERSION};
|
||||||
use ::log::warn;
|
use ::log::warn;
|
||||||
use core::ops::Index;
|
use core::ops::Index;
|
||||||
use crossbeam::thread;
|
|
||||||
use ffmpeg_next::codec::threading::{Config, Type as ThreadingType};
|
use ffmpeg_next::codec::threading::{Config, Type as ThreadingType};
|
||||||
use ffmpeg_next::util::channel_layout::ChannelLayout;
|
use ffmpeg_next::util::channel_layout::ChannelLayout;
|
||||||
use ffmpeg_next::util::error::Error;
|
use ffmpeg_next::util::error::Error;
|
||||||
|
@ -35,7 +33,7 @@ use std::fmt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::sync::mpsc::Receiver;
|
use std::sync::mpsc::Receiver;
|
||||||
use std::thread as std_thread;
|
use std::thread;
|
||||||
use strum::{EnumCount, IntoEnumIterator};
|
use strum::{EnumCount, IntoEnumIterator};
|
||||||
use strum_macros::{EnumCount, EnumIter};
|
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,
|
* 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 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.
|
* results.
|
||||||
* To double-check that your sample array has the right format, you could run
|
* 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 -`,
|
* `ffmpeg -i path_to_your_song.flac -ar 22050 -ac 1 -c:a pcm_f32le -f hash -hash addler32 -`,
|
||||||
* which will give you the ripemd160 hash of the sample array if the song
|
* which will give you the addler32 checksum of the sample array if the song
|
||||||
* has been decoded properly. You can then compute the ripemd160 hash of your sample
|
* 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 hashes are the same.
|
* 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
|
* (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> {
|
pub fn analyze(sample_array: &[f32]) -> BlissResult<Analysis> {
|
||||||
let largest_window = vec![
|
let largest_window = vec![
|
||||||
|
@ -261,8 +259,8 @@ impl Song {
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
thread::scope(|s| {
|
thread::scope(|s| -> BlissResult<Analysis> {
|
||||||
let child_tempo: thread::ScopedJoinHandle<'_, BlissResult<f32>> = s.spawn(|_| {
|
let child_tempo = s.spawn(|| {
|
||||||
let mut tempo_desc = BPMDesc::new(SAMPLE_RATE)?;
|
let mut tempo_desc = BPMDesc::new(SAMPLE_RATE)?;
|
||||||
let windows = sample_array
|
let windows = sample_array
|
||||||
.windows(BPMDesc::WINDOW_SIZE)
|
.windows(BPMDesc::WINDOW_SIZE)
|
||||||
|
@ -274,17 +272,14 @@ impl Song {
|
||||||
Ok(tempo_desc.get_value())
|
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);
|
let mut chroma_desc = ChromaDesc::new(SAMPLE_RATE, 12);
|
||||||
chroma_desc.do_(sample_array)?;
|
chroma_desc.do_(sample_array)?;
|
||||||
Ok(chroma_desc.get_values())
|
Ok(chroma_desc.get_values())
|
||||||
});
|
});
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
let child_timbral: thread::ScopedJoinHandle<
|
let child_timbral = s.spawn(|| {
|
||||||
'_,
|
|
||||||
BlissResult<(Vec<f32>, Vec<f32>, Vec<f32>)>,
|
|
||||||
> = s.spawn(|_| {
|
|
||||||
let mut spectral_desc = SpectralDesc::new(SAMPLE_RATE)?;
|
let mut spectral_desc = SpectralDesc::new(SAMPLE_RATE)?;
|
||||||
let windows = sample_array
|
let windows = sample_array
|
||||||
.windows(SpectralDesc::WINDOW_SIZE)
|
.windows(SpectralDesc::WINDOW_SIZE)
|
||||||
|
@ -298,14 +293,13 @@ impl Song {
|
||||||
Ok((centroid, rolloff, flatness))
|
Ok((centroid, rolloff, flatness))
|
||||||
});
|
});
|
||||||
|
|
||||||
let child_zcr: thread::ScopedJoinHandle<'_, BlissResult<f32>> = s.spawn(|_| {
|
let child_zcr = s.spawn(|| {
|
||||||
let mut zcr_desc = ZeroCrossingRateDesc::default();
|
let mut zcr_desc = ZeroCrossingRateDesc::default();
|
||||||
zcr_desc.do_(sample_array);
|
zcr_desc.do_(sample_array);
|
||||||
Ok(zcr_desc.get_value())
|
Ok(zcr_desc.get_value())
|
||||||
});
|
});
|
||||||
|
|
||||||
let child_loudness: thread::ScopedJoinHandle<'_, BlissResult<Vec<f32>>> =
|
let child_loudness = s.spawn(|| {
|
||||||
s.spawn(|_| {
|
|
||||||
let mut loudness_desc = LoudnessDesc::default();
|
let mut loudness_desc = LoudnessDesc::default();
|
||||||
let windows = sample_array.chunks(LoudnessDesc::WINDOW_SIZE);
|
let windows = sample_array.chunks(LoudnessDesc::WINDOW_SIZE);
|
||||||
|
|
||||||
|
@ -337,7 +331,6 @@ impl Song {
|
||||||
})?;
|
})?;
|
||||||
Ok(Analysis::new(array))
|
Ok(Analysis::new(array))
|
||||||
})
|
})
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn decode(path: &Path) -> BlissResult<Vec<f32>> {
|
pub(crate) fn decode(path: &Path) -> BlissResult<Vec<f32>> {
|
||||||
|
@ -412,7 +405,7 @@ impl Song {
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
let in_codec_format = decoder.format();
|
let in_codec_format = decoder.format();
|
||||||
let in_codec_rate = decoder.rate();
|
let in_codec_rate = decoder.rate();
|
||||||
let child = std_thread::spawn(move || {
|
let child = thread::spawn(move || {
|
||||||
resample_frame(
|
resample_frame(
|
||||||
rx,
|
rx,
|
||||||
in_codec_format,
|
in_codec_format,
|
||||||
|
@ -598,8 +591,8 @@ fn push_to_sample_array(frame: &ffmpeg::frame::Audio, sample_array: &mut Vec<f32
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use adler32::RollingAdler32;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use ripemd::{Digest, Ripemd160};
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -648,34 +641,28 @@ mod tests {
|
||||||
assert_eq!(FEATURES_VERSION, song.features_version);
|
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 samples = Song::decode(path).unwrap();
|
||||||
let mut hasher = Ripemd160::new();
|
let mut hasher = RollingAdler32::new();
|
||||||
for sample in samples.iter() {
|
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]
|
#[test]
|
||||||
fn test_resample_multi() {
|
fn test_resample_multi() {
|
||||||
let path = Path::new("data/s32_stereo_44_1_kHz.flac");
|
let path = Path::new("data/s32_stereo_44_1_kHz.flac");
|
||||||
let expected_hash = [
|
let expected_hash = 0xbbcba1cf;
|
||||||
0xc5, 0xf8, 0x23, 0xce, 0x63, 0x2c, 0xf4, 0xa0, 0x72, 0x66, 0xbb, 0x49, 0xad, 0x84,
|
_test_decode(&path, expected_hash);
|
||||||
0xb6, 0xea, 0x48, 0x48, 0x9c, 0x50,
|
|
||||||
];
|
|
||||||
_test_decode(&path, &expected_hash);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_resample_stereo() {
|
fn test_resample_stereo() {
|
||||||
let path = Path::new("data/s16_stereo_22_5kHz.flac");
|
let path = Path::new("data/s16_stereo_22_5kHz.flac");
|
||||||
let expected_hash = [
|
let expected_hash = 0x1d7b2d6d;
|
||||||
0x24, 0xed, 0x45, 0x58, 0x06, 0xbf, 0xfb, 0x05, 0x57, 0x5f, 0xdc, 0x4d, 0xb4, 0x9b,
|
_test_decode(&path, expected_hash);
|
||||||
0xa5, 0x2b, 0x05, 0x56, 0x10, 0x4f,
|
|
||||||
];
|
|
||||||
_test_decode(&path, &expected_hash);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -683,12 +670,9 @@ mod tests {
|
||||||
let path = Path::new("data/s16_mono_22_5kHz.flac");
|
let path = Path::new("data/s16_mono_22_5kHz.flac");
|
||||||
// Obtained through
|
// Obtained through
|
||||||
// ffmpeg -i data/s16_mono_22_5kHz.flac -ar 22050 -ac 1 -c:a pcm_f32le
|
// ffmpeg -i data/s16_mono_22_5kHz.flac -ar 22050 -ac 1 -c:a pcm_f32le
|
||||||
// -f hash -hash ripemd160 -
|
// -f hash -hash addler32 -
|
||||||
let expected_hash = [
|
let expected_hash = 0x5e01930b;
|
||||||
0x9d, 0x95, 0xa5, 0xf2, 0xd2, 0x9c, 0x68, 0xe8, 0x8a, 0x70, 0xcd, 0xf3, 0x54, 0x2c,
|
_test_decode(&path, expected_hash);
|
||||||
0x5b, 0x45, 0x98, 0xb4, 0xf3, 0xb4,
|
|
||||||
];
|
|
||||||
_test_decode(&path, &expected_hash);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -696,12 +680,9 @@ mod tests {
|
||||||
let path = Path::new("data/s32_stereo_44_1_kHz.mp3");
|
let path = Path::new("data/s32_stereo_44_1_kHz.mp3");
|
||||||
// Obtained through
|
// Obtained through
|
||||||
// ffmpeg -i data/s16_mono_22_5kHz.mp3 -ar 22050 -ac 1 -c:a pcm_f32le
|
// ffmpeg -i data/s16_mono_22_5kHz.mp3 -ar 22050 -ac 1 -c:a pcm_f32le
|
||||||
// -f hash -hash ripemd160 -
|
// -f hash -hash addler32 -
|
||||||
let expected_hash = [
|
let expected_hash = 0x69ca6906;
|
||||||
0x28, 0x25, 0x6b, 0x7b, 0x6e, 0x37, 0x1c, 0xcf, 0xc7, 0x06, 0xdf, 0x62, 0x8c, 0x0e,
|
_test_decode(&path, expected_hash);
|
||||||
0x91, 0xf7, 0xd6, 0x1f, 0xac, 0x5b,
|
|
||||||
];
|
|
||||||
_test_decode(&path, &expected_hash);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -757,11 +738,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_decode_wav() {
|
fn test_decode_wav() {
|
||||||
let expected_hash = [
|
let expected_hash = 0xde831e82;
|
||||||
0xf0, 0xe0, 0x85, 0x4e, 0xf6, 0x53, 0x76, 0xfa, 0x7a, 0xa5, 0x65, 0x76, 0xf9, 0xe1,
|
_test_decode(Path::new("data/piano.wav"), expected_hash);
|
||||||
0xe8, 0xe0, 0x81, 0xc8, 0xdc, 0x61,
|
|
||||||
];
|
|
||||||
_test_decode(Path::new("data/piano.wav"), &expected_hash);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -101,7 +101,7 @@ mod tests {
|
||||||
fn test_tempo_real() {
|
fn test_tempo_real() {
|
||||||
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||||
let mut tempo_desc = BPMDesc::new(SAMPLE_RATE).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();
|
tempo_desc.do_(&chunk).unwrap();
|
||||||
}
|
}
|
||||||
assert!(0.01 > (0.378605 - tempo_desc.get_value()).abs());
|
assert!(0.01 > (0.378605 - tempo_desc.get_value()).abs());
|
||||||
|
|
|
@ -283,7 +283,7 @@ mod tests {
|
||||||
fn test_zcr() {
|
fn test_zcr() {
|
||||||
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||||
let mut zcr_desc = ZeroCrossingRateDesc::default();
|
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);
|
zcr_desc.do_(&chunk);
|
||||||
}
|
}
|
||||||
assert!(0.001 > (-0.85036 - zcr_desc.get_value()).abs());
|
assert!(0.001 > (-0.85036 - zcr_desc.get_value()).abs());
|
||||||
|
@ -303,13 +303,14 @@ mod tests {
|
||||||
assert!(0.0000001 > (expected - actual).abs());
|
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();
|
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();
|
spectral_desc.do_(&chunk).unwrap();
|
||||||
}
|
}
|
||||||
|
println!("{:?}", spectral_desc.get_flatness());
|
||||||
// White noise - as close to 1 as possible
|
// 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
|
for (expected, actual) in expected_values
|
||||||
.iter()
|
.iter()
|
||||||
.zip(spectral_desc.get_flatness().iter())
|
.zip(spectral_desc.get_flatness().iter())
|
||||||
|
@ -322,7 +323,7 @@ mod tests {
|
||||||
fn test_spectral_flatness() {
|
fn test_spectral_flatness() {
|
||||||
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||||
let mut spectral_desc = SpectralDesc::new(SAMPLE_RATE).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_desc.do_(&chunk).unwrap();
|
||||||
}
|
}
|
||||||
// Spectral flatness mean value computed here with phase vocoder before normalization: 0.111949615
|
// 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 song = Song::decode(Path::new("data/tone_11080Hz.flac")).unwrap();
|
||||||
let mut spectral_desc = SpectralDesc::new(SAMPLE_RATE).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_desc.do_(&chunk).unwrap();
|
||||||
}
|
}
|
||||||
let expected_values = vec![0.9967681, -0.99615175];
|
let expected_values = vec![0.9967681, -0.99615175];
|
||||||
|
@ -368,7 +369,7 @@ mod tests {
|
||||||
fn test_spectral_roll_off() {
|
fn test_spectral_roll_off() {
|
||||||
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||||
let mut spectral_desc = SpectralDesc::new(SAMPLE_RATE).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_desc.do_(&chunk).unwrap();
|
||||||
}
|
}
|
||||||
let expected_values = vec![-0.6326486, -0.7260933];
|
let expected_values = vec![-0.6326486, -0.7260933];
|
||||||
|
@ -386,7 +387,7 @@ mod tests {
|
||||||
fn test_spectral_centroid() {
|
fn test_spectral_centroid() {
|
||||||
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||||
let mut spectral_desc = SpectralDesc::new(SAMPLE_RATE).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_desc.do_(&chunk).unwrap();
|
||||||
}
|
}
|
||||||
// Spectral centroid mean value computed here with phase vocoder before normalization: 1354.2273
|
// 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 song = Song::decode(Path::new("data/tone_11080Hz.flac")).unwrap();
|
||||||
let mut spectral_desc = SpectralDesc::new(SAMPLE_RATE).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_desc.do_(&chunk).unwrap();
|
||||||
}
|
}
|
||||||
let expected_values = vec![0.97266, -0.9609926];
|
let expected_values = vec![0.97266, -0.9609926];
|
||||||
|
|
|
@ -498,7 +498,7 @@ mod tests {
|
||||||
|
|
||||||
let song = Song::decode(Path::new("data/piano.flac")).unwrap();
|
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());
|
assert!(!stft.is_empty() && !expected_stft.is_empty());
|
||||||
for (expected, actual) in expected_stft.iter().zip(stft.iter()) {
|
for (expected, actual) in expected_stft.iter().zip(stft.iter()) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user