Merge pull request #13 from Polochon-street/review-comments
Some review comments
This commit is contained in:
commit
138ff39dd1
14 changed files with 181 additions and 131 deletions
|
@ -1,5 +1,10 @@
|
|||
# Changelog
|
||||
|
||||
## 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
|
||||
* Fixed an allocation bug in Song::decode that potentially caused segfaults.
|
||||
|
||||
|
|
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -75,7 +75,7 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
|||
|
||||
[[package]]
|
||||
name = "bliss-audio"
|
||||
version = "0.2.6"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"bliss-audio-aubio-rs",
|
||||
"crossbeam",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "bliss-audio"
|
||||
version = "0.2.6"
|
||||
version = "0.3.0"
|
||||
authors = ["Polochon-street <polochonstreet@gmx.fr>"]
|
||||
edition = "2018"
|
||||
license = "GPL-3.0-only"
|
||||
|
@ -19,9 +19,12 @@ no-default-features = true
|
|||
# https://github.com/zmwangx/rust-ffmpeg/pull/60
|
||||
# or https://github.com/zmwangx/rust-ffmpeg/pull/62 is in
|
||||
default = ["bliss-audio-aubio-rs/static", "build-ffmpeg"]
|
||||
# Build ffmpeg instead of using the host's.
|
||||
build-ffmpeg = ["ffmpeg-next/build"]
|
||||
bench = []
|
||||
# Use if you want to build python bindings with maturin.
|
||||
python-bindings = ["bliss-audio-aubio-rs/fftw3"]
|
||||
# Enable the benchmarks with `cargo +nightly bench --features=bench`
|
||||
bench = []
|
||||
|
||||
[dependencies]
|
||||
ripemd160 = "0.9.0"
|
||||
|
|
|
@ -8,7 +8,8 @@ library used to make playlists by analyzing songs, and computing distance betwee
|
|||
|
||||
Like bliss, it eases the creation of « intelligent » playlists and/or continuous
|
||||
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),
|
||||
[blissify](https://crates.io/crates/blissify) implements bliss for
|
||||
|
|
BIN
data/no_tags.flac
Normal file
BIN
data/no_tags.flac
Normal file
Binary file not shown.
|
@ -17,7 +17,7 @@ fn main() -> Result<(), String> {
|
|||
let song2 = Song::new(&second_path).map_err(|x| x.to_string())?;
|
||||
|
||||
println!(
|
||||
"d({}, {}) = {}",
|
||||
"d({:?}, {:?}) = {}",
|
||||
song1.path,
|
||||
song2.path,
|
||||
song1.distance(&song2)
|
||||
|
|
|
@ -7,7 +7,7 @@ extern crate noisy_float;
|
|||
|
||||
use crate::utils::stft;
|
||||
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_stats::interpolate::Midpoint;
|
||||
use ndarray_stats::QuantileExt;
|
||||
|
@ -51,7 +51,7 @@ impl ChromaDesc {
|
|||
* Passing a full song here once instead of streaming smaller parts of the
|
||||
* 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 tuning = estimate_tuning(
|
||||
self.sample_rate as u32,
|
||||
|
@ -155,7 +155,7 @@ fn chroma_filter(
|
|||
n_fft: usize,
|
||||
n_chroma: u32,
|
||||
tuning: f64,
|
||||
) -> Result<Array2<f64>, BlissError> {
|
||||
) -> BlissResult<Array2<f64>> {
|
||||
let ctroct = 5.0;
|
||||
let octwidth = 2.;
|
||||
let n_chroma_float = f64::from(n_chroma);
|
||||
|
@ -226,7 +226,7 @@ fn pip_track(
|
|||
sample_rate: u32,
|
||||
spectrum: &Array2<f64>,
|
||||
n_fft: usize,
|
||||
) -> Result<(Vec<f64>, Vec<f64>), BlissError> {
|
||||
) -> BlissResult<(Vec<f64>, Vec<f64>)> {
|
||||
let sample_rate_float = f64::from(sample_rate);
|
||||
let fmin = 150.0_f64;
|
||||
let fmax = 4000.0_f64.min(sample_rate_float / 2.0);
|
||||
|
@ -291,7 +291,7 @@ fn pitch_tuning(
|
|||
frequencies: &mut Array1<f64>,
|
||||
resolution: f64,
|
||||
bins_per_octave: u32,
|
||||
) -> Result<f64, BlissError> {
|
||||
) -> BlissResult<f64> {
|
||||
if frequencies.is_empty() {
|
||||
return Ok(0.0);
|
||||
}
|
||||
|
@ -320,7 +320,7 @@ fn estimate_tuning(
|
|||
n_fft: usize,
|
||||
resolution: f64,
|
||||
bins_per_octave: u32,
|
||||
) -> Result<f64, BlissError> {
|
||||
) -> BlissResult<f64> {
|
||||
let (pitch, mag) = pip_track(sample_rate, &spectrum, n_fft)?;
|
||||
|
||||
let (filtered_pitch, filtered_mag): (Vec<N64>, Vec<N64>) = pitch
|
||||
|
@ -372,6 +372,7 @@ mod test {
|
|||
use ndarray::{arr1, arr2, Array2};
|
||||
use ndarray_npy::ReadNpyExt;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn test_chroma_interval_features() {
|
||||
|
@ -437,7 +438,7 @@ mod test {
|
|||
|
||||
#[test]
|
||||
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);
|
||||
chroma_desc.do_(&song.sample_array).unwrap();
|
||||
let expected_values = vec![
|
||||
|
@ -459,7 +460,7 @@ mod test {
|
|||
|
||||
#[test]
|
||||
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()
|
||||
.sample_array;
|
||||
let mut stft = stft(&signal, 8192, 2205);
|
||||
|
@ -487,7 +488,7 @@ mod test {
|
|||
|
||||
#[test]
|
||||
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()
|
||||
.sample_array;
|
||||
let stft = stft(&signal, 8192, 2205);
|
||||
|
@ -556,6 +557,7 @@ mod bench {
|
|||
use ndarray_npy::ReadNpyExt;
|
||||
use std::fs::File;
|
||||
use test::Bencher;
|
||||
use std::path::Path;
|
||||
|
||||
#[bench]
|
||||
fn bench_estimate_tuning(b: &mut Bencher) {
|
||||
|
@ -603,7 +605,7 @@ mod bench {
|
|||
|
||||
#[bench]
|
||||
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 signal = song.sample_array;
|
||||
b.iter(|| {
|
||||
|
@ -614,7 +616,7 @@ mod bench {
|
|||
|
||||
#[bench]
|
||||
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 signal = song.sample_array;
|
||||
b.iter(|| {
|
||||
|
@ -625,7 +627,7 @@ mod bench {
|
|||
|
||||
#[bench]
|
||||
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()
|
||||
.sample_array;
|
||||
let mut stft = stft(&signal, 8192, 2205);
|
||||
|
|
21
src/lib.rs
21
src/lib.rs
|
@ -24,9 +24,9 @@
|
|||
//!
|
||||
//! ## Analyze & compute the distance between two songs
|
||||
//! ```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 song2 = Song::new("/path/to/song2")?;
|
||||
//!
|
||||
|
@ -37,15 +37,15 @@
|
|||
//!
|
||||
//! ### Make a playlist from a song
|
||||
//! ```no_run
|
||||
//! use bliss_audio::{BlissError, Song};
|
||||
//! use bliss_audio::{BlissResult, Song};
|
||||
//! 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 mut songs: Vec<Song> = paths
|
||||
//! .iter()
|
||||
//! .map(|path| Song::new(path))
|
||||
//! .collect::<Result<Vec<Song>, BlissError>>()?;
|
||||
//! .collect::<BlissResult<Vec<Song>>>()?;
|
||||
//!
|
||||
//! // Assuming there is a first song
|
||||
//! let first_song = songs.first().unwrap().to_owned();
|
||||
|
@ -55,8 +55,8 @@
|
|||
//! "Playlist is: {:?}",
|
||||
//! songs
|
||||
//! .iter()
|
||||
//! .map(|song| &song.path)
|
||||
//! .collect::<Vec<&String>>()
|
||||
//! .map(|song| song.path.to_string_lossy().to_string())
|
||||
//! .collect::<Vec<String>>()
|
||||
//! );
|
||||
//! Ok(())
|
||||
//! }
|
||||
|
@ -100,13 +100,16 @@ pub enum BlissError {
|
|||
ProviderError(String),
|
||||
}
|
||||
|
||||
/// bliss error type
|
||||
pub type BlissResult<T> = Result<T, BlissError>;
|
||||
|
||||
/// Simple function to bulk analyze a set of songs represented by their
|
||||
/// absolute paths.
|
||||
///
|
||||
/// When making an extension for an audio player, prefer
|
||||
/// implementing the `Library` trait.
|
||||
#[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 num_cpus = num_cpus::get();
|
||||
|
||||
|
@ -175,7 +178,7 @@ mod tests {
|
|||
|
||||
let mut analysed_songs: Vec<String> = results
|
||||
.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();
|
||||
analysed_songs.sort_by(|a, b| a.cmp(b));
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! Module containing the Library trait, useful to get started to implement
|
||||
//! a plug-in for an audio player.
|
||||
use crate::{BlissError, Song};
|
||||
use crate::{BlissError, BlissResult, Song};
|
||||
use log::{debug, error, info};
|
||||
use noisy_float::prelude::*;
|
||||
use std::sync::mpsc;
|
||||
|
@ -11,18 +11,18 @@ use std::thread;
|
|||
pub trait Library {
|
||||
/// Return the absolute path of all the songs in an
|
||||
/// 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.
|
||||
/// 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
|
||||
/// 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.
|
||||
///
|
||||
/// This should work only after having run `analyze_library` at least
|
||||
/// 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``.
|
||||
///
|
||||
|
@ -41,7 +41,7 @@ pub trait Library {
|
|||
&self,
|
||||
first_song: Song,
|
||||
playlist_length: usize,
|
||||
) -> Result<Vec<Song>, BlissError> {
|
||||
) -> BlissResult<Vec<Song>> {
|
||||
let analysis_current_song = first_song.analysis;
|
||||
let mut songs = self.get_stored_songs()?;
|
||||
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
|
||||
/// 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() {
|
||||
return Ok(());
|
||||
}
|
||||
|
@ -67,8 +67,8 @@ pub trait Library {
|
|||
|
||||
#[allow(clippy::type_complexity)]
|
||||
let (tx, rx): (
|
||||
Sender<(String, Result<Song, BlissError>)>,
|
||||
Receiver<(String, Result<Song, BlissError>)>,
|
||||
Sender<(String, BlissResult<Song>)>,
|
||||
Receiver<(String, BlissResult<Song>)>,
|
||||
) = mpsc::channel();
|
||||
let mut handles = Vec::new();
|
||||
let mut chunk_length = paths.len() / num_cpus;
|
||||
|
@ -96,8 +96,8 @@ pub trait Library {
|
|||
match song {
|
||||
Ok(song) => {
|
||||
self.store_song(&song)
|
||||
.unwrap_or_else(|_| error!("Error while storing song '{}'", song.path));
|
||||
info!("Analyzed and stored song '{}' successfully.", song.path)
|
||||
.unwrap_or_else(|_| error!("Error while storing song '{}'", song.path.display()));
|
||||
info!("Analyzed and stored song '{}' successfully.", song.path.display())
|
||||
}
|
||||
Err(e) => {
|
||||
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
|
||||
/// `store_error_song` implementations.
|
||||
fn analyze_library(&mut self) -> Result<(), BlissError> {
|
||||
fn analyze_library(&mut self) -> BlissResult<()> {
|
||||
let paths = self
|
||||
.get_songs_paths()
|
||||
.map_err(|e| BlissError::ProviderError(e.to_string()))?;
|
||||
|
@ -135,6 +135,7 @@ pub trait Library {
|
|||
mod test {
|
||||
use super::*;
|
||||
use crate::song::Analysis;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Default)]
|
||||
struct TestLibrary {
|
||||
|
@ -143,7 +144,7 @@ mod test {
|
|||
}
|
||||
|
||||
impl Library for TestLibrary {
|
||||
fn get_songs_paths(&self) -> Result<Vec<String>, BlissError> {
|
||||
fn get_songs_paths(&self) -> BlissResult<Vec<String>> {
|
||||
Ok(vec![
|
||||
String::from("./data/white_noise.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());
|
||||
Ok(())
|
||||
}
|
||||
|
@ -161,12 +162,12 @@ mod test {
|
|||
&mut self,
|
||||
song_path: String,
|
||||
error: BlissError,
|
||||
) -> Result<(), BlissError> {
|
||||
) -> BlissResult<()> {
|
||||
self.failed_files.push((song_path, error.to_string()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_stored_songs(&self) -> Result<Vec<Song>, BlissError> {
|
||||
fn get_stored_songs(&self) -> BlissResult<Vec<Song>> {
|
||||
Ok(self.internal_storage.to_owned())
|
||||
}
|
||||
}
|
||||
|
@ -175,23 +176,23 @@ mod test {
|
|||
struct 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(
|
||||
"Could not get songs path",
|
||||
)))
|
||||
}
|
||||
|
||||
fn store_song(&mut self, _: &Song) -> Result<(), BlissError> {
|
||||
fn store_song(&mut self, _: &Song) -> BlissResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_stored_songs(&self) -> Result<Vec<Song>, BlissError> {
|
||||
fn get_stored_songs(&self) -> BlissResult<Vec<Song>> {
|
||||
Err(BlissError::ProviderError(String::from(
|
||||
"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(())
|
||||
}
|
||||
}
|
||||
|
@ -200,7 +201,7 @@ mod test {
|
|||
struct FailingStorage;
|
||||
|
||||
impl Library for FailingStorage {
|
||||
fn get_songs_paths(&self) -> Result<Vec<String>, BlissError> {
|
||||
fn get_songs_paths(&self) -> BlissResult<Vec<String>> {
|
||||
Ok(vec![
|
||||
String::from("./data/white_noise.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!(
|
||||
"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![])
|
||||
}
|
||||
|
||||
|
@ -224,7 +225,7 @@ mod test {
|
|||
&mut self,
|
||||
song_path: String,
|
||||
error: BlissError,
|
||||
) -> Result<(), BlissError> {
|
||||
) -> BlissResult<()> {
|
||||
Err(BlissError::ProviderError(format!(
|
||||
"Could not store errored song: {}, with error: {}",
|
||||
song_path, error
|
||||
|
@ -247,7 +248,7 @@ mod test {
|
|||
fn test_playlist_from_song_fail() {
|
||||
let test_library = FailingLibrary {};
|
||||
let song = Song {
|
||||
path: String::from("path-to-first"),
|
||||
path: Path::new("path-to-first").to_path_buf(),
|
||||
analysis: Analysis::new([0.; 20]),
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -294,7 +295,7 @@ mod test {
|
|||
let mut songs = test_library
|
||||
.internal_storage
|
||||
.iter()
|
||||
.map(|x| x.path.to_owned())
|
||||
.map(|x| x.path.to_str().unwrap().to_string())
|
||||
.collect::<Vec<String>>();
|
||||
songs.sort();
|
||||
|
||||
|
@ -311,25 +312,25 @@ mod test {
|
|||
fn test_playlist_from_song() {
|
||||
let mut test_library = TestLibrary::default();
|
||||
let first_song = Song {
|
||||
path: String::from("path-to-first"),
|
||||
path: Path::new("path-to-first").to_path_buf(),
|
||||
analysis: Analysis::new([0.; 20]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
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]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let third_song = Song {
|
||||
path: String::from("path-to-third"),
|
||||
path: Path::new("path-to-third").to_path_buf(),
|
||||
analysis: Analysis::new([10.; 20]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let fourth_song = Song {
|
||||
path: String::from("path-to-fourth"),
|
||||
path: Path::new("path-to-fourth").to_path_buf(),
|
||||
analysis: Analysis::new([20.; 20]),
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -350,19 +351,19 @@ mod test {
|
|||
fn test_playlist_from_song_too_little_songs() {
|
||||
let mut test_library = TestLibrary::default();
|
||||
let first_song = Song {
|
||||
path: String::from("path-to-first"),
|
||||
path: Path::new("path-to-first").to_path_buf(),
|
||||
analysis: Analysis::new([0.; 20]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
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]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let third_song = Song {
|
||||
path: String::from("path-to-third"),
|
||||
path: Path::new("path-to-third").to_path_buf(),
|
||||
analysis: Analysis::new([10.; 20]),
|
||||
..Default::default()
|
||||
};
|
||||
|
|
|
@ -64,10 +64,11 @@ impl Normalize for LoudnessDesc {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::Song;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
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();
|
||||
for chunk in song.sample_array.chunks_exact(LoudnessDesc::WINDOW_SIZE) {
|
||||
loudness_desc.do_(&chunk);
|
||||
|
|
138
src/song.rs
138
src/song.rs
|
@ -17,7 +17,7 @@ use crate::chroma::ChromaDesc;
|
|||
use crate::misc::LoudnessDesc;
|
||||
use crate::temporal::BPMDesc;
|
||||
use crate::timbral::{SpectralDesc, ZeroCrossingRateDesc};
|
||||
use crate::{BlissError, SAMPLE_RATE};
|
||||
use crate::{BlissError, BlissResult, SAMPLE_RATE};
|
||||
use ::log::warn;
|
||||
use core::ops::Index;
|
||||
use crossbeam::thread;
|
||||
|
@ -36,6 +36,8 @@ use std::convert::TryInto;
|
|||
use std::fmt;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::thread as std_thread;
|
||||
use strum::{EnumCount, IntoEnumIterator};
|
||||
use strum_macros::{EnumCount, EnumIter};
|
||||
|
@ -46,17 +48,17 @@ use strum_macros::{EnumCount, EnumIter};
|
|||
/// other metadata (artist, genre...)
|
||||
pub struct Song {
|
||||
/// Song's provided file path
|
||||
pub path: String,
|
||||
/// Song's artist, read from the metadata (`""` if empty)
|
||||
pub artist: String,
|
||||
/// Song's title, read from the metadata (`""` if empty)
|
||||
pub title: String,
|
||||
/// Song's album name, read from the metadata (`""` if empty)
|
||||
pub album: String,
|
||||
/// Song's tracked number, read from the metadata (`""` if empty)
|
||||
pub track_number: String,
|
||||
pub path: PathBuf,
|
||||
/// Song's artist, read from the metadata
|
||||
pub artist: Option<String>,
|
||||
/// Song's title, read from the metadata
|
||||
pub title: Option<String>,
|
||||
/// Song's album name, read from the metadata
|
||||
pub album: Option<String>,
|
||||
/// Song's tracked number, read from the metadata
|
||||
pub track_number: Option<String>,
|
||||
/// Song's genre, read from the metadata (`""` if empty)
|
||||
pub genre: String,
|
||||
pub genre: Option<String>,
|
||||
/// bliss analysis results
|
||||
pub analysis: Analysis,
|
||||
}
|
||||
|
@ -66,9 +68,9 @@ pub struct Song {
|
|||
///
|
||||
/// * Example:
|
||||
/// ```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")?;
|
||||
/// println!("{}", song.analysis[AnalysisIndex::Tempo]);
|
||||
/// Ok(())
|
||||
|
@ -217,8 +219,8 @@ impl Song {
|
|||
/// The error type returned should give a hint as to whether it was a
|
||||
/// decoding ([DecodingError](BlissError::DecodingError)) or an analysis
|
||||
/// ([AnalysisError](BlissError::AnalysisError)) error.
|
||||
pub fn new(path: &str) -> Result<Self, BlissError> {
|
||||
let raw_song = Song::decode(&path)?;
|
||||
pub fn new<P: AsRef<Path>>(path: P) -> BlissResult<Self> {
|
||||
let raw_song = Song::decode(path.as_ref())?;
|
||||
|
||||
Ok(Song {
|
||||
path: raw_song.path,
|
||||
|
@ -242,7 +244,7 @@ impl Song {
|
|||
* Useful in the rare cases where the full song is not
|
||||
* completely available.
|
||||
**/
|
||||
fn analyse(sample_array: Vec<f32>) -> Result<Analysis, BlissError> {
|
||||
fn analyse(sample_array: Vec<f32>) -> BlissResult<Analysis> {
|
||||
let largest_window = vec![
|
||||
BPMDesc::WINDOW_SIZE,
|
||||
ChromaDesc::WINDOW_SIZE,
|
||||
|
@ -259,7 +261,7 @@ impl Song {
|
|||
}
|
||||
|
||||
thread::scope(|s| {
|
||||
let child_tempo: thread::ScopedJoinHandle<'_, Result<f32, BlissError>> =
|
||||
let child_tempo: thread::ScopedJoinHandle<'_, BlissResult<f32>> =
|
||||
s.spawn(|_| {
|
||||
let mut tempo_desc = BPMDesc::new(SAMPLE_RATE)?;
|
||||
let windows = sample_array
|
||||
|
@ -272,7 +274,7 @@ impl Song {
|
|||
Ok(tempo_desc.get_value())
|
||||
});
|
||||
|
||||
let child_chroma: thread::ScopedJoinHandle<'_, Result<Vec<f32>, BlissError>> =
|
||||
let child_chroma: thread::ScopedJoinHandle<'_, BlissResult<Vec<f32>>> =
|
||||
s.spawn(|_| {
|
||||
let mut chroma_desc = ChromaDesc::new(SAMPLE_RATE, 12);
|
||||
chroma_desc.do_(&sample_array)?;
|
||||
|
@ -282,7 +284,7 @@ impl Song {
|
|||
#[allow(clippy::type_complexity)]
|
||||
let child_timbral: thread::ScopedJoinHandle<
|
||||
'_,
|
||||
Result<(Vec<f32>, Vec<f32>, Vec<f32>), BlissError>,
|
||||
BlissResult<(Vec<f32>, Vec<f32>, Vec<f32>)>,
|
||||
> = s.spawn(|_| {
|
||||
let mut spectral_desc = SpectralDesc::new(SAMPLE_RATE)?;
|
||||
let windows = sample_array
|
||||
|
@ -297,13 +299,13 @@ impl Song {
|
|||
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();
|
||||
zcr_desc.do_(&sample_array);
|
||||
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(|_| {
|
||||
let mut loudness_desc = LoudnessDesc::default();
|
||||
let windows = sample_array.chunks(LoudnessDesc::WINDOW_SIZE);
|
||||
|
@ -339,12 +341,12 @@ impl Song {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn decode(path: &str) -> Result<InternalSong, BlissError> {
|
||||
pub(crate) fn decode(path: &Path) -> BlissResult<InternalSong> {
|
||||
ffmpeg::init()
|
||||
.map_err(|e| BlissError::DecodingError(format!("ffmpeg init error: {:?}.", e)))?;
|
||||
log::set_level(Level::Quiet);
|
||||
let mut song = InternalSong {
|
||||
path: path.to_string(),
|
||||
path: path.into(),
|
||||
..Default::default()
|
||||
};
|
||||
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);
|
||||
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") {
|
||||
song.artist = artist.to_string();
|
||||
song.artist = match artist {
|
||||
"" => None,
|
||||
a => Some(a.to_string()),
|
||||
};
|
||||
|
||||
};
|
||||
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") {
|
||||
song.genre = genre.to_string();
|
||||
song.genre = match genre {
|
||||
"" => None,
|
||||
g => Some(g.to_string()),
|
||||
};
|
||||
};
|
||||
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 = {
|
||||
if codec.channel_layout() == ChannelLayout::empty() {
|
||||
|
@ -494,12 +512,12 @@ impl Song {
|
|||
|
||||
#[derive(Default, Debug)]
|
||||
pub(crate) struct InternalSong {
|
||||
pub path: String,
|
||||
pub artist: String,
|
||||
pub title: String,
|
||||
pub album: String,
|
||||
pub track_number: String,
|
||||
pub genre: String,
|
||||
pub path: PathBuf,
|
||||
pub artist: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub album: Option<String>,
|
||||
pub track_number: Option<String>,
|
||||
pub genre: Option<String>,
|
||||
pub sample_array: Vec<f32>,
|
||||
}
|
||||
|
||||
|
@ -507,7 +525,7 @@ fn resample_frame(
|
|||
rx: Receiver<Audio>,
|
||||
mut resample_context: Context,
|
||||
mut sample_array: Vec<f32>,
|
||||
) -> Result<Vec<f32>, BlissError> {
|
||||
) -> BlissResult<Vec<f32>> {
|
||||
let mut resampled = ffmpeg::frame::Audio::empty();
|
||||
for decoded in rx.iter() {
|
||||
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 {
|
||||
use super::*;
|
||||
use ripemd160::{Digest, Ripemd160};
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn test_analysis_too_small() {
|
||||
|
@ -582,7 +601,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
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![
|
||||
0.3846389,
|
||||
-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 mut hasher = Ripemd160::new();
|
||||
for sample in song.sample_array.iter() {
|
||||
|
@ -622,17 +641,27 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_tags() {
|
||||
let song = Song::decode("data/s16_mono_22_5kHz.flac").unwrap();
|
||||
assert_eq!(song.artist, "David TMX");
|
||||
assert_eq!(song.title, "Renaissance");
|
||||
assert_eq!(song.album, "Renaissance");
|
||||
assert_eq!(song.track_number, "02");
|
||||
assert_eq!(song.genre, "Pop");
|
||||
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||
assert_eq!(song.artist, Some(String::from("David TMX")));
|
||||
assert_eq!(song.title, Some(String::from("Renaissance")));
|
||||
assert_eq!(song.album, Some(String::from("Renaissance")));
|
||||
assert_eq!(song.track_number, Some(String::from("02")));
|
||||
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]
|
||||
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 = [
|
||||
0xc5, 0xf8, 0x23, 0xce, 0x63, 0x2c, 0xf4, 0xa0, 0x72, 0x66, 0xbb, 0x49, 0xad, 0x84,
|
||||
0xb6, 0xea, 0x48, 0x48, 0x9c, 0x50,
|
||||
|
@ -642,7 +671,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
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 = [
|
||||
0x24, 0xed, 0x45, 0x58, 0x06, 0xbf, 0xfb, 0x05, 0x57, 0x5f, 0xdc, 0x4d, 0xb4, 0x9b,
|
||||
0xa5, 0x2b, 0x05, 0x56, 0x10, 0x4f,
|
||||
|
@ -652,7 +681,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
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
|
||||
// ffmpeg -i data/s16_mono_22_5kHz.flac -ar 22050 -ac 1 -c:a pcm_f32le
|
||||
// -f hash -hash ripemd160 -
|
||||
|
@ -665,7 +694,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
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
|
||||
// ffmpeg -i data/s16_mono_22_5kHz.mp3 -ar 22050 -ac 1 -c:a pcm_f32le
|
||||
// -f hash -hash ripemd160 -
|
||||
|
@ -678,13 +707,13 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
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();
|
||||
}
|
||||
|
||||
#[test]
|
||||
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 sample_array = song.sample_array;
|
||||
assert_eq!(
|
||||
|
@ -692,7 +721,7 @@ mod tests {
|
|||
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 sample_array = song.sample_array;
|
||||
assert_eq!(
|
||||
|
@ -700,7 +729,7 @@ mod tests {
|
|||
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 sample_array = song.sample_array;
|
||||
assert!(sample_array.len() as f32 / sample_array.capacity() as f32 > 0.90);
|
||||
|
@ -738,13 +767,13 @@ mod tests {
|
|||
#[test]
|
||||
fn test_decode_errors() {
|
||||
assert_eq!(
|
||||
Song::decode("nonexistent").unwrap_err(),
|
||||
Song::decode(Path::new("nonexistent")).unwrap_err(),
|
||||
BlissError::DecodingError(String::from(
|
||||
"while opening format: ffmpeg::Error(2: No such file or directory)."
|
||||
)),
|
||||
);
|
||||
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.")),
|
||||
);
|
||||
}
|
||||
|
@ -782,10 +811,11 @@ mod bench {
|
|||
extern crate test;
|
||||
use crate::Song;
|
||||
use test::Bencher;
|
||||
use std::path::Path;
|
||||
|
||||
#[bench]
|
||||
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(|| {
|
||||
Song::decode(&path).unwrap();
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
//! of a given Song.
|
||||
|
||||
use crate::utils::Normalize;
|
||||
use crate::BlissError;
|
||||
use crate::{BlissError, BlissResult};
|
||||
use bliss_audio_aubio_rs::{OnsetMode, Tempo};
|
||||
use log::warn;
|
||||
use ndarray::arr1;
|
||||
|
@ -39,7 +39,7 @@ impl BPMDesc {
|
|||
pub const WINDOW_SIZE: usize = 512;
|
||||
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 {
|
||||
aubio_obj: Tempo::new(
|
||||
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| {
|
||||
BlissError::AnalysisError(format!(
|
||||
"aubio error while computing tempo {}",
|
||||
|
@ -101,10 +101,11 @@ impl Normalize for BPMDesc {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::{Song, SAMPLE_RATE};
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
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();
|
||||
for chunk in song.sample_array.chunks_exact(BPMDesc::HOP_SIZE) {
|
||||
tempo_desc.do_(&chunk).unwrap();
|
||||
|
|
|
@ -9,7 +9,7 @@ use bliss_audio_aubio_rs::{bin_to_freq, PVoc, SpecDesc, SpecShape};
|
|||
use ndarray::{arr1, Axis};
|
||||
|
||||
use 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.
|
||||
|
@ -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 {
|
||||
centroid_aubio_desc: SpecDesc::new(SpecShape::Centroid, SpectralDesc::WINDOW_SIZE)
|
||||
.map_err(|e| {
|
||||
|
@ -158,7 +158,7 @@ impl SpectralDesc {
|
|||
* `get_centroid`, `get_flatness` and `get_rolloff` to get the respective
|
||||
* 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];
|
||||
self.phase_vocoder
|
||||
.do_(chunk, fftgrain.as_mut_slice())
|
||||
|
@ -266,6 +266,7 @@ impl Normalize for ZeroCrossingRateDesc {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::Song;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn test_zcr_boundaries() {
|
||||
|
@ -287,7 +288,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
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();
|
||||
for chunk in song.sample_array.chunks_exact(SpectralDesc::HOP_SIZE) {
|
||||
zcr_desc.do_(&chunk);
|
||||
|
@ -309,7 +310,7 @@ mod tests {
|
|||
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();
|
||||
for chunk in song.sample_array.chunks_exact(SpectralDesc::HOP_SIZE) {
|
||||
spectral_desc.do_(&chunk).unwrap();
|
||||
|
@ -326,7 +327,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
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();
|
||||
for chunk in song.sample_array.chunks_exact(SpectralDesc::HOP_SIZE) {
|
||||
spectral_desc.do_(&chunk).unwrap();
|
||||
|
@ -356,7 +357,7 @@ mod tests {
|
|||
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();
|
||||
for chunk in song.sample_array.chunks_exact(SpectralDesc::HOP_SIZE) {
|
||||
spectral_desc.do_(&chunk).unwrap();
|
||||
|
@ -372,7 +373,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
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();
|
||||
for chunk in song.sample_array.chunks_exact(SpectralDesc::HOP_SIZE) {
|
||||
spectral_desc.do_(&chunk).unwrap();
|
||||
|
@ -390,7 +391,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
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();
|
||||
for chunk in song.sample_array.chunks_exact(SpectralDesc::HOP_SIZE) {
|
||||
spectral_desc.do_(&chunk).unwrap();
|
||||
|
@ -419,7 +420,7 @@ mod tests {
|
|||
{
|
||||
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();
|
||||
for chunk in song.sample_array.chunks_exact(SpectralDesc::HOP_SIZE) {
|
||||
spectral_desc.do_(&chunk).unwrap();
|
||||
|
|
|
@ -172,6 +172,7 @@ mod tests {
|
|||
use ndarray::{arr1, Array};
|
||||
use ndarray_npy::ReadNpyExt;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn test_convolve() {
|
||||
|
@ -497,7 +498,7 @@ mod tests {
|
|||
let file = File::open("data/librosa-stft.npy").unwrap();
|
||||
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);
|
||||
|
||||
|
@ -525,6 +526,7 @@ mod bench {
|
|||
use crate::Song;
|
||||
use ndarray::Array;
|
||||
use test::Bencher;
|
||||
use std::path::Path;
|
||||
|
||||
#[bench]
|
||||
fn bench_convolve(b: &mut Bencher) {
|
||||
|
@ -538,7 +540,7 @@ mod bench {
|
|||
|
||||
#[bench]
|
||||
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(|| {
|
||||
stft(&signal, 2048, 512);
|
||||
|
|
Loading…
Reference in a new issue