Merge pull request #13 from Polochon-street/review-comments
Some review comments
This commit is contained in:
commit
138ff39dd1
14 changed files with 181 additions and 131 deletions
|
@ -1,5 +1,10 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## bliss 0.3.0
|
||||||
|
* Changed `Song.path` from `String` to `PathBuf`.
|
||||||
|
* Made `Song` metadata (artist, album, etc) `Option`s.
|
||||||
|
* Added a `BlissResult` error type.
|
||||||
|
|
||||||
## bliss 0.2.6
|
## bliss 0.2.6
|
||||||
* Fixed an allocation bug in Song::decode that potentially caused segfaults.
|
* Fixed an allocation bug in Song::decode that potentially caused segfaults.
|
||||||
|
|
||||||
|
|
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -75,7 +75,7 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bliss-audio"
|
name = "bliss-audio"
|
||||||
version = "0.2.6"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bliss-audio-aubio-rs",
|
"bliss-audio-aubio-rs",
|
||||||
"crossbeam",
|
"crossbeam",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "bliss-audio"
|
name = "bliss-audio"
|
||||||
version = "0.2.6"
|
version = "0.3.0"
|
||||||
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"
|
||||||
|
@ -19,9 +19,12 @@ no-default-features = true
|
||||||
# https://github.com/zmwangx/rust-ffmpeg/pull/60
|
# https://github.com/zmwangx/rust-ffmpeg/pull/60
|
||||||
# or https://github.com/zmwangx/rust-ffmpeg/pull/62 is in
|
# or https://github.com/zmwangx/rust-ffmpeg/pull/62 is in
|
||||||
default = ["bliss-audio-aubio-rs/static", "build-ffmpeg"]
|
default = ["bliss-audio-aubio-rs/static", "build-ffmpeg"]
|
||||||
|
# Build ffmpeg instead of using the host's.
|
||||||
build-ffmpeg = ["ffmpeg-next/build"]
|
build-ffmpeg = ["ffmpeg-next/build"]
|
||||||
bench = []
|
# Use if you want to build python bindings with maturin.
|
||||||
python-bindings = ["bliss-audio-aubio-rs/fftw3"]
|
python-bindings = ["bliss-audio-aubio-rs/fftw3"]
|
||||||
|
# Enable the benchmarks with `cargo +nightly bench --features=bench`
|
||||||
|
bench = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ripemd160 = "0.9.0"
|
ripemd160 = "0.9.0"
|
||||||
|
|
|
@ -8,7 +8,8 @@ library used to make playlists by analyzing songs, and computing distance betwee
|
||||||
|
|
||||||
Like bliss, it eases the creation of « intelligent » playlists and/or continuous
|
Like bliss, it eases the creation of « intelligent » playlists and/or continuous
|
||||||
play, à la Spotify/Grooveshark Radio, as well as easing creating plug-ins for
|
play, à la Spotify/Grooveshark Radio, as well as easing creating plug-ins for
|
||||||
existing audio players.
|
existing audio players. For instance, you can use it to make calm playlists
|
||||||
|
to help you sleeping, fast playlists to get you started during the day, etc.
|
||||||
|
|
||||||
For now (and if you're looking for an easy-to use smooth play experience),
|
For now (and if you're looking for an easy-to use smooth play experience),
|
||||||
[blissify](https://crates.io/crates/blissify) implements bliss for
|
[blissify](https://crates.io/crates/blissify) implements bliss for
|
||||||
|
|
BIN
data/no_tags.flac
Normal file
BIN
data/no_tags.flac
Normal file
Binary file not shown.
|
@ -17,7 +17,7 @@ fn main() -> Result<(), String> {
|
||||||
let song2 = Song::new(&second_path).map_err(|x| x.to_string())?;
|
let song2 = Song::new(&second_path).map_err(|x| x.to_string())?;
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"d({}, {}) = {}",
|
"d({:?}, {:?}) = {}",
|
||||||
song1.path,
|
song1.path,
|
||||||
song2.path,
|
song2.path,
|
||||||
song1.distance(&song2)
|
song1.distance(&song2)
|
||||||
|
|
|
@ -7,7 +7,7 @@ extern crate noisy_float;
|
||||||
|
|
||||||
use crate::utils::stft;
|
use crate::utils::stft;
|
||||||
use crate::utils::{hz_to_octs_inplace, Normalize};
|
use crate::utils::{hz_to_octs_inplace, Normalize};
|
||||||
use crate::BlissError;
|
use crate::{BlissError, BlissResult};
|
||||||
use ndarray::{arr1, arr2, concatenate, s, Array, Array1, Array2, Axis, Zip};
|
use ndarray::{arr1, arr2, concatenate, s, Array, Array1, Array2, Axis, Zip};
|
||||||
use ndarray_stats::interpolate::Midpoint;
|
use ndarray_stats::interpolate::Midpoint;
|
||||||
use ndarray_stats::QuantileExt;
|
use ndarray_stats::QuantileExt;
|
||||||
|
@ -51,7 +51,7 @@ impl ChromaDesc {
|
||||||
* Passing a full song here once instead of streaming smaller parts of the
|
* Passing a full song here once instead of streaming smaller parts of the
|
||||||
* song will greatly improve accuracy.
|
* song will greatly improve accuracy.
|
||||||
*/
|
*/
|
||||||
pub fn do_(&mut self, signal: &[f32]) -> Result<(), BlissError> {
|
pub fn do_(&mut self, signal: &[f32]) -> BlissResult<()> {
|
||||||
let mut stft = stft(signal, ChromaDesc::WINDOW_SIZE, 2205);
|
let mut stft = stft(signal, ChromaDesc::WINDOW_SIZE, 2205);
|
||||||
let tuning = estimate_tuning(
|
let tuning = estimate_tuning(
|
||||||
self.sample_rate as u32,
|
self.sample_rate as u32,
|
||||||
|
@ -155,7 +155,7 @@ fn chroma_filter(
|
||||||
n_fft: usize,
|
n_fft: usize,
|
||||||
n_chroma: u32,
|
n_chroma: u32,
|
||||||
tuning: f64,
|
tuning: f64,
|
||||||
) -> Result<Array2<f64>, BlissError> {
|
) -> BlissResult<Array2<f64>> {
|
||||||
let ctroct = 5.0;
|
let ctroct = 5.0;
|
||||||
let octwidth = 2.;
|
let octwidth = 2.;
|
||||||
let n_chroma_float = f64::from(n_chroma);
|
let n_chroma_float = f64::from(n_chroma);
|
||||||
|
@ -226,7 +226,7 @@ fn pip_track(
|
||||||
sample_rate: u32,
|
sample_rate: u32,
|
||||||
spectrum: &Array2<f64>,
|
spectrum: &Array2<f64>,
|
||||||
n_fft: usize,
|
n_fft: usize,
|
||||||
) -> Result<(Vec<f64>, Vec<f64>), BlissError> {
|
) -> BlissResult<(Vec<f64>, Vec<f64>)> {
|
||||||
let sample_rate_float = f64::from(sample_rate);
|
let sample_rate_float = f64::from(sample_rate);
|
||||||
let fmin = 150.0_f64;
|
let fmin = 150.0_f64;
|
||||||
let fmax = 4000.0_f64.min(sample_rate_float / 2.0);
|
let fmax = 4000.0_f64.min(sample_rate_float / 2.0);
|
||||||
|
@ -291,7 +291,7 @@ fn pitch_tuning(
|
||||||
frequencies: &mut Array1<f64>,
|
frequencies: &mut Array1<f64>,
|
||||||
resolution: f64,
|
resolution: f64,
|
||||||
bins_per_octave: u32,
|
bins_per_octave: u32,
|
||||||
) -> Result<f64, BlissError> {
|
) -> BlissResult<f64> {
|
||||||
if frequencies.is_empty() {
|
if frequencies.is_empty() {
|
||||||
return Ok(0.0);
|
return Ok(0.0);
|
||||||
}
|
}
|
||||||
|
@ -320,7 +320,7 @@ fn estimate_tuning(
|
||||||
n_fft: usize,
|
n_fft: usize,
|
||||||
resolution: f64,
|
resolution: f64,
|
||||||
bins_per_octave: u32,
|
bins_per_octave: u32,
|
||||||
) -> Result<f64, BlissError> {
|
) -> 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
|
||||||
|
@ -372,6 +372,7 @@ mod test {
|
||||||
use ndarray::{arr1, arr2, Array2};
|
use ndarray::{arr1, arr2, Array2};
|
||||||
use ndarray_npy::ReadNpyExt;
|
use ndarray_npy::ReadNpyExt;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_chroma_interval_features() {
|
fn test_chroma_interval_features() {
|
||||||
|
@ -437,7 +438,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_chroma_desc() {
|
fn test_chroma_desc() {
|
||||||
let song = Song::decode("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.sample_array).unwrap();
|
||||||
let expected_values = vec![
|
let expected_values = vec![
|
||||||
|
@ -459,7 +460,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_chroma_stft_decode() {
|
fn test_chroma_stft_decode() {
|
||||||
let signal = Song::decode("data/s16_mono_22_5kHz.flac")
|
let signal = Song::decode(Path::new("data/s16_mono_22_5kHz.flac"))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.sample_array;
|
.sample_array;
|
||||||
let mut stft = stft(&signal, 8192, 2205);
|
let mut stft = stft(&signal, 8192, 2205);
|
||||||
|
@ -487,7 +488,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_estimate_tuning_decode() {
|
fn test_estimate_tuning_decode() {
|
||||||
let signal = Song::decode("data/s16_mono_22_5kHz.flac")
|
let signal = Song::decode(Path::new("data/s16_mono_22_5kHz.flac"))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.sample_array;
|
.sample_array;
|
||||||
let stft = stft(&signal, 8192, 2205);
|
let stft = stft(&signal, 8192, 2205);
|
||||||
|
@ -556,6 +557,7 @@ mod bench {
|
||||||
use ndarray_npy::ReadNpyExt;
|
use ndarray_npy::ReadNpyExt;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_estimate_tuning(b: &mut Bencher) {
|
fn bench_estimate_tuning(b: &mut Bencher) {
|
||||||
|
@ -603,7 +605,7 @@ mod bench {
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_chroma_desc(b: &mut Bencher) {
|
fn bench_chroma_desc(b: &mut Bencher) {
|
||||||
let song = Song::decode("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);
|
||||||
let signal = song.sample_array;
|
let signal = song.sample_array;
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
|
@ -614,7 +616,7 @@ mod bench {
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_chroma_stft(b: &mut Bencher) {
|
fn bench_chroma_stft(b: &mut Bencher) {
|
||||||
let song = Song::decode("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);
|
||||||
let signal = song.sample_array;
|
let signal = song.sample_array;
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
|
@ -625,7 +627,7 @@ mod bench {
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_chroma_stft_decode(b: &mut Bencher) {
|
fn bench_chroma_stft_decode(b: &mut Bencher) {
|
||||||
let signal = Song::decode("data/s16_mono_22_5kHz.flac")
|
let signal = Song::decode(Path::new("data/s16_mono_22_5kHz.flac"))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.sample_array;
|
.sample_array;
|
||||||
let mut stft = stft(&signal, 8192, 2205);
|
let mut stft = stft(&signal, 8192, 2205);
|
||||||
|
|
21
src/lib.rs
21
src/lib.rs
|
@ -24,9 +24,9 @@
|
||||||
//!
|
//!
|
||||||
//! ## Analyze & compute the distance between two songs
|
//! ## Analyze & compute the distance between two songs
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! use bliss_audio::{BlissError, Song};
|
//! use bliss_audio::{BlissResult, Song};
|
||||||
//!
|
//!
|
||||||
//! fn main() -> Result<(), BlissError> {
|
//! fn main() -> BlissResult<()> {
|
||||||
//! let song1 = Song::new("/path/to/song1")?;
|
//! let song1 = Song::new("/path/to/song1")?;
|
||||||
//! let song2 = Song::new("/path/to/song2")?;
|
//! let song2 = Song::new("/path/to/song2")?;
|
||||||
//!
|
//!
|
||||||
|
@ -37,15 +37,15 @@
|
||||||
//!
|
//!
|
||||||
//! ### Make a playlist from a song
|
//! ### Make a playlist from a song
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! use bliss_audio::{BlissError, Song};
|
//! use bliss_audio::{BlissResult, Song};
|
||||||
//! use noisy_float::prelude::n32;
|
//! use noisy_float::prelude::n32;
|
||||||
//!
|
//!
|
||||||
//! fn main() -> Result<(), BlissError> {
|
//! fn main() -> BlissResult<()> {
|
||||||
//! let paths = vec!["/path/to/song1", "/path/to/song2", "/path/to/song3"];
|
//! let paths = vec!["/path/to/song1", "/path/to/song2", "/path/to/song3"];
|
||||||
//! let mut songs: Vec<Song> = paths
|
//! let mut songs: Vec<Song> = paths
|
||||||
//! .iter()
|
//! .iter()
|
||||||
//! .map(|path| Song::new(path))
|
//! .map(|path| Song::new(path))
|
||||||
//! .collect::<Result<Vec<Song>, BlissError>>()?;
|
//! .collect::<BlissResult<Vec<Song>>>()?;
|
||||||
//!
|
//!
|
||||||
//! // Assuming there is a first song
|
//! // Assuming there is a first song
|
||||||
//! let first_song = songs.first().unwrap().to_owned();
|
//! let first_song = songs.first().unwrap().to_owned();
|
||||||
|
@ -55,8 +55,8 @@
|
||||||
//! "Playlist is: {:?}",
|
//! "Playlist is: {:?}",
|
||||||
//! songs
|
//! songs
|
||||||
//! .iter()
|
//! .iter()
|
||||||
//! .map(|song| &song.path)
|
//! .map(|song| song.path.to_string_lossy().to_string())
|
||||||
//! .collect::<Vec<&String>>()
|
//! .collect::<Vec<String>>()
|
||||||
//! );
|
//! );
|
||||||
//! Ok(())
|
//! Ok(())
|
||||||
//! }
|
//! }
|
||||||
|
@ -100,13 +100,16 @@ pub enum BlissError {
|
||||||
ProviderError(String),
|
ProviderError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// bliss error type
|
||||||
|
pub type BlissResult<T> = Result<T, BlissError>;
|
||||||
|
|
||||||
/// Simple function to bulk analyze a set of songs represented by their
|
/// Simple function to bulk analyze a set of songs represented by their
|
||||||
/// absolute paths.
|
/// absolute paths.
|
||||||
///
|
///
|
||||||
/// When making an extension for an audio player, prefer
|
/// When making an extension for an audio player, prefer
|
||||||
/// implementing the `Library` trait.
|
/// implementing the `Library` trait.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn bulk_analyse(paths: Vec<String>) -> Vec<Result<Song, BlissError>> {
|
pub fn bulk_analyse(paths: Vec<String>) -> Vec<BlissResult<Song>> {
|
||||||
let mut songs = Vec::with_capacity(paths.len());
|
let mut songs = Vec::with_capacity(paths.len());
|
||||||
let num_cpus = num_cpus::get();
|
let num_cpus = num_cpus::get();
|
||||||
|
|
||||||
|
@ -175,7 +178,7 @@ mod tests {
|
||||||
|
|
||||||
let mut analysed_songs: Vec<String> = results
|
let mut analysed_songs: Vec<String> = results
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|x| x.as_ref().ok().map(|x| x.path.to_string()))
|
.filter_map(|x| x.as_ref().ok().map(|x| x.path.to_str().unwrap().to_string()))
|
||||||
.collect();
|
.collect();
|
||||||
analysed_songs.sort_by(|a, b| a.cmp(b));
|
analysed_songs.sort_by(|a, b| a.cmp(b));
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! Module containing the Library trait, useful to get started to implement
|
//! Module containing the Library trait, useful to get started to implement
|
||||||
//! a plug-in for an audio player.
|
//! a plug-in for an audio player.
|
||||||
use crate::{BlissError, Song};
|
use crate::{BlissError, BlissResult, Song};
|
||||||
use log::{debug, error, info};
|
use log::{debug, error, info};
|
||||||
use noisy_float::prelude::*;
|
use noisy_float::prelude::*;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
@ -11,18 +11,18 @@ use std::thread;
|
||||||
pub trait Library {
|
pub trait Library {
|
||||||
/// Return the absolute path of all the songs in an
|
/// Return the absolute path of all the songs in an
|
||||||
/// audio player's music library.
|
/// audio player's music library.
|
||||||
fn get_songs_paths(&self) -> Result<Vec<String>, BlissError>;
|
fn get_songs_paths(&self) -> BlissResult<Vec<String>>;
|
||||||
/// Store an analyzed Song object in some (cold) storage, e.g.
|
/// Store an analyzed Song object in some (cold) storage, e.g.
|
||||||
/// a database, a file...
|
/// a database, a file...
|
||||||
fn store_song(&mut self, song: &Song) -> Result<(), BlissError>;
|
fn store_song(&mut self, song: &Song) -> BlissResult<()>;
|
||||||
/// Log and / or store that an error happened while trying to decode and
|
/// Log and / or store that an error happened while trying to decode and
|
||||||
/// analyze a song.
|
/// analyze a song.
|
||||||
fn store_error_song(&mut self, song_path: String, error: BlissError) -> Result<(), BlissError>;
|
fn store_error_song(&mut self, song_path: String, error: BlissError) -> BlissResult<()>;
|
||||||
/// Retrieve a list of all the stored Songs.
|
/// Retrieve a list of all the stored Songs.
|
||||||
///
|
///
|
||||||
/// This should work only after having run `analyze_library` at least
|
/// This should work only after having run `analyze_library` at least
|
||||||
/// once.
|
/// once.
|
||||||
fn get_stored_songs(&self) -> Result<Vec<Song>, BlissError>;
|
fn get_stored_songs(&self) -> BlissResult<Vec<Song>>;
|
||||||
|
|
||||||
/// Return a list of songs that are similar to ``first_song``.
|
/// Return a list of songs that are similar to ``first_song``.
|
||||||
///
|
///
|
||||||
|
@ -41,7 +41,7 @@ pub trait Library {
|
||||||
&self,
|
&self,
|
||||||
first_song: Song,
|
first_song: Song,
|
||||||
playlist_length: usize,
|
playlist_length: usize,
|
||||||
) -> Result<Vec<Song>, BlissError> {
|
) -> BlissResult<Vec<Song>> {
|
||||||
let analysis_current_song = first_song.analysis;
|
let analysis_current_song = first_song.analysis;
|
||||||
let mut songs = self.get_stored_songs()?;
|
let mut songs = self.get_stored_songs()?;
|
||||||
songs.sort_by_cached_key(|song| n32(analysis_current_song.distance(&song.analysis)));
|
songs.sort_by_cached_key(|song| n32(analysis_current_song.distance(&song.analysis)));
|
||||||
|
@ -59,7 +59,7 @@ pub trait Library {
|
||||||
///
|
///
|
||||||
/// Note: this is mostly useful for updating a song library. For the first
|
/// Note: this is mostly useful for updating a song library. For the first
|
||||||
/// run, you probably want to use `analyze_library`.
|
/// run, you probably want to use `analyze_library`.
|
||||||
fn analyze_paths(&mut self, paths: Vec<String>) -> Result<(), BlissError> {
|
fn analyze_paths(&mut self, paths: Vec<String>) -> BlissResult<()> {
|
||||||
if paths.is_empty() {
|
if paths.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -67,8 +67,8 @@ pub trait Library {
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
let (tx, rx): (
|
let (tx, rx): (
|
||||||
Sender<(String, Result<Song, BlissError>)>,
|
Sender<(String, BlissResult<Song>)>,
|
||||||
Receiver<(String, Result<Song, BlissError>)>,
|
Receiver<(String, BlissResult<Song>)>,
|
||||||
) = mpsc::channel();
|
) = mpsc::channel();
|
||||||
let mut handles = Vec::new();
|
let mut handles = Vec::new();
|
||||||
let mut chunk_length = paths.len() / num_cpus;
|
let mut chunk_length = paths.len() / num_cpus;
|
||||||
|
@ -96,8 +96,8 @@ pub trait Library {
|
||||||
match song {
|
match song {
|
||||||
Ok(song) => {
|
Ok(song) => {
|
||||||
self.store_song(&song)
|
self.store_song(&song)
|
||||||
.unwrap_or_else(|_| error!("Error while storing song '{}'", song.path));
|
.unwrap_or_else(|_| error!("Error while storing song '{}'", song.path.display()));
|
||||||
info!("Analyzed and stored song '{}' successfully.", song.path)
|
info!("Analyzed and stored song '{}' successfully.", song.path.display())
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.store_error_song(path.to_string(), e.to_owned())
|
self.store_error_song(path.to_string(), e.to_owned())
|
||||||
|
@ -122,7 +122,7 @@ pub trait Library {
|
||||||
|
|
||||||
/// Analyzes a song library, using `get_songs_paths`, `store_song` and
|
/// Analyzes a song library, using `get_songs_paths`, `store_song` and
|
||||||
/// `store_error_song` implementations.
|
/// `store_error_song` implementations.
|
||||||
fn analyze_library(&mut self) -> Result<(), BlissError> {
|
fn analyze_library(&mut self) -> BlissResult<()> {
|
||||||
let paths = self
|
let paths = self
|
||||||
.get_songs_paths()
|
.get_songs_paths()
|
||||||
.map_err(|e| BlissError::ProviderError(e.to_string()))?;
|
.map_err(|e| BlissError::ProviderError(e.to_string()))?;
|
||||||
|
@ -135,6 +135,7 @@ pub trait Library {
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::song::Analysis;
|
use crate::song::Analysis;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct TestLibrary {
|
struct TestLibrary {
|
||||||
|
@ -143,7 +144,7 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Library for TestLibrary {
|
impl Library for TestLibrary {
|
||||||
fn get_songs_paths(&self) -> Result<Vec<String>, BlissError> {
|
fn get_songs_paths(&self) -> BlissResult<Vec<String>> {
|
||||||
Ok(vec![
|
Ok(vec![
|
||||||
String::from("./data/white_noise.flac"),
|
String::from("./data/white_noise.flac"),
|
||||||
String::from("./data/s16_mono_22_5kHz.flac"),
|
String::from("./data/s16_mono_22_5kHz.flac"),
|
||||||
|
@ -152,7 +153,7 @@ mod test {
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn store_song(&mut self, song: &Song) -> Result<(), BlissError> {
|
fn store_song(&mut self, song: &Song) -> BlissResult<()> {
|
||||||
self.internal_storage.push(song.to_owned());
|
self.internal_storage.push(song.to_owned());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -161,12 +162,12 @@ mod test {
|
||||||
&mut self,
|
&mut self,
|
||||||
song_path: String,
|
song_path: String,
|
||||||
error: BlissError,
|
error: BlissError,
|
||||||
) -> Result<(), BlissError> {
|
) -> BlissResult<()> {
|
||||||
self.failed_files.push((song_path, error.to_string()));
|
self.failed_files.push((song_path, error.to_string()));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_stored_songs(&self) -> Result<Vec<Song>, BlissError> {
|
fn get_stored_songs(&self) -> BlissResult<Vec<Song>> {
|
||||||
Ok(self.internal_storage.to_owned())
|
Ok(self.internal_storage.to_owned())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,23 +176,23 @@ mod test {
|
||||||
struct FailingLibrary;
|
struct FailingLibrary;
|
||||||
|
|
||||||
impl Library for FailingLibrary {
|
impl Library for FailingLibrary {
|
||||||
fn get_songs_paths(&self) -> Result<Vec<String>, BlissError> {
|
fn get_songs_paths(&self) -> BlissResult<Vec<String>> {
|
||||||
Err(BlissError::ProviderError(String::from(
|
Err(BlissError::ProviderError(String::from(
|
||||||
"Could not get songs path",
|
"Could not get songs path",
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn store_song(&mut self, _: &Song) -> Result<(), BlissError> {
|
fn store_song(&mut self, _: &Song) -> BlissResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_stored_songs(&self) -> Result<Vec<Song>, BlissError> {
|
fn get_stored_songs(&self) -> BlissResult<Vec<Song>> {
|
||||||
Err(BlissError::ProviderError(String::from(
|
Err(BlissError::ProviderError(String::from(
|
||||||
"Could not get stored songs",
|
"Could not get stored songs",
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn store_error_song(&mut self, _: String, _: BlissError) -> Result<(), BlissError> {
|
fn store_error_song(&mut self, _: String, _: BlissError) -> BlissResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,7 +201,7 @@ mod test {
|
||||||
struct FailingStorage;
|
struct FailingStorage;
|
||||||
|
|
||||||
impl Library for FailingStorage {
|
impl Library for FailingStorage {
|
||||||
fn get_songs_paths(&self) -> Result<Vec<String>, BlissError> {
|
fn get_songs_paths(&self) -> BlissResult<Vec<String>> {
|
||||||
Ok(vec![
|
Ok(vec![
|
||||||
String::from("./data/white_noise.flac"),
|
String::from("./data/white_noise.flac"),
|
||||||
String::from("./data/s16_mono_22_5kHz.flac"),
|
String::from("./data/s16_mono_22_5kHz.flac"),
|
||||||
|
@ -209,14 +210,14 @@ mod test {
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn store_song(&mut self, song: &Song) -> Result<(), BlissError> {
|
fn store_song(&mut self, song: &Song) -> BlissResult<()> {
|
||||||
Err(BlissError::ProviderError(format!(
|
Err(BlissError::ProviderError(format!(
|
||||||
"Could not store song {}",
|
"Could not store song {}",
|
||||||
song.path
|
song.path.display()
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_stored_songs(&self) -> Result<Vec<Song>, BlissError> {
|
fn get_stored_songs(&self) -> BlissResult<Vec<Song>> {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,7 +225,7 @@ mod test {
|
||||||
&mut self,
|
&mut self,
|
||||||
song_path: String,
|
song_path: String,
|
||||||
error: BlissError,
|
error: BlissError,
|
||||||
) -> Result<(), BlissError> {
|
) -> BlissResult<()> {
|
||||||
Err(BlissError::ProviderError(format!(
|
Err(BlissError::ProviderError(format!(
|
||||||
"Could not store errored song: {}, with error: {}",
|
"Could not store errored song: {}, with error: {}",
|
||||||
song_path, error
|
song_path, error
|
||||||
|
@ -247,7 +248,7 @@ mod test {
|
||||||
fn test_playlist_from_song_fail() {
|
fn test_playlist_from_song_fail() {
|
||||||
let test_library = FailingLibrary {};
|
let test_library = FailingLibrary {};
|
||||||
let song = Song {
|
let song = Song {
|
||||||
path: String::from("path-to-first"),
|
path: Path::new("path-to-first").to_path_buf(),
|
||||||
analysis: Analysis::new([0.; 20]),
|
analysis: Analysis::new([0.; 20]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -294,7 +295,7 @@ mod test {
|
||||||
let mut songs = test_library
|
let mut songs = test_library
|
||||||
.internal_storage
|
.internal_storage
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| x.path.to_owned())
|
.map(|x| x.path.to_str().unwrap().to_string())
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
songs.sort();
|
songs.sort();
|
||||||
|
|
||||||
|
@ -311,25 +312,25 @@ mod test {
|
||||||
fn test_playlist_from_song() {
|
fn test_playlist_from_song() {
|
||||||
let mut test_library = TestLibrary::default();
|
let mut test_library = TestLibrary::default();
|
||||||
let first_song = Song {
|
let first_song = Song {
|
||||||
path: String::from("path-to-first"),
|
path: Path::new("path-to-first").to_path_buf(),
|
||||||
analysis: Analysis::new([0.; 20]),
|
analysis: Analysis::new([0.; 20]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let second_song = Song {
|
let second_song = Song {
|
||||||
path: String::from("path-to-second"),
|
path: Path::new("path-to-second").to_path_buf(),
|
||||||
analysis: Analysis::new([0.1; 20]),
|
analysis: Analysis::new([0.1; 20]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let third_song = Song {
|
let third_song = Song {
|
||||||
path: String::from("path-to-third"),
|
path: Path::new("path-to-third").to_path_buf(),
|
||||||
analysis: Analysis::new([10.; 20]),
|
analysis: Analysis::new([10.; 20]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let fourth_song = Song {
|
let fourth_song = Song {
|
||||||
path: String::from("path-to-fourth"),
|
path: Path::new("path-to-fourth").to_path_buf(),
|
||||||
analysis: Analysis::new([20.; 20]),
|
analysis: Analysis::new([20.; 20]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -350,19 +351,19 @@ mod test {
|
||||||
fn test_playlist_from_song_too_little_songs() {
|
fn test_playlist_from_song_too_little_songs() {
|
||||||
let mut test_library = TestLibrary::default();
|
let mut test_library = TestLibrary::default();
|
||||||
let first_song = Song {
|
let first_song = Song {
|
||||||
path: String::from("path-to-first"),
|
path: Path::new("path-to-first").to_path_buf(),
|
||||||
analysis: Analysis::new([0.; 20]),
|
analysis: Analysis::new([0.; 20]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let second_song = Song {
|
let second_song = Song {
|
||||||
path: String::from("path-to-second"),
|
path: Path::new("path-to-second").to_path_buf(),
|
||||||
analysis: Analysis::new([0.1; 20]),
|
analysis: Analysis::new([0.1; 20]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let third_song = Song {
|
let third_song = Song {
|
||||||
path: String::from("path-to-third"),
|
path: Path::new("path-to-third").to_path_buf(),
|
||||||
analysis: Analysis::new([10.; 20]),
|
analysis: Analysis::new([10.; 20]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
|
@ -64,10 +64,11 @@ impl Normalize for LoudnessDesc {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::Song;
|
use crate::Song;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_loudness() {
|
fn test_loudness() {
|
||||||
let song = Song::decode("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.sample_array.chunks_exact(LoudnessDesc::WINDOW_SIZE) {
|
||||||
loudness_desc.do_(&chunk);
|
loudness_desc.do_(&chunk);
|
||||||
|
|
138
src/song.rs
138
src/song.rs
|
@ -17,7 +17,7 @@ use crate::chroma::ChromaDesc;
|
||||||
use crate::misc::LoudnessDesc;
|
use crate::misc::LoudnessDesc;
|
||||||
use crate::temporal::BPMDesc;
|
use crate::temporal::BPMDesc;
|
||||||
use crate::timbral::{SpectralDesc, ZeroCrossingRateDesc};
|
use crate::timbral::{SpectralDesc, ZeroCrossingRateDesc};
|
||||||
use crate::{BlissError, SAMPLE_RATE};
|
use crate::{BlissError, BlissResult, SAMPLE_RATE};
|
||||||
use ::log::warn;
|
use ::log::warn;
|
||||||
use core::ops::Index;
|
use core::ops::Index;
|
||||||
use crossbeam::thread;
|
use crossbeam::thread;
|
||||||
|
@ -36,6 +36,8 @@ use std::convert::TryInto;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::sync::mpsc::Receiver;
|
use std::sync::mpsc::Receiver;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::thread as std_thread;
|
use std::thread as std_thread;
|
||||||
use strum::{EnumCount, IntoEnumIterator};
|
use strum::{EnumCount, IntoEnumIterator};
|
||||||
use strum_macros::{EnumCount, EnumIter};
|
use strum_macros::{EnumCount, EnumIter};
|
||||||
|
@ -46,17 +48,17 @@ use strum_macros::{EnumCount, EnumIter};
|
||||||
/// other metadata (artist, genre...)
|
/// other metadata (artist, genre...)
|
||||||
pub struct Song {
|
pub struct Song {
|
||||||
/// Song's provided file path
|
/// Song's provided file path
|
||||||
pub path: String,
|
pub path: PathBuf,
|
||||||
/// Song's artist, read from the metadata (`""` if empty)
|
/// Song's artist, read from the metadata
|
||||||
pub artist: String,
|
pub artist: Option<String>,
|
||||||
/// Song's title, read from the metadata (`""` if empty)
|
/// Song's title, read from the metadata
|
||||||
pub title: String,
|
pub title: Option<String>,
|
||||||
/// Song's album name, read from the metadata (`""` if empty)
|
/// Song's album name, read from the metadata
|
||||||
pub album: String,
|
pub album: Option<String>,
|
||||||
/// Song's tracked number, read from the metadata (`""` if empty)
|
/// Song's tracked number, read from the metadata
|
||||||
pub track_number: String,
|
pub track_number: Option<String>,
|
||||||
/// Song's genre, read from the metadata (`""` if empty)
|
/// Song's genre, read from the metadata (`""` if empty)
|
||||||
pub genre: String,
|
pub genre: Option<String>,
|
||||||
/// bliss analysis results
|
/// bliss analysis results
|
||||||
pub analysis: Analysis,
|
pub analysis: Analysis,
|
||||||
}
|
}
|
||||||
|
@ -66,9 +68,9 @@ pub struct Song {
|
||||||
///
|
///
|
||||||
/// * Example:
|
/// * Example:
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// use bliss_audio::{AnalysisIndex, BlissError, Song};
|
/// use bliss_audio::{AnalysisIndex, BlissResult, Song};
|
||||||
///
|
///
|
||||||
/// fn main() -> Result<(), BlissError> {
|
/// fn main() -> BlissResult<()> {
|
||||||
/// let song = Song::new("path/to/song")?;
|
/// let song = Song::new("path/to/song")?;
|
||||||
/// println!("{}", song.analysis[AnalysisIndex::Tempo]);
|
/// println!("{}", song.analysis[AnalysisIndex::Tempo]);
|
||||||
/// Ok(())
|
/// Ok(())
|
||||||
|
@ -217,8 +219,8 @@ impl Song {
|
||||||
/// The error type returned should give a hint as to whether it was a
|
/// The error type returned should give a hint as to whether it was a
|
||||||
/// decoding ([DecodingError](BlissError::DecodingError)) or an analysis
|
/// decoding ([DecodingError](BlissError::DecodingError)) or an analysis
|
||||||
/// ([AnalysisError](BlissError::AnalysisError)) error.
|
/// ([AnalysisError](BlissError::AnalysisError)) error.
|
||||||
pub fn new(path: &str) -> Result<Self, BlissError> {
|
pub fn new<P: AsRef<Path>>(path: P) -> BlissResult<Self> {
|
||||||
let raw_song = Song::decode(&path)?;
|
let raw_song = Song::decode(path.as_ref())?;
|
||||||
|
|
||||||
Ok(Song {
|
Ok(Song {
|
||||||
path: raw_song.path,
|
path: raw_song.path,
|
||||||
|
@ -242,7 +244,7 @@ impl Song {
|
||||||
* Useful in the rare cases where the full song is not
|
* Useful in the rare cases where the full song is not
|
||||||
* completely available.
|
* completely available.
|
||||||
**/
|
**/
|
||||||
fn analyse(sample_array: Vec<f32>) -> Result<Analysis, BlissError> {
|
fn analyse(sample_array: Vec<f32>) -> BlissResult<Analysis> {
|
||||||
let largest_window = vec![
|
let largest_window = vec![
|
||||||
BPMDesc::WINDOW_SIZE,
|
BPMDesc::WINDOW_SIZE,
|
||||||
ChromaDesc::WINDOW_SIZE,
|
ChromaDesc::WINDOW_SIZE,
|
||||||
|
@ -259,7 +261,7 @@ impl Song {
|
||||||
}
|
}
|
||||||
|
|
||||||
thread::scope(|s| {
|
thread::scope(|s| {
|
||||||
let child_tempo: thread::ScopedJoinHandle<'_, Result<f32, BlissError>> =
|
let child_tempo: thread::ScopedJoinHandle<'_, BlissResult<f32>> =
|
||||||
s.spawn(|_| {
|
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
|
||||||
|
@ -272,7 +274,7 @@ impl Song {
|
||||||
Ok(tempo_desc.get_value())
|
Ok(tempo_desc.get_value())
|
||||||
});
|
});
|
||||||
|
|
||||||
let child_chroma: thread::ScopedJoinHandle<'_, Result<Vec<f32>, BlissError>> =
|
let child_chroma: thread::ScopedJoinHandle<'_, BlissResult<Vec<f32>>> =
|
||||||
s.spawn(|_| {
|
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)?;
|
||||||
|
@ -282,7 +284,7 @@ impl Song {
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
let child_timbral: thread::ScopedJoinHandle<
|
let child_timbral: thread::ScopedJoinHandle<
|
||||||
'_,
|
'_,
|
||||||
Result<(Vec<f32>, Vec<f32>, Vec<f32>), BlissError>,
|
BlissResult<(Vec<f32>, Vec<f32>, Vec<f32>)>,
|
||||||
> = s.spawn(|_| {
|
> = 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
|
||||||
|
@ -297,13 +299,13 @@ impl Song {
|
||||||
Ok((centroid, rolloff, flatness))
|
Ok((centroid, rolloff, flatness))
|
||||||
});
|
});
|
||||||
|
|
||||||
let child_zcr: thread::ScopedJoinHandle<'_, Result<f32, BlissError>> = s.spawn(|_| {
|
let child_zcr: thread::ScopedJoinHandle<'_, BlissResult<f32>> = 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<'_, Result<Vec<f32>, BlissError>> = s
|
let child_loudness: thread::ScopedJoinHandle<'_, BlissResult<Vec<f32>>> = s
|
||||||
.spawn(|_| {
|
.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);
|
||||||
|
@ -339,12 +341,12 @@ impl Song {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn decode(path: &str) -> Result<InternalSong, BlissError> {
|
pub(crate) fn decode(path: &Path) -> BlissResult<InternalSong> {
|
||||||
ffmpeg::init()
|
ffmpeg::init()
|
||||||
.map_err(|e| BlissError::DecodingError(format!("ffmpeg init error: {:?}.", e)))?;
|
.map_err(|e| BlissError::DecodingError(format!("ffmpeg init error: {:?}.", e)))?;
|
||||||
log::set_level(Level::Quiet);
|
log::set_level(Level::Quiet);
|
||||||
let mut song = InternalSong {
|
let mut song = InternalSong {
|
||||||
path: path.to_string(),
|
path: path.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let mut format = ffmpeg::format::input(&path)
|
let mut format = ffmpeg::format::input(&path)
|
||||||
|
@ -378,19 +380,35 @@ impl Song {
|
||||||
};
|
};
|
||||||
let sample_array: Vec<f32> = Vec::with_capacity(expected_sample_number as usize);
|
let sample_array: Vec<f32> = Vec::with_capacity(expected_sample_number as usize);
|
||||||
if let Some(title) = format.metadata().get("title") {
|
if let Some(title) = format.metadata().get("title") {
|
||||||
song.title = title.to_string();
|
song.title = match title {
|
||||||
|
"" => None,
|
||||||
|
t => Some(t.to_string()),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
if let Some(artist) = format.metadata().get("artist") {
|
if let Some(artist) = format.metadata().get("artist") {
|
||||||
song.artist = artist.to_string();
|
song.artist = match artist {
|
||||||
|
"" => None,
|
||||||
|
a => Some(a.to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
if let Some(album) = format.metadata().get("album") {
|
if let Some(album) = format.metadata().get("album") {
|
||||||
song.album = album.to_string();
|
song.album = match album {
|
||||||
|
"" => None,
|
||||||
|
a => Some(a.to_string()),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
if let Some(genre) = format.metadata().get("genre") {
|
if let Some(genre) = format.metadata().get("genre") {
|
||||||
song.genre = genre.to_string();
|
song.genre = match genre {
|
||||||
|
"" => None,
|
||||||
|
g => Some(g.to_string()),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
if let Some(track_number) = format.metadata().get("track") {
|
if let Some(track_number) = format.metadata().get("track") {
|
||||||
song.track_number = track_number.to_string();
|
song.track_number = match track_number {
|
||||||
|
"" => None,
|
||||||
|
t => Some(t.to_string()),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
let in_channel_layout = {
|
let in_channel_layout = {
|
||||||
if codec.channel_layout() == ChannelLayout::empty() {
|
if codec.channel_layout() == ChannelLayout::empty() {
|
||||||
|
@ -494,12 +512,12 @@ impl Song {
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub(crate) struct InternalSong {
|
pub(crate) struct InternalSong {
|
||||||
pub path: String,
|
pub path: PathBuf,
|
||||||
pub artist: String,
|
pub artist: Option<String>,
|
||||||
pub title: String,
|
pub title: Option<String>,
|
||||||
pub album: String,
|
pub album: Option<String>,
|
||||||
pub track_number: String,
|
pub track_number: Option<String>,
|
||||||
pub genre: String,
|
pub genre: Option<String>,
|
||||||
pub sample_array: Vec<f32>,
|
pub sample_array: Vec<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -507,7 +525,7 @@ fn resample_frame(
|
||||||
rx: Receiver<Audio>,
|
rx: Receiver<Audio>,
|
||||||
mut resample_context: Context,
|
mut resample_context: Context,
|
||||||
mut sample_array: Vec<f32>,
|
mut sample_array: Vec<f32>,
|
||||||
) -> Result<Vec<f32>, BlissError> {
|
) -> BlissResult<Vec<f32>> {
|
||||||
let mut resampled = ffmpeg::frame::Audio::empty();
|
let mut resampled = ffmpeg::frame::Audio::empty();
|
||||||
for decoded in rx.iter() {
|
for decoded in rx.iter() {
|
||||||
resampled = ffmpeg::frame::Audio::empty();
|
resampled = ffmpeg::frame::Audio::empty();
|
||||||
|
@ -564,6 +582,7 @@ fn push_to_sample_array(frame: &ffmpeg::frame::Audio, sample_array: &mut Vec<f32
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use ripemd160::{Digest, Ripemd160};
|
use ripemd160::{Digest, Ripemd160};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_analysis_too_small() {
|
fn test_analysis_too_small() {
|
||||||
|
@ -582,7 +601,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_analyse() {
|
fn test_analyse() {
|
||||||
let song = Song::new("data/s16_mono_22_5kHz.flac").unwrap();
|
let song = Song::new(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||||
let expected_analysis = vec![
|
let expected_analysis = vec![
|
||||||
0.3846389,
|
0.3846389,
|
||||||
-0.849141,
|
-0.849141,
|
||||||
|
@ -610,7 +629,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _test_decode(path: &str, expected_hash: &[u8]) {
|
fn _test_decode(path: &Path, expected_hash: &[u8]) {
|
||||||
let song = Song::decode(path).unwrap();
|
let song = Song::decode(path).unwrap();
|
||||||
let mut hasher = Ripemd160::new();
|
let mut hasher = Ripemd160::new();
|
||||||
for sample in song.sample_array.iter() {
|
for sample in song.sample_array.iter() {
|
||||||
|
@ -622,17 +641,27 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tags() {
|
fn test_tags() {
|
||||||
let song = Song::decode("data/s16_mono_22_5kHz.flac").unwrap();
|
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||||
assert_eq!(song.artist, "David TMX");
|
assert_eq!(song.artist, Some(String::from("David TMX")));
|
||||||
assert_eq!(song.title, "Renaissance");
|
assert_eq!(song.title, Some(String::from("Renaissance")));
|
||||||
assert_eq!(song.album, "Renaissance");
|
assert_eq!(song.album, Some(String::from("Renaissance")));
|
||||||
assert_eq!(song.track_number, "02");
|
assert_eq!(song.track_number, Some(String::from("02")));
|
||||||
assert_eq!(song.genre, "Pop");
|
assert_eq!(song.genre, Some(String::from("Pop")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_tags() {
|
||||||
|
let song = Song::decode(Path::new("data/no_tags.flac")).unwrap();
|
||||||
|
assert_eq!(song.artist, None);
|
||||||
|
assert_eq!(song.title, None);
|
||||||
|
assert_eq!(song.album, None);
|
||||||
|
assert_eq!(song.track_number, None);
|
||||||
|
assert_eq!(song.genre, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_resample_multi() {
|
fn test_resample_multi() {
|
||||||
let path = String::from("data/s32_stereo_44_1_kHz.flac");
|
let path = Path::new("data/s32_stereo_44_1_kHz.flac");
|
||||||
let expected_hash = [
|
let expected_hash = [
|
||||||
0xc5, 0xf8, 0x23, 0xce, 0x63, 0x2c, 0xf4, 0xa0, 0x72, 0x66, 0xbb, 0x49, 0xad, 0x84,
|
0xc5, 0xf8, 0x23, 0xce, 0x63, 0x2c, 0xf4, 0xa0, 0x72, 0x66, 0xbb, 0x49, 0xad, 0x84,
|
||||||
0xb6, 0xea, 0x48, 0x48, 0x9c, 0x50,
|
0xb6, 0xea, 0x48, 0x48, 0x9c, 0x50,
|
||||||
|
@ -642,7 +671,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_resample_stereo() {
|
fn test_resample_stereo() {
|
||||||
let path = String::from("data/s16_stereo_22_5kHz.flac");
|
let path = Path::new("data/s16_stereo_22_5kHz.flac");
|
||||||
let expected_hash = [
|
let expected_hash = [
|
||||||
0x24, 0xed, 0x45, 0x58, 0x06, 0xbf, 0xfb, 0x05, 0x57, 0x5f, 0xdc, 0x4d, 0xb4, 0x9b,
|
0x24, 0xed, 0x45, 0x58, 0x06, 0xbf, 0xfb, 0x05, 0x57, 0x5f, 0xdc, 0x4d, 0xb4, 0x9b,
|
||||||
0xa5, 0x2b, 0x05, 0x56, 0x10, 0x4f,
|
0xa5, 0x2b, 0x05, 0x56, 0x10, 0x4f,
|
||||||
|
@ -652,7 +681,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_decode_mono() {
|
fn test_decode_mono() {
|
||||||
let path = String::from("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 ripemd160 -
|
||||||
|
@ -665,7 +694,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_decode_mp3() {
|
fn test_decode_mp3() {
|
||||||
let path = String::from("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 ripemd160 -
|
||||||
|
@ -678,13 +707,13 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_dont_panic_no_channel_layout() {
|
fn test_dont_panic_no_channel_layout() {
|
||||||
let path = String::from("data/no_channel.wav");
|
let path = Path::new("data/no_channel.wav");
|
||||||
Song::decode(&path).unwrap();
|
Song::decode(&path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_decode_right_capacity_vec() {
|
fn test_decode_right_capacity_vec() {
|
||||||
let path = String::from("data/s16_mono_22_5kHz.flac");
|
let path = Path::new("data/s16_mono_22_5kHz.flac");
|
||||||
let song = Song::decode(&path).unwrap();
|
let song = Song::decode(&path).unwrap();
|
||||||
let sample_array = song.sample_array;
|
let sample_array = song.sample_array;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -692,7 +721,7 @@ mod tests {
|
||||||
sample_array.capacity()
|
sample_array.capacity()
|
||||||
);
|
);
|
||||||
|
|
||||||
let path = String::from("data/s32_stereo_44_1_kHz.flac");
|
let path = Path::new("data/s32_stereo_44_1_kHz.flac");
|
||||||
let song = Song::decode(&path).unwrap();
|
let song = Song::decode(&path).unwrap();
|
||||||
let sample_array = song.sample_array;
|
let sample_array = song.sample_array;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -700,7 +729,7 @@ mod tests {
|
||||||
sample_array.capacity()
|
sample_array.capacity()
|
||||||
);
|
);
|
||||||
|
|
||||||
let path = String::from("data/capacity_fix.ogg");
|
let path = Path::new("data/capacity_fix.ogg");
|
||||||
let song = Song::decode(&path).unwrap();
|
let song = Song::decode(&path).unwrap();
|
||||||
let sample_array = song.sample_array;
|
let sample_array = song.sample_array;
|
||||||
assert!(sample_array.len() as f32 / sample_array.capacity() as f32 > 0.90);
|
assert!(sample_array.len() as f32 / sample_array.capacity() as f32 > 0.90);
|
||||||
|
@ -738,13 +767,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_decode_errors() {
|
fn test_decode_errors() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Song::decode("nonexistent").unwrap_err(),
|
Song::decode(Path::new("nonexistent")).unwrap_err(),
|
||||||
BlissError::DecodingError(String::from(
|
BlissError::DecodingError(String::from(
|
||||||
"while opening format: ffmpeg::Error(2: No such file or directory)."
|
"while opening format: ffmpeg::Error(2: No such file or directory)."
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Song::decode("data/picture.png").unwrap_err(),
|
Song::decode(Path::new("data/picture.png")).unwrap_err(),
|
||||||
BlissError::DecodingError(String::from("No audio stream found.")),
|
BlissError::DecodingError(String::from("No audio stream found.")),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -782,10 +811,11 @@ mod bench {
|
||||||
extern crate test;
|
extern crate test;
|
||||||
use crate::Song;
|
use crate::Song;
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_resample_multi(b: &mut Bencher) {
|
fn bench_resample_multi(b: &mut Bencher) {
|
||||||
let path = String::from("./data/s32_stereo_44_1_kHz.flac");
|
let path = Path::new("./data/s32_stereo_44_1_kHz.flac");
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
Song::decode(&path).unwrap();
|
Song::decode(&path).unwrap();
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
//! of a given Song.
|
//! of a given Song.
|
||||||
|
|
||||||
use crate::utils::Normalize;
|
use crate::utils::Normalize;
|
||||||
use crate::BlissError;
|
use crate::{BlissError, BlissResult};
|
||||||
use bliss_audio_aubio_rs::{OnsetMode, Tempo};
|
use bliss_audio_aubio_rs::{OnsetMode, Tempo};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use ndarray::arr1;
|
use ndarray::arr1;
|
||||||
|
@ -39,7 +39,7 @@ impl BPMDesc {
|
||||||
pub const WINDOW_SIZE: usize = 512;
|
pub const WINDOW_SIZE: usize = 512;
|
||||||
pub const HOP_SIZE: usize = BPMDesc::WINDOW_SIZE / 2;
|
pub const HOP_SIZE: usize = BPMDesc::WINDOW_SIZE / 2;
|
||||||
|
|
||||||
pub fn new(sample_rate: u32) -> Result<Self, BlissError> {
|
pub fn new(sample_rate: u32) -> BlissResult<Self> {
|
||||||
Ok(BPMDesc {
|
Ok(BPMDesc {
|
||||||
aubio_obj: Tempo::new(
|
aubio_obj: Tempo::new(
|
||||||
OnsetMode::SpecFlux,
|
OnsetMode::SpecFlux,
|
||||||
|
@ -57,7 +57,7 @@ impl BPMDesc {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn do_(&mut self, chunk: &[f32]) -> Result<(), BlissError> {
|
pub fn do_(&mut self, chunk: &[f32]) -> BlissResult<()> {
|
||||||
let result = self.aubio_obj.do_result(chunk).map_err(|e| {
|
let result = self.aubio_obj.do_result(chunk).map_err(|e| {
|
||||||
BlissError::AnalysisError(format!(
|
BlissError::AnalysisError(format!(
|
||||||
"aubio error while computing tempo {}",
|
"aubio error while computing tempo {}",
|
||||||
|
@ -101,10 +101,11 @@ impl Normalize for BPMDesc {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{Song, SAMPLE_RATE};
|
use crate::{Song, SAMPLE_RATE};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tempo_real() {
|
fn test_tempo_real() {
|
||||||
let song = Song::decode("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.sample_array.chunks_exact(BPMDesc::HOP_SIZE) {
|
||||||
tempo_desc.do_(&chunk).unwrap();
|
tempo_desc.do_(&chunk).unwrap();
|
||||||
|
|
|
@ -9,7 +9,7 @@ use bliss_audio_aubio_rs::{bin_to_freq, PVoc, SpecDesc, SpecShape};
|
||||||
use ndarray::{arr1, Axis};
|
use ndarray::{arr1, Axis};
|
||||||
|
|
||||||
use super::utils::{geometric_mean, mean, number_crossings, Normalize};
|
use super::utils::{geometric_mean, mean, number_crossings, Normalize};
|
||||||
use crate::{BlissError, SAMPLE_RATE};
|
use crate::{BlissError, BlissResult, SAMPLE_RATE};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General object holding all the spectral descriptor.
|
* General object holding all the spectral descriptor.
|
||||||
|
@ -120,7 +120,7 @@ impl SpectralDesc {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(sample_rate: u32) -> Result<Self, BlissError> {
|
pub fn new(sample_rate: u32) -> BlissResult<Self> {
|
||||||
Ok(SpectralDesc {
|
Ok(SpectralDesc {
|
||||||
centroid_aubio_desc: SpecDesc::new(SpecShape::Centroid, SpectralDesc::WINDOW_SIZE)
|
centroid_aubio_desc: SpecDesc::new(SpecShape::Centroid, SpectralDesc::WINDOW_SIZE)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
|
@ -158,7 +158,7 @@ impl SpectralDesc {
|
||||||
* `get_centroid`, `get_flatness` and `get_rolloff` to get the respective
|
* `get_centroid`, `get_flatness` and `get_rolloff` to get the respective
|
||||||
* descriptors' values.
|
* descriptors' values.
|
||||||
*/
|
*/
|
||||||
pub fn do_(&mut self, chunk: &[f32]) -> Result<(), BlissError> {
|
pub fn do_(&mut self, chunk: &[f32]) -> BlissResult<()> {
|
||||||
let mut fftgrain: Vec<f32> = vec![0.0; SpectralDesc::WINDOW_SIZE];
|
let mut fftgrain: Vec<f32> = vec![0.0; SpectralDesc::WINDOW_SIZE];
|
||||||
self.phase_vocoder
|
self.phase_vocoder
|
||||||
.do_(chunk, fftgrain.as_mut_slice())
|
.do_(chunk, fftgrain.as_mut_slice())
|
||||||
|
@ -266,6 +266,7 @@ impl Normalize for ZeroCrossingRateDesc {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::Song;
|
use crate::Song;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_zcr_boundaries() {
|
fn test_zcr_boundaries() {
|
||||||
|
@ -287,7 +288,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_zcr() {
|
fn test_zcr() {
|
||||||
let song = Song::decode("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.sample_array.chunks_exact(SpectralDesc::HOP_SIZE) {
|
||||||
zcr_desc.do_(&chunk);
|
zcr_desc.do_(&chunk);
|
||||||
|
@ -309,7 +310,7 @@ mod tests {
|
||||||
assert!(0.0000001 > (expected - actual).abs());
|
assert!(0.0000001 > (expected - actual).abs());
|
||||||
}
|
}
|
||||||
|
|
||||||
let song = Song::decode("data/white_noise.flac").unwrap();
|
let song = Song::decode(Path::new("data/white_noise.flac")).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.sample_array.chunks_exact(SpectralDesc::HOP_SIZE) {
|
||||||
spectral_desc.do_(&chunk).unwrap();
|
spectral_desc.do_(&chunk).unwrap();
|
||||||
|
@ -326,7 +327,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_spectral_flatness() {
|
fn test_spectral_flatness() {
|
||||||
let song = Song::decode("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.sample_array.chunks_exact(SpectralDesc::HOP_SIZE) {
|
||||||
spectral_desc.do_(&chunk).unwrap();
|
spectral_desc.do_(&chunk).unwrap();
|
||||||
|
@ -356,7 +357,7 @@ mod tests {
|
||||||
assert!(0.0000001 > (expected - actual).abs());
|
assert!(0.0000001 > (expected - actual).abs());
|
||||||
}
|
}
|
||||||
|
|
||||||
let song = Song::decode("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.sample_array.chunks_exact(SpectralDesc::HOP_SIZE) {
|
||||||
spectral_desc.do_(&chunk).unwrap();
|
spectral_desc.do_(&chunk).unwrap();
|
||||||
|
@ -372,7 +373,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_spectral_roll_off() {
|
fn test_spectral_roll_off() {
|
||||||
let song = Song::decode("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.sample_array.chunks_exact(SpectralDesc::HOP_SIZE) {
|
||||||
spectral_desc.do_(&chunk).unwrap();
|
spectral_desc.do_(&chunk).unwrap();
|
||||||
|
@ -390,7 +391,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_spectral_centroid() {
|
fn test_spectral_centroid() {
|
||||||
let song = Song::decode("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.sample_array.chunks_exact(SpectralDesc::HOP_SIZE) {
|
||||||
spectral_desc.do_(&chunk).unwrap();
|
spectral_desc.do_(&chunk).unwrap();
|
||||||
|
@ -419,7 +420,7 @@ mod tests {
|
||||||
{
|
{
|
||||||
assert!(0.0000001 > (expected - actual).abs());
|
assert!(0.0000001 > (expected - actual).abs());
|
||||||
}
|
}
|
||||||
let song = Song::decode("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.sample_array.chunks_exact(SpectralDesc::HOP_SIZE) {
|
||||||
spectral_desc.do_(&chunk).unwrap();
|
spectral_desc.do_(&chunk).unwrap();
|
||||||
|
|
|
@ -172,6 +172,7 @@ mod tests {
|
||||||
use ndarray::{arr1, Array};
|
use ndarray::{arr1, Array};
|
||||||
use ndarray_npy::ReadNpyExt;
|
use ndarray_npy::ReadNpyExt;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_convolve() {
|
fn test_convolve() {
|
||||||
|
@ -497,7 +498,7 @@ mod tests {
|
||||||
let file = File::open("data/librosa-stft.npy").unwrap();
|
let file = File::open("data/librosa-stft.npy").unwrap();
|
||||||
let expected_stft = Array2::<f32>::read_npy(file).unwrap().mapv(|x| x as f64);
|
let expected_stft = Array2::<f32>::read_npy(file).unwrap().mapv(|x| x as f64);
|
||||||
|
|
||||||
let song = Song::decode("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.sample_array, 2048, 512);
|
||||||
|
|
||||||
|
@ -525,6 +526,7 @@ mod bench {
|
||||||
use crate::Song;
|
use crate::Song;
|
||||||
use ndarray::Array;
|
use ndarray::Array;
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_convolve(b: &mut Bencher) {
|
fn bench_convolve(b: &mut Bencher) {
|
||||||
|
@ -538,7 +540,7 @@ mod bench {
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_compute_stft(b: &mut Bencher) {
|
fn bench_compute_stft(b: &mut Bencher) {
|
||||||
let signal = Song::decode("data/piano.flac").unwrap().sample_array;
|
let signal = Song::decode(Path::new("data/piano.flac")).unwrap().sample_array;
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
stft(&signal, 2048, 512);
|
stft(&signal, 2048, 512);
|
||||||
|
|
Loading…
Reference in a new issue