Add a features' version number
This commit is contained in:
parent
a27b91c6fd
commit
8468a9ab8f
10 changed files with 28 additions and 15 deletions
|
@ -1,5 +1,8 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## bliss 0.4.3
|
||||||
|
* Add features' version on each Song instance.
|
||||||
|
|
||||||
## bliss 0.4.2
|
## bliss 0.4.2
|
||||||
* Add a binary example to easily make playlists.
|
* Add a binary example to easily make playlists.
|
||||||
|
|
||||||
|
|
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -86,7 +86,7 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bliss-audio"
|
name = "bliss-audio"
|
||||||
version = "0.4.2"
|
version = "0.4.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bliss-audio-aubio-rs",
|
"bliss-audio-aubio-rs",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "bliss-audio"
|
name = "bliss-audio"
|
||||||
version = "0.4.2"
|
version = "0.4.3"
|
||||||
authors = ["Polochon-street <polochonstreet@gmx.fr>"]
|
authors = ["Polochon-street <polochonstreet@gmx.fr>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
|
|
|
@ -321,7 +321,7 @@ fn estimate_tuning(
|
||||||
resolution: f64,
|
resolution: f64,
|
||||||
bins_per_octave: u32,
|
bins_per_octave: u32,
|
||||||
) -> BlissResult<f64> {
|
) -> BlissResult<f64> {
|
||||||
let (pitch, mag) = pip_track(sample_rate, &spectrum, n_fft)?;
|
let (pitch, mag) = pip_track(sample_rate, spectrum, n_fft)?;
|
||||||
|
|
||||||
let (filtered_pitch, filtered_mag): (Vec<N64>, Vec<N64>) = pitch
|
let (filtered_pitch, filtered_mag): (Vec<N64>, Vec<N64>) = pitch
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -107,7 +107,7 @@ pub fn dedup_playlist_custom_distance(
|
||||||
distance: impl DistanceMetric,
|
distance: impl DistanceMetric,
|
||||||
) {
|
) {
|
||||||
songs.dedup_by(|s1, s2| {
|
songs.dedup_by(|s1, s2| {
|
||||||
n32(s1.custom_distance(&s2, &distance)) < distance_threshold.unwrap_or(0.05)
|
n32(s1.custom_distance(s2, &distance)) < distance_threshold.unwrap_or(0.05)
|
||||||
|| (s1.title.is_some()
|
|| (s1.title.is_some()
|
||||||
&& s2.title.is_some()
|
&& s2.title.is_some()
|
||||||
&& s1.artist.is_some()
|
&& s1.artist.is_some()
|
||||||
|
|
|
@ -85,6 +85,10 @@ pub use song::{Analysis, AnalysisIndex, Song, NUMBER_FEATURES};
|
||||||
|
|
||||||
const CHANNELS: u16 = 1;
|
const CHANNELS: u16 = 1;
|
||||||
const SAMPLE_RATE: u32 = 22050;
|
const SAMPLE_RATE: u32 = 22050;
|
||||||
|
/// Stores the current version of bliss-rs' features.
|
||||||
|
/// It is bumped every time one or more feature is added, updated or removed,
|
||||||
|
/// so plug-ins can rescan libraries when there is a major change.
|
||||||
|
const FEATURES_VERSION: u16 = 1;
|
||||||
|
|
||||||
#[derive(Error, Clone, Debug, PartialEq)]
|
#[derive(Error, Clone, Debug, PartialEq)]
|
||||||
/// Umbrella type for bliss error types
|
/// Umbrella type for bliss error types
|
||||||
|
|
|
@ -56,7 +56,7 @@ pub trait Library {
|
||||||
|
|
||||||
for song in &songs {
|
for song in &songs {
|
||||||
if let Some(album) = &song.album {
|
if let Some(album) = &song.album {
|
||||||
if let Some(analysis) = albums_analysis.get_mut(&album as &str) {
|
if let Some(analysis) = albums_analysis.get_mut(album as &str) {
|
||||||
analysis
|
analysis
|
||||||
.push_row(song.analysis.as_arr1().view())
|
.push_row(song.analysis.as_arr1().view())
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
|
@ -65,7 +65,7 @@ pub trait Library {
|
||||||
} else {
|
} else {
|
||||||
let mut array = Array::zeros((1, song.analysis.as_arr1().len()));
|
let mut array = Array::zeros((1, song.analysis.as_arr1().len()));
|
||||||
array.assign(&song.analysis.as_arr1());
|
array.assign(&song.analysis.as_arr1());
|
||||||
albums_analysis.insert(&album, array);
|
albums_analysis.insert(album, array);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ pub trait Library {
|
||||||
albums.sort_by_key(|(_, analysis)| {
|
albums.sort_by_key(|(_, analysis)| {
|
||||||
n32(euclidean_distance(
|
n32(euclidean_distance(
|
||||||
first_analysis.as_ref().unwrap(),
|
first_analysis.as_ref().unwrap(),
|
||||||
&analysis,
|
analysis,
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
let albums = albums.get(..playlist_length).unwrap_or(&albums);
|
let albums = albums.get(..playlist_length).unwrap_or(&albums);
|
||||||
|
|
14
src/song.rs
14
src/song.rs
|
@ -12,7 +12,7 @@ extern crate ffmpeg_next as ffmpeg;
|
||||||
extern crate ndarray;
|
extern crate ndarray;
|
||||||
extern crate ndarray_npy;
|
extern crate ndarray_npy;
|
||||||
|
|
||||||
use super::CHANNELS;
|
use crate::{CHANNELS, FEATURES_VERSION};
|
||||||
use crate::chroma::ChromaDesc;
|
use crate::chroma::ChromaDesc;
|
||||||
use crate::distance::{euclidean_distance, DistanceMetric};
|
use crate::distance::{euclidean_distance, DistanceMetric};
|
||||||
use crate::misc::LoudnessDesc;
|
use crate::misc::LoudnessDesc;
|
||||||
|
@ -61,6 +61,10 @@ pub struct Song {
|
||||||
pub genre: Option<String>,
|
pub genre: Option<String>,
|
||||||
/// bliss analysis results
|
/// bliss analysis results
|
||||||
pub analysis: Analysis,
|
pub analysis: Analysis,
|
||||||
|
/// Version of the features the song was analyzed with.
|
||||||
|
/// A simple integer that is bumped every time a breaking change
|
||||||
|
/// is introduced in the features.
|
||||||
|
pub features_version: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, EnumIter, EnumCount)]
|
#[derive(Debug, EnumIter, EnumCount)]
|
||||||
|
@ -251,6 +255,7 @@ impl Song {
|
||||||
track_number: raw_song.track_number,
|
track_number: raw_song.track_number,
|
||||||
genre: raw_song.genre,
|
genre: raw_song.genre,
|
||||||
analysis: Song::analyse(raw_song.sample_array)?,
|
analysis: Song::analyse(raw_song.sample_array)?,
|
||||||
|
features_version: FEATURES_VERSION,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,7 +294,7 @@ impl Song {
|
||||||
.step_by(BPMDesc::HOP_SIZE);
|
.step_by(BPMDesc::HOP_SIZE);
|
||||||
|
|
||||||
for window in windows {
|
for window in windows {
|
||||||
tempo_desc.do_(&window)?;
|
tempo_desc.do_(window)?;
|
||||||
}
|
}
|
||||||
Ok(tempo_desc.get_value())
|
Ok(tempo_desc.get_value())
|
||||||
});
|
});
|
||||||
|
@ -310,7 +315,7 @@ impl Song {
|
||||||
.windows(SpectralDesc::WINDOW_SIZE)
|
.windows(SpectralDesc::WINDOW_SIZE)
|
||||||
.step_by(SpectralDesc::HOP_SIZE);
|
.step_by(SpectralDesc::HOP_SIZE);
|
||||||
for window in windows {
|
for window in windows {
|
||||||
spectral_desc.do_(&window)?;
|
spectral_desc.do_(window)?;
|
||||||
}
|
}
|
||||||
let centroid = spectral_desc.get_centroid();
|
let centroid = spectral_desc.get_centroid();
|
||||||
let rolloff = spectral_desc.get_rolloff();
|
let rolloff = spectral_desc.get_rolloff();
|
||||||
|
@ -330,7 +335,7 @@ impl Song {
|
||||||
let windows = sample_array.chunks(LoudnessDesc::WINDOW_SIZE);
|
let windows = sample_array.chunks(LoudnessDesc::WINDOW_SIZE);
|
||||||
|
|
||||||
for window in windows {
|
for window in windows {
|
||||||
loudness_desc.do_(&window);
|
loudness_desc.do_(window);
|
||||||
}
|
}
|
||||||
Ok(loudness_desc.get_value())
|
Ok(loudness_desc.get_value())
|
||||||
});
|
});
|
||||||
|
@ -657,6 +662,7 @@ mod tests {
|
||||||
for (x, y) in song.analysis.as_vec().iter().zip(expected_analysis) {
|
for (x, y) in song.analysis.as_vec().iter().zip(expected_analysis) {
|
||||||
assert!(0.01 > (x - y).abs());
|
assert!(0.01 > (x - y).abs());
|
||||||
}
|
}
|
||||||
|
assert_eq!(FEATURES_VERSION, song.features_version);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _test_decode(path: &Path, expected_hash: &[u8]) {
|
fn _test_decode(path: &Path, expected_hash: &[u8]) {
|
||||||
|
|
|
@ -204,12 +204,12 @@ impl SpectralDesc {
|
||||||
self.values_rolloff.push(freq);
|
self.values_rolloff.push(freq);
|
||||||
|
|
||||||
let cvec: CVec = fftgrain.as_slice().into();
|
let cvec: CVec = fftgrain.as_slice().into();
|
||||||
let geo_mean = geometric_mean(&cvec.norm());
|
let geo_mean = geometric_mean(cvec.norm());
|
||||||
if geo_mean == 0.0 {
|
if geo_mean == 0.0 {
|
||||||
self.values_flatness.push(0.0);
|
self.values_flatness.push(0.0);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let flatness = geo_mean / mean(&cvec.norm());
|
let flatness = geo_mean / mean(cvec.norm());
|
||||||
self.values_flatness.push(flatness);
|
self.values_flatness.push(flatness);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ pub(crate) fn stft(signal: &[f32], window_length: usize, hop_length: usize) -> A
|
||||||
(signal.len() as f32 / hop_length as f32).ceil() as usize,
|
(signal.len() as f32 / hop_length as f32).ceil() as usize,
|
||||||
window_length / 2 + 1,
|
window_length / 2 + 1,
|
||||||
));
|
));
|
||||||
let signal = reflect_pad(&signal, window_length / 2);
|
let signal = reflect_pad(signal, window_length / 2);
|
||||||
|
|
||||||
// Periodic, so window_size + 1
|
// Periodic, so window_size + 1
|
||||||
let mut hann_window = Array::zeros(window_length + 1);
|
let mut hann_window = Array::zeros(window_length + 1);
|
||||||
|
@ -45,7 +45,7 @@ pub(crate) fn stft(signal: &[f32], window_length: usize, hop_length: usize) -> A
|
||||||
.step_by(hop_length)
|
.step_by(hop_length)
|
||||||
.zip(stft.rows_mut())
|
.zip(stft.rows_mut())
|
||||||
{
|
{
|
||||||
let mut signal = (arr1(&window) * &hann_window).mapv(|x| Complex::new(x, 0.));
|
let mut signal = (arr1(window) * &hann_window).mapv(|x| Complex::new(x, 0.));
|
||||||
match signal.as_slice_mut() {
|
match signal.as_slice_mut() {
|
||||||
Some(s) => fft.process(s),
|
Some(s) => fft.process(s),
|
||||||
None => {
|
None => {
|
||||||
|
|
Loading…
Reference in a new issue