Add radio sorter with improved music analysis and misc cleanup

This commit is contained in:
NGnius (Graham) 2023-01-24 22:44:56 -05:00
parent 34aa327742
commit b76ab9cdcb
13 changed files with 378 additions and 108 deletions

92
Cargo.lock generated
View file

@ -382,12 +382,6 @@ dependencies = [
"os_str_bytes", "os_str_bytes",
] ]
[[package]]
name = "claxon"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bfbf56724aa9eca8afa4fcfadeb479e722935bb2a0900c2d37e0cc477af0688"
[[package]] [[package]]
name = "combine" name = "combine"
version = "4.6.6" version = "4.6.6"
@ -772,15 +766,6 @@ dependencies = [
"miniz_oxide", "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]] [[package]]
name = "fs_extra" name = "fs_extra"
version = "1.2.0" version = "1.2.0"
@ -885,12 +870,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "hound"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d13cdbd5dbb29f9c88095bbdc2590c9cba0d0a1269b983fef6b2cdd7e9f4db1"
[[package]] [[package]]
name = "humantime" name = "humantime"
version = "2.1.0" version = "2.1.0"
@ -1006,17 +985,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 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]] [[package]]
name = "libc" name = "libc"
version = "0.2.137" version = "0.2.137"
@ -1145,26 +1113,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 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]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.5.4" version = "0.5.4"
@ -1235,7 +1183,6 @@ dependencies = [
name = "muss-player" name = "muss-player"
version = "0.9.0" version = "0.9.0"
dependencies = [ dependencies = [
"fluent-uri",
"m3u8-rs", "m3u8-rs",
"mpd", "mpd",
"mpris-player", "mpris-player",
@ -1524,15 +1471,6 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "ogg"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e"
dependencies = [
"byteorder",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.16.0" version = "1.16.0"
@ -1993,11 +1931,7 @@ version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb10b653d5ec0e9411a2e7d46e2c7f4046fd87d35b9955bd73ba4108d69072b5" checksum = "eb10b653d5ec0e9411a2e7d46e2c7f4046fd87d35b9955bd73ba4108d69072b5"
dependencies = [ dependencies = [
"claxon",
"cpal", "cpal",
"hound",
"lewton",
"minimp3",
"symphonia", "symphonia",
] ]
@ -2206,17 +2140,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" 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]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.10.0" version = "1.10.0"
@ -2604,21 +2527,6 @@ dependencies = [
"serde_json", "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]] [[package]]
name = "toml" name = "toml"
version = "0.5.9" version = "0.5.9"

@ -1 +1 @@
Subproject commit 4db79db51f6b931066b0185180d5884f6cdfe935 Subproject commit 2dcdd5643bc0cd055887128637abbb9bed041f77

View file

@ -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::shuffle_sort()) // accepts ~(shuffle)
.add(crate::lang::vocabulary::sorters::bliss_sort()) .add(crate::lang::vocabulary::sorters::bliss_sort())
.add(crate::lang::vocabulary::sorters::bliss_next_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 // iter blocks
.add( .add(
crate::lang::ItemBlockFactory::new() crate::lang::ItemBlockFactory::new()

View file

@ -9,6 +9,9 @@ use crate::tokens::Token;
use crate::Context; use crate::Context;
use crate::Item; use crate::Item;
// TODO change API to allow for accumulating modifiers
// e.g. build_op(&self, Option<Box<dyn Op>>, tokens) -> ...
pub trait SimpleOpFactory<T: Op + 'static> { pub trait SimpleOpFactory<T: Op + 'static> {
fn is_op_simple(&self, tokens: &VecDeque<Token>) -> bool; fn is_op_simple(&self, tokens: &VecDeque<Token>) -> bool;

View file

@ -2,7 +2,7 @@ use std::collections::VecDeque;
#[cfg(feature = "advanced")] #[cfg(feature = "advanced")]
use std::fmt::{Debug, Display, Error, Formatter}; 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; use crate::lang::SyntaxError;
#[cfg(feature = "advanced")] #[cfg(feature = "advanced")]
use crate::lang::{IteratorItem, Op, RuntimeMsg, Sorter}; use crate::lang::{IteratorItem, Op, RuntimeMsg, Sorter};
@ -142,7 +142,9 @@ pub struct BlissNextSorterFactory;
impl SorterFactory<BlissNextSorter> for BlissNextSorterFactory { impl SorterFactory<BlissNextSorter> for BlissNextSorterFactory {
fn is_sorter(&self, tokens: &VecDeque<&Token>) -> bool { 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( fn build_sorter(
@ -150,7 +152,11 @@ impl SorterFactory<BlissNextSorter> for BlissNextSorterFactory {
tokens: &mut VecDeque<Token>, tokens: &mut VecDeque<Token>,
_dict: &LanguageDictionary, _dict: &LanguageDictionary,
) -> Result<BlissNextSorter, SyntaxError> { ) -> Result<BlissNextSorter, SyntaxError> {
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)?; assert_name("bliss_next", tokens)?;
Ok(BlissNextSorter::default()) Ok(BlissNextSorter::default())
} }

View file

@ -5,7 +5,7 @@ use std::fmt::{Debug, Display, Error, Formatter};
#[cfg(feature = "advanced")] #[cfg(feature = "advanced")]
use std::collections::HashMap; 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; use crate::lang::SyntaxError;
#[cfg(feature = "advanced")] #[cfg(feature = "advanced")]
use crate::lang::{IteratorItem, Op, RuntimeMsg, Sorter}; use crate::lang::{IteratorItem, Op, RuntimeMsg, Sorter};
@ -134,7 +134,7 @@ pub struct BlissSorterFactory;
impl SorterFactory<BlissSorter> for BlissSorterFactory { impl SorterFactory<BlissSorter> for BlissSorterFactory {
fn is_sorter(&self, tokens: &VecDeque<&Token>) -> bool { fn is_sorter(&self, tokens: &VecDeque<&Token>) -> bool {
tokens.len() > 1 tokens.len() > 1
&& check_name("advanced", tokens[0]) && (tokens[0].is_tilde() || check_name("advanced", tokens[0]))
&& check_name("bliss_first", tokens[1]) && check_name("bliss_first", tokens[1])
} }
@ -143,7 +143,11 @@ impl SorterFactory<BlissSorter> for BlissSorterFactory {
tokens: &mut VecDeque<Token>, tokens: &mut VecDeque<Token>,
_dict: &LanguageDictionary, _dict: &LanguageDictionary,
) -> Result<BlissSorter, SyntaxError> { ) -> Result<BlissSorter, SyntaxError> {
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)?; assert_name("bliss_first", tokens)?;
Ok(BlissSorter::default()) Ok(BlissSorter::default())
} }

View file

@ -2,6 +2,7 @@ mod bliss_next_sorter;
mod bliss_sorter; mod bliss_sorter;
mod empty_sorter; mod empty_sorter;
mod field_sorter; mod field_sorter;
mod radio_sorter;
mod shuffle; mod shuffle;
pub use bliss_next_sorter::{ 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 bliss_sorter::{bliss_sort, BlissSorter, BlissSorterFactory, BlissSorterStatementFactory};
pub use empty_sorter::{empty_sort, EmptySorter, EmptySorterFactory, EmptySorterStatementFactory}; pub use empty_sorter::{empty_sort, EmptySorter, EmptySorterFactory, EmptySorterStatementFactory};
pub use field_sorter::{field_sort, FieldSorter, FieldSorterFactory, FieldSorterStatementFactory}; pub use field_sorter::{field_sort, FieldSorter, FieldSorterFactory, FieldSorterStatementFactory};
pub use radio_sorter::{radio_sort, RadioSorter, RadioSorterFactory, RadioSorterStatementFactory};
pub use shuffle::{ pub use shuffle::{
shuffle_sort, ShuffleSorter, ShuffleSorterFactory, ShuffleSorterStatementFactory, shuffle_sort, ShuffleSorter, ShuffleSorterFactory, ShuffleSorterStatementFactory,
}; };

View file

@ -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<Item>,
comparison: Option<MusicAnalyzerDistance>,
}
#[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<IteratorItem>,
) -> 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<RadioSorter> 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<Token>,
_dict: &LanguageDictionary,
) -> Result<RadioSorter, SyntaxError> {
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<RadioSorter, RadioSorterFactory>;
#[inline(always)]
pub fn radio_sort() -> RadioSorterStatementFactory {
RadioSorterStatementFactory::new(RadioSorterFactory)
}

View file

@ -27,5 +27,5 @@ pub mod general {
#[cfg(feature = "advanced")] #[cfg(feature = "advanced")]
pub mod advanced { pub mod advanced {
pub use super::music_analysis::{DefaultAnalyzer, MusicAnalyzer}; pub use super::music_analysis::{DefaultAnalyzer, MusicAnalyzer, MusicAnalyzerDistance};
} }

View file

@ -7,7 +7,7 @@ use std::sync::mpsc::{channel, Receiver, Sender};
#[cfg(feature = "bliss-audio-symphonia")] #[cfg(feature = "bliss-audio-symphonia")]
use crate::lang::TypePrimitive; use crate::lang::TypePrimitive;
#[cfg(feature = "bliss-audio-symphonia")] #[cfg(feature = "bliss-audio-symphonia")]
use bliss_audio_symphonia::{BlissError, Song}; use bliss_audio_symphonia::{BlissError, Song, AnalysisIndex};
// assumed processor threads // assumed processor threads
const DEFAULT_PARALLELISM: usize = 2; const DEFAULT_PARALLELISM: usize = 2;
@ -23,6 +23,14 @@ use crate::Item;
const PATH_FIELD: &str = "filename"; const PATH_FIELD: &str = "filename";
#[derive(Debug, Clone)]
pub enum MusicAnalyzerDistance {
Tempo,
Spectrum,
Loudness,
Chroma,
}
pub trait MusicAnalyzer: Debug + Send { pub trait MusicAnalyzer: Debug + Send {
fn prepare_distance(&mut self, from: &Item, to: &Item) -> Result<(), RuntimeMsg>; 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<f64, RuntimeMsg>; fn get_distance(&mut self, from: &Item, to: &Item) -> Result<f64, RuntimeMsg>;
fn get_custom_distance(&mut self, from: &Item, to: &Item, compare: MusicAnalyzerDistance) -> Result<f64, RuntimeMsg>;
fn clear_cache(&mut self) -> Result<(), RuntimeMsg>; fn clear_cache(&mut self) -> Result<(), RuntimeMsg>;
} }
@ -104,6 +114,32 @@ impl DefaultAnalyzer {
}) })
.map_err(|e| RuntimeMsg(format!("Channel send error: {}", e))) .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")] #[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<f64, RuntimeMsg> {
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> { fn clear_cache(&mut self) -> Result<(), RuntimeMsg> {
self.requests self.requests
.send(RequestType::Clear {}) .send(RequestType::Clear {})
@ -161,15 +269,19 @@ pub struct DefaultAnalyzer {}
#[cfg(not(feature = "bliss-audio-symphonia"))] #[cfg(not(feature = "bliss-audio-symphonia"))]
impl MusicAnalyzer for DefaultAnalyzer { 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(()) Ok(())
} }
fn prepare_item(&mut self, item: &Item) -> Result<(), RuntimeMsg> { fn prepare_item(&mut self, _item: &Item) -> Result<(), RuntimeMsg> {
Ok(()) Ok(())
} }
fn get_distance(&mut self, item: &Item) -> Result<f64, RuntimeMsg> { fn get_distance(&mut self, _from: &Item, _to: &Item) -> Result<f64, RuntimeMsg> {
Ok(f64::MAX)
}
fn get_custom_distance(&mut self, _from: &Item, _to: &Item, _compare: MusicAnalyzerDistance) -> Result<f64, RuntimeMsg> {
Ok(f64::MAX) Ok(f64::MAX)
} }

View file

@ -379,6 +379,11 @@ fn execute_blissfirstsort_line() -> Result<(), InterpreterError> {
"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`)~(advanced bliss_first)", "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`)~(advanced bliss_first)",
false, false,
true, 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)", "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`)~(advanced bliss_next)",
false, false,
true, 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,
) )
} }

View file

@ -6,9 +6,8 @@ license = "LGPL-2.1-only OR GPL-3.0-only"
readme = "README.md" readme = "README.md"
[dependencies] [dependencies]
rodio = { version = "^0.16", features = ["symphonia-all"]} rodio = { version = "^0.16", features = ["symphonia-all"], default-features = false}
m3u8-rs = { version = "^3.0" } m3u8-rs = { version = "^3.0" }
fluent-uri = { version = "^0.1" }
mpd = { version = "0.0.12", optional = true } mpd = { version = "0.0.12", optional = true }
# local # local

View file

@ -72,7 +72,7 @@ impl<I: std::iter::Iterator<Item = Result<Item, InterpreterError>>> PlayerServer
std::thread::spawn(move || { std::thread::spawn(move || {
let sample_rate = source_in2.sample_rate(); let sample_rate = source_in2.sample_rate();
let channels = source_in2.channels() as u32; 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)); let duration = std::time::Duration::from_secs_f64(sample_count / ((sample_rate * channels) as f64));
event3.lock().map(|event| event3.lock().map(|event|
event.send( event.send(