Some review comments

This commit is contained in:
Polochon-street 2021-06-13 14:07:17 +02:00
parent 78651c17c7
commit 33520acbc3
13 changed files with 179 additions and 130 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"

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