From b76ab9cdcbfc78370527843a6aae39a5270053e4 Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Tue, 24 Jan 2023 22:44:56 -0500 Subject: [PATCH] Add radio sorter with improved music analysis and misc cleanup --- Cargo.lock | 92 -------- bliss-rs | 2 +- interpreter/src/interpretor.rs | 3 +- interpreter/src/lang/operation.rs | 3 + .../vocabulary/sorters/bliss_next_sorter.rs | 12 +- .../lang/vocabulary/sorters/bliss_sorter.rs | 10 +- .../src/lang/vocabulary/sorters/mod.rs | 2 + .../lang/vocabulary/sorters/radio_sorter.rs | 216 ++++++++++++++++++ interpreter/src/processing/mod.rs | 2 +- interpreter/src/processing/music_analysis.rs | 120 +++++++++- interpreter/tests/single_line.rs | 19 ++ player/Cargo.toml | 3 +- player/src/player_wrapper.rs | 2 +- 13 files changed, 378 insertions(+), 108 deletions(-) create mode 100644 interpreter/src/lang/vocabulary/sorters/radio_sorter.rs diff --git a/Cargo.lock b/Cargo.lock index 33338bf..933faec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -382,12 +382,6 @@ dependencies = [ "os_str_bytes", ] -[[package]] -name = "claxon" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bfbf56724aa9eca8afa4fcfadeb479e722935bb2a0900c2d37e0cc477af0688" - [[package]] name = "combine" version = "4.6.6" @@ -772,15 +766,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "fluent-uri" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80caca0b85a39e8e4139c36441543e698b204279ae8065b3af413c9bf8b33146" -dependencies = [ - "bitflags", -] - [[package]] name = "fs_extra" version = "1.2.0" @@ -885,12 +870,6 @@ dependencies = [ "libc", ] -[[package]] -name = "hound" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d13cdbd5dbb29f9c88095bbdc2590c9cba0d0a1269b983fef6b2cdd7e9f4db1" - [[package]] name = "humantime" version = "2.1.0" @@ -1006,17 +985,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" -[[package]] -name = "lewton" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030" -dependencies = [ - "byteorder", - "ogg", - "tinyvec", -] - [[package]] name = "libc" version = "0.2.137" @@ -1145,26 +1113,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "minimp3" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985438f75febf74c392071a975a29641b420dd84431135a6e6db721de4b74372" -dependencies = [ - "minimp3-sys", - "slice-deque", - "thiserror", -] - -[[package]] -name = "minimp3-sys" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e21c73734c69dc95696c9ed8926a2b393171d98b3f5f5935686a26a487ab9b90" -dependencies = [ - "cc", -] - [[package]] name = "miniz_oxide" version = "0.5.4" @@ -1235,7 +1183,6 @@ dependencies = [ name = "muss-player" version = "0.9.0" dependencies = [ - "fluent-uri", "m3u8-rs", "mpd", "mpris-player", @@ -1524,15 +1471,6 @@ dependencies = [ "cc", ] -[[package]] -name = "ogg" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" -dependencies = [ - "byteorder", -] - [[package]] name = "once_cell" version = "1.16.0" @@ -1993,11 +1931,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb10b653d5ec0e9411a2e7d46e2c7f4046fd87d35b9955bd73ba4108d69072b5" dependencies = [ - "claxon", "cpal", - "hound", - "lewton", - "minimp3", "symphonia", ] @@ -2206,17 +2140,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" -[[package]] -name = "slice-deque" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31ef6ee280cdefba6d2d0b4b78a84a1c1a3f3a4cec98c2d4231c8bc225de0f25" -dependencies = [ - "libc", - "mach", - "winapi 0.3.9", -] - [[package]] name = "smallvec" version = "1.10.0" @@ -2604,21 +2527,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - [[package]] name = "toml" version = "0.5.9" diff --git a/bliss-rs b/bliss-rs index 4db79db..2dcdd56 160000 --- a/bliss-rs +++ b/bliss-rs @@ -1 +1 @@ -Subproject commit 4db79db51f6b931066b0185180d5884f6cdfe935 +Subproject commit 2dcdd5643bc0cd055887128637abbb9bed041f77 diff --git a/interpreter/src/interpretor.rs b/interpreter/src/interpretor.rs index 5b7f392..d38a67b 100644 --- a/interpreter/src/interpretor.rs +++ b/interpreter/src/interpretor.rs @@ -19,7 +19,8 @@ pub(crate) fn standard_vocab(vocabulary: &mut LanguageDictionary) { .add(crate::lang::vocabulary::sorters::shuffle_sort()) // accepts ~(shuffle) .add(crate::lang::vocabulary::sorters::bliss_sort()) .add(crate::lang::vocabulary::sorters::bliss_next_sort()) - .add(crate::lang::vocabulary::sorters::field_sort()) // accepts any ~(something) + .add(crate::lang::vocabulary::sorters::radio_sort()) + .add(crate::lang::vocabulary::sorters::field_sort()) // accepts any ~(name) // iter blocks .add( crate::lang::ItemBlockFactory::new() diff --git a/interpreter/src/lang/operation.rs b/interpreter/src/lang/operation.rs index c6aa8dc..581c168 100644 --- a/interpreter/src/lang/operation.rs +++ b/interpreter/src/lang/operation.rs @@ -9,6 +9,9 @@ use crate::tokens::Token; use crate::Context; use crate::Item; +// TODO change API to allow for accumulating modifiers +// e.g. build_op(&self, Option>, tokens) -> ... + pub trait SimpleOpFactory { fn is_op_simple(&self, tokens: &VecDeque) -> bool; diff --git a/interpreter/src/lang/vocabulary/sorters/bliss_next_sorter.rs b/interpreter/src/lang/vocabulary/sorters/bliss_next_sorter.rs index 8423b11..8bb2d54 100644 --- a/interpreter/src/lang/vocabulary/sorters/bliss_next_sorter.rs +++ b/interpreter/src/lang/vocabulary/sorters/bliss_next_sorter.rs @@ -2,7 +2,7 @@ use std::collections::VecDeque; #[cfg(feature = "advanced")] use std::fmt::{Debug, Display, Error, Formatter}; -use crate::lang::utility::{assert_name, check_name}; +use crate::lang::utility::{assert_name, check_name, assert_token_raw}; use crate::lang::SyntaxError; #[cfg(feature = "advanced")] use crate::lang::{IteratorItem, Op, RuntimeMsg, Sorter}; @@ -142,7 +142,9 @@ pub struct BlissNextSorterFactory; impl SorterFactory for BlissNextSorterFactory { fn is_sorter(&self, tokens: &VecDeque<&Token>) -> bool { - tokens.len() > 1 && check_name("advanced", tokens[0]) && check_name("bliss_next", tokens[1]) + tokens.len() > 1 + && (tokens[0].is_tilde() || check_name("advanced", tokens[0])) + && check_name("bliss_next", tokens[1]) } fn build_sorter( @@ -150,7 +152,11 @@ impl SorterFactory for BlissNextSorterFactory { tokens: &mut VecDeque, _dict: &LanguageDictionary, ) -> Result { - assert_name("advanced", tokens)?; + if tokens[0].is_tilde() { + assert_token_raw(Token::Tilde, tokens)?; + } else { + assert_name("advanced", tokens)?; + } assert_name("bliss_next", tokens)?; Ok(BlissNextSorter::default()) } diff --git a/interpreter/src/lang/vocabulary/sorters/bliss_sorter.rs b/interpreter/src/lang/vocabulary/sorters/bliss_sorter.rs index 516d0ff..74374f9 100644 --- a/interpreter/src/lang/vocabulary/sorters/bliss_sorter.rs +++ b/interpreter/src/lang/vocabulary/sorters/bliss_sorter.rs @@ -5,7 +5,7 @@ use std::fmt::{Debug, Display, Error, Formatter}; #[cfg(feature = "advanced")] use std::collections::HashMap; -use crate::lang::utility::{assert_name, check_name}; +use crate::lang::utility::{assert_name, check_name, assert_token_raw}; use crate::lang::SyntaxError; #[cfg(feature = "advanced")] use crate::lang::{IteratorItem, Op, RuntimeMsg, Sorter}; @@ -134,7 +134,7 @@ pub struct BlissSorterFactory; impl SorterFactory for BlissSorterFactory { fn is_sorter(&self, tokens: &VecDeque<&Token>) -> bool { tokens.len() > 1 - && check_name("advanced", tokens[0]) + && (tokens[0].is_tilde() || check_name("advanced", tokens[0])) && check_name("bliss_first", tokens[1]) } @@ -143,7 +143,11 @@ impl SorterFactory for BlissSorterFactory { tokens: &mut VecDeque, _dict: &LanguageDictionary, ) -> Result { - assert_name("advanced", tokens)?; + if tokens[0].is_tilde() { + assert_token_raw(Token::Tilde, tokens)?; + } else { + assert_name("advanced", tokens)?; + } assert_name("bliss_first", tokens)?; Ok(BlissSorter::default()) } diff --git a/interpreter/src/lang/vocabulary/sorters/mod.rs b/interpreter/src/lang/vocabulary/sorters/mod.rs index d9f42e7..d349c28 100644 --- a/interpreter/src/lang/vocabulary/sorters/mod.rs +++ b/interpreter/src/lang/vocabulary/sorters/mod.rs @@ -2,6 +2,7 @@ mod bliss_next_sorter; mod bliss_sorter; mod empty_sorter; mod field_sorter; +mod radio_sorter; mod shuffle; pub use bliss_next_sorter::{ @@ -10,6 +11,7 @@ pub use bliss_next_sorter::{ pub use bliss_sorter::{bliss_sort, BlissSorter, BlissSorterFactory, BlissSorterStatementFactory}; pub use empty_sorter::{empty_sort, EmptySorter, EmptySorterFactory, EmptySorterStatementFactory}; pub use field_sorter::{field_sort, FieldSorter, FieldSorterFactory, FieldSorterStatementFactory}; +pub use radio_sorter::{radio_sort, RadioSorter, RadioSorterFactory, RadioSorterStatementFactory}; pub use shuffle::{ shuffle_sort, ShuffleSorter, ShuffleSorterFactory, ShuffleSorterStatementFactory, }; diff --git a/interpreter/src/lang/vocabulary/sorters/radio_sorter.rs b/interpreter/src/lang/vocabulary/sorters/radio_sorter.rs new file mode 100644 index 0000000..bb42a32 --- /dev/null +++ b/interpreter/src/lang/vocabulary/sorters/radio_sorter.rs @@ -0,0 +1,216 @@ +use std::collections::VecDeque; +#[cfg(feature = "advanced")] +use std::fmt::{Debug, Display, Error, Formatter}; + +use rand::{thread_rng, Rng}; + +use crate::lang::utility::{assert_name, check_name, assert_token_raw, assert_token}; +use crate::lang::SyntaxError; +#[cfg(feature = "advanced")] +use crate::lang::{IteratorItem, Op, RuntimeMsg, Sorter}; +use crate::lang::{LanguageDictionary, SortStatementFactory, SorterFactory}; +use crate::tokens::Token; +#[cfg(feature = "advanced")] +use crate::Item; +#[cfg(feature = "advanced")] +use crate::processing::advanced::MusicAnalyzerDistance; + +#[cfg(feature = "advanced")] +#[derive(Debug)] +pub struct RadioSorter { + up_to: usize, + algorithm_done: bool, + init_done: bool, + item_buf: VecDeque, + comparison: Option, +} + +#[cfg(feature = "advanced")] +impl std::clone::Clone for RadioSorter { + fn clone(&self) -> Self { + Self { + up_to: self.up_to, + algorithm_done: self.algorithm_done, + init_done: self.init_done, + item_buf: self.item_buf.clone(), + comparison: None, + } + } +} + +#[cfg(feature = "advanced")] +impl Default for RadioSorter { + fn default() -> Self { + Self { + up_to: usize::MAX, + algorithm_done: false, + init_done: false, + item_buf: VecDeque::new(), + comparison: None, + } + } +} + +#[cfg(feature = "advanced")] +impl Sorter for RadioSorter { + fn sort( + &mut self, + iterator: &mut dyn Op, + items_out: &mut VecDeque, + ) -> Result<(), RuntimeMsg> { + if !self.init_done { + // first run + self.init_done = true; + while let Some(item) = iterator.next() { + match item { + Ok(item) => self.item_buf.push_back(item), + Err(e) => items_out.push_back(Err(e)), + } + if self.item_buf.len() + items_out.len() >= self.up_to { + break; + } + } + if !self.item_buf.is_empty() { + // choose (new) random first element + let random_num: usize = thread_rng().gen(); + let random_i = random_num % self.item_buf.len(); + self.item_buf.swap(random_i, 0); + // compare everything to new first element + let first = &self.item_buf[0]; + let mut ctx = iterator.escape(); + for i in 1..self.item_buf.len() { + let item = &self.item_buf[i]; + if let Err(e) = ctx.analysis.prepare_distance(first, item) { + iterator.enter(ctx); + return Err(e); + } + } + iterator.enter(ctx); + items_out.push_back(Ok(first.to_owned())); + } + } else { + #[allow(clippy::collapsible_else_if)] + if self.item_buf.len() > 2 { + let last = self.item_buf.pop_front().unwrap(); + let mut best_index = 0; + let mut best_distance = f64::MAX; + let mut ctx = iterator.escape(); + for i in 0..self.item_buf.len() { + let current_item = &self.item_buf[i]; + if let Some(comp) = self.comparison.clone() { + match ctx.analysis.get_custom_distance(&last, current_item, comp) { + Err(e) => { + iterator.enter(ctx); + return Err(e); + } + Ok(distance) => { + if distance < best_distance { + best_index = i; + best_distance = distance; + } + } + } + } else { + match ctx.analysis.get_distance(&last, current_item) { + Err(e) => { + iterator.enter(ctx); + return Err(e); + } + Ok(distance) => { + if distance < best_distance { + best_index = i; + best_distance = distance; + } + } + } + } + + } + if best_index != 0 { + self.item_buf.swap(0, best_index); + } + items_out.push_back(Ok(self.item_buf[0].clone())); + let next = &self.item_buf[0]; + for i in 1..self.item_buf.len() { + let item = &self.item_buf[i]; + if let Err(e) = ctx.analysis.prepare_distance(next, item) { + iterator.enter(ctx); + return Err(e); + } + } + iterator.enter(ctx); + } else if self.item_buf.len() == 2 { + self.item_buf.pop_front(); + items_out.push_back(Ok(self.item_buf.pop_front().unwrap())); + // note item_buf is emptied here, so this will not proceed to len() == 1 case on next call + } else if !self.item_buf.is_empty() { + // edge case where item_buf only ever had 1 item + items_out.push_back(Ok(self.item_buf.pop_front().unwrap())); + } + } + Ok(()) + } + + fn reset(&mut self) { + self.init_done = false; + } +} + +#[cfg(not(feature = "advanced"))] +pub type RadioSorter = crate::lang::vocabulary::sorters::EmptySorter; + +#[cfg(feature = "advanced")] +impl Display for RadioSorter { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "~ radio") + } +} + +pub struct RadioSorterFactory; + +impl SorterFactory for RadioSorterFactory { + fn is_sorter(&self, tokens: &VecDeque<&Token>) -> bool { + tokens.len() > 1 + && tokens[0].is_tilde() + && check_name("radio", tokens[1]) + } + + fn build_sorter( + &self, + tokens: &mut VecDeque, + _dict: &LanguageDictionary, + ) -> Result { + assert_token_raw(Token::Tilde, tokens)?; + assert_name("radio", tokens)?; + #[allow(dead_code)] + let mode = if !tokens.is_empty() { + Some(assert_token(|t| match t { + Token::Name(n) => match &n as &str { + "tempo" | "beat" => Some(MusicAnalyzerDistance::Tempo), + "spectrum" | "s" => Some(MusicAnalyzerDistance::Spectrum), + "loudness" | "volume" => Some(MusicAnalyzerDistance::Loudness), + "chroma" | "c" => Some(MusicAnalyzerDistance::Chroma), + _ => None + }, + _ => None, + }, Token::Name("".into()), tokens)?) + } else { + None + }; + #[cfg(not(feature = "advanced"))] + {Ok(RadioSorter::default())} + #[cfg(feature = "advanced")] + {Ok(RadioSorter { + comparison: mode, + ..Default::default() + })} + } +} + +pub type RadioSorterStatementFactory = + SortStatementFactory; + +#[inline(always)] +pub fn radio_sort() -> RadioSorterStatementFactory { + RadioSorterStatementFactory::new(RadioSorterFactory) +} diff --git a/interpreter/src/processing/mod.rs b/interpreter/src/processing/mod.rs index 497de2a..935a182 100644 --- a/interpreter/src/processing/mod.rs +++ b/interpreter/src/processing/mod.rs @@ -27,5 +27,5 @@ pub mod general { #[cfg(feature = "advanced")] pub mod advanced { - pub use super::music_analysis::{DefaultAnalyzer, MusicAnalyzer}; + pub use super::music_analysis::{DefaultAnalyzer, MusicAnalyzer, MusicAnalyzerDistance}; } diff --git a/interpreter/src/processing/music_analysis.rs b/interpreter/src/processing/music_analysis.rs index 43b55f1..2cebff7 100644 --- a/interpreter/src/processing/music_analysis.rs +++ b/interpreter/src/processing/music_analysis.rs @@ -7,7 +7,7 @@ use std::sync::mpsc::{channel, Receiver, Sender}; #[cfg(feature = "bliss-audio-symphonia")] use crate::lang::TypePrimitive; #[cfg(feature = "bliss-audio-symphonia")] -use bliss_audio_symphonia::{BlissError, Song}; +use bliss_audio_symphonia::{BlissError, Song, AnalysisIndex}; // assumed processor threads const DEFAULT_PARALLELISM: usize = 2; @@ -23,6 +23,14 @@ use crate::Item; const PATH_FIELD: &str = "filename"; +#[derive(Debug, Clone)] +pub enum MusicAnalyzerDistance { + Tempo, + Spectrum, + Loudness, + Chroma, +} + pub trait MusicAnalyzer: Debug + Send { fn prepare_distance(&mut self, from: &Item, to: &Item) -> Result<(), RuntimeMsg>; @@ -30,6 +38,8 @@ pub trait MusicAnalyzer: Debug + Send { fn get_distance(&mut self, from: &Item, to: &Item) -> Result; + fn get_custom_distance(&mut self, from: &Item, to: &Item, compare: MusicAnalyzerDistance) -> Result; + fn clear_cache(&mut self) -> Result<(), RuntimeMsg>; } @@ -104,6 +114,32 @@ impl DefaultAnalyzer { }) .map_err(|e| RuntimeMsg(format!("Channel send error: {}", e))) } + + fn bliss_song_to_array(song: &Song) -> [f64; bliss_audio_symphonia::NUMBER_FEATURES] { + let analysis = &song.analysis; + [ + analysis[AnalysisIndex::Tempo] as _, + analysis[AnalysisIndex::Zcr] as _, + analysis[AnalysisIndex::MeanSpectralCentroid] as _, + analysis[AnalysisIndex::StdDeviationSpectralCentroid] as _, + analysis[AnalysisIndex::MeanSpectralRolloff] as _, + analysis[AnalysisIndex::StdDeviationSpectralRolloff] as _, + analysis[AnalysisIndex::MeanSpectralFlatness] as _, + analysis[AnalysisIndex::StdDeviationSpectralFlatness] as _, + analysis[AnalysisIndex::MeanLoudness] as _, + analysis[AnalysisIndex::StdDeviationLoudness] as _, + analysis[AnalysisIndex::Chroma1] as _, + analysis[AnalysisIndex::Chroma2] as _, + analysis[AnalysisIndex::Chroma3] as _, + analysis[AnalysisIndex::Chroma4] as _, + analysis[AnalysisIndex::Chroma5] as _, + analysis[AnalysisIndex::Chroma6] as _, + analysis[AnalysisIndex::Chroma7] as _, + analysis[AnalysisIndex::Chroma8] as _, + analysis[AnalysisIndex::Chroma9] as _, + analysis[AnalysisIndex::Chroma10] as _, + ] + } } #[cfg(feature = "bliss-audio-symphonia")] @@ -148,6 +184,78 @@ impl MusicAnalyzer for DefaultAnalyzer { )) } + fn get_custom_distance(&mut self, from: &Item, to: &Item, compare: MusicAnalyzerDistance) -> Result { + self.request_song(from, true)?; + self.request_song(to, true)?; + let path_from = Self::get_path(from)?; + let path_to = Self::get_path(to)?; + let mut from_song = None; + let mut to_song = None; + for response in self.responses.iter() { + match response { + ResponseType::Distance { .. } => {}, + ResponseType::Song { + path, + song + } => { + if path_from == path { + from_song = Some(song.map_err(|e| RuntimeMsg(format!("Bliss error: {}", e)))?); + } else if path_to == path { + to_song = Some(song.map_err(|e| RuntimeMsg(format!("Bliss error: {}", e)))?); + } + if to_song.is_some() && from_song.is_some() { + break; + } + }, + ResponseType::UnsupportedSong { path, msg } => { + if path == path_to || path == path_from { + return Err(RuntimeMsg(format!("Bliss error: {}", msg))); + } + } + } + } + if to_song.is_some() && from_song.is_some() { + let to_arr = Self::bliss_song_to_array(&to_song.unwrap()); + let from_arr = Self::bliss_song_to_array(&from_song.unwrap()); + Ok(match compare { + MusicAnalyzerDistance::Tempo => ( + (to_arr[AnalysisIndex::Tempo as usize] - from_arr[AnalysisIndex::Tempo as usize]).powi(2) + + (to_arr[AnalysisIndex::Zcr as usize] - from_arr[AnalysisIndex::Zcr as usize]).powi(2) + ).sqrt(), + MusicAnalyzerDistance::Spectrum => ( + (to_arr[AnalysisIndex::MeanSpectralCentroid as usize] - from_arr[AnalysisIndex::MeanSpectralCentroid as usize]).powi(2) + + (to_arr[AnalysisIndex::StdDeviationSpectralCentroid as usize] - from_arr[AnalysisIndex::StdDeviationSpectralCentroid as usize]).powi(2) + + (to_arr[AnalysisIndex::MeanSpectralRolloff as usize] - from_arr[AnalysisIndex::MeanSpectralRolloff as usize]).powi(2) + + (to_arr[AnalysisIndex::StdDeviationSpectralRolloff as usize] - from_arr[AnalysisIndex::StdDeviationSpectralRolloff as usize]).powi(2) + + (to_arr[AnalysisIndex::MeanSpectralFlatness as usize] - from_arr[AnalysisIndex::MeanSpectralFlatness as usize]).powi(2) + + (to_arr[AnalysisIndex::StdDeviationSpectralFlatness as usize] - from_arr[AnalysisIndex::StdDeviationSpectralFlatness as usize]).powi(2) + ).sqrt(), + MusicAnalyzerDistance::Loudness => { + let mean_delta = to_arr[AnalysisIndex::MeanLoudness as usize] - from_arr[AnalysisIndex::MeanLoudness as usize]; + let deviation_delta = to_arr[AnalysisIndex::StdDeviationLoudness as usize] - from_arr[AnalysisIndex::StdDeviationLoudness as usize]; + + (mean_delta.powi(2) + deviation_delta.powi(2)).sqrt() + }, + MusicAnalyzerDistance::Chroma => ( + (to_arr[AnalysisIndex::Chroma1 as usize] - from_arr[AnalysisIndex::Chroma1 as usize]).powi(2) + + (to_arr[AnalysisIndex::Chroma2 as usize] - from_arr[AnalysisIndex::Chroma2 as usize]).powi(2) + + (to_arr[AnalysisIndex::Chroma3 as usize] - from_arr[AnalysisIndex::Chroma3 as usize]).powi(2) + + (to_arr[AnalysisIndex::Chroma4 as usize] - from_arr[AnalysisIndex::Chroma4 as usize]).powi(2) + + (to_arr[AnalysisIndex::Chroma5 as usize] - from_arr[AnalysisIndex::Chroma5 as usize]).powi(2) + + (to_arr[AnalysisIndex::Chroma6 as usize] - from_arr[AnalysisIndex::Chroma6 as usize]).powi(2) + + (to_arr[AnalysisIndex::Chroma7 as usize] - from_arr[AnalysisIndex::Chroma7 as usize]).powi(2) + + (to_arr[AnalysisIndex::Chroma8 as usize] - from_arr[AnalysisIndex::Chroma8 as usize]).powi(2) + + (to_arr[AnalysisIndex::Chroma9 as usize] - from_arr[AnalysisIndex::Chroma9 as usize]).powi(2) + + (to_arr[AnalysisIndex::Chroma10 as usize] - from_arr[AnalysisIndex::Chroma10 as usize]).powi(2) + ).sqrt(), + }) + } else { + Err(RuntimeMsg( + "Channel closed without complete response: internal error".to_owned(), + )) + } + } + fn clear_cache(&mut self) -> Result<(), RuntimeMsg> { self.requests .send(RequestType::Clear {}) @@ -161,15 +269,19 @@ pub struct DefaultAnalyzer {} #[cfg(not(feature = "bliss-audio-symphonia"))] impl MusicAnalyzer for DefaultAnalyzer { - fn prepare_distance(&mut self, from: &Item, to: &Item) -> Result<(), RuntimeMsg> { + fn prepare_distance(&mut self, _from: &Item, _to: &Item) -> Result<(), RuntimeMsg> { Ok(()) } - fn prepare_item(&mut self, item: &Item) -> Result<(), RuntimeMsg> { + fn prepare_item(&mut self, _item: &Item) -> Result<(), RuntimeMsg> { Ok(()) } - fn get_distance(&mut self, item: &Item) -> Result { + fn get_distance(&mut self, _from: &Item, _to: &Item) -> Result { + Ok(f64::MAX) + } + + fn get_custom_distance(&mut self, _from: &Item, _to: &Item, _compare: MusicAnalyzerDistance) -> Result { Ok(f64::MAX) } diff --git a/interpreter/tests/single_line.rs b/interpreter/tests/single_line.rs index fda6a07..28af02f 100644 --- a/interpreter/tests/single_line.rs +++ b/interpreter/tests/single_line.rs @@ -379,6 +379,11 @@ fn execute_blissfirstsort_line() -> Result<(), InterpreterError> { "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`)~(advanced bliss_first)", false, true, + )?; + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`)~(~bliss_first)", + false, + true, ) } @@ -388,6 +393,20 @@ fn execute_blissnextsort_line() -> Result<(), InterpreterError> { "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`)~(advanced bliss_next)", false, true, + )?; + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`)~(~bliss_next)", + false, + true, + ) +} + +#[test] +fn execute_radiosort_line() -> Result<(), InterpreterError> { + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`)~(~radio)", + false, + true, ) } diff --git a/player/Cargo.toml b/player/Cargo.toml index cf0b7b0..ff8c8a8 100644 --- a/player/Cargo.toml +++ b/player/Cargo.toml @@ -6,9 +6,8 @@ license = "LGPL-2.1-only OR GPL-3.0-only" readme = "README.md" [dependencies] -rodio = { version = "^0.16", features = ["symphonia-all"]} +rodio = { version = "^0.16", features = ["symphonia-all"], default-features = false} m3u8-rs = { version = "^3.0" } -fluent-uri = { version = "^0.1" } mpd = { version = "0.0.12", optional = true } # local diff --git a/player/src/player_wrapper.rs b/player/src/player_wrapper.rs index dc4e836..c405cd8 100644 --- a/player/src/player_wrapper.rs +++ b/player/src/player_wrapper.rs @@ -72,7 +72,7 @@ impl>> PlayerServer std::thread::spawn(move || { let sample_rate = source_in2.sample_rate(); let channels = source_in2.channels() as u32; - let sample_count = source_in2.clone().count() as f64; + let sample_count = source_in2.count() as f64; let duration = std::time::Duration::from_secs_f64(sample_count / ((sample_rate * channels) as f64)); event3.lock().map(|event| event.send(