Merge pull request #13 from Polochon-street/review-comments

Some review comments
This commit is contained in:
Polochon-street 2021-06-15 23:41:25 +02:00
commit 138ff39dd1
14 changed files with 181 additions and 131 deletions

View file

@ -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
View file

@ -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",

View file

@ -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"

View file

@ -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

Binary file not shown.

View file

@ -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)

View file

@ -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);

View file

@ -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));

View file

@ -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()
}; };

View file

@ -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);

View file

@ -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();
}); });

View file

@ -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();

View file

@ -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();

View file

@ -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);