Add radio sorter with improved music analysis and misc cleanup
This commit is contained in:
parent
34aa327742
commit
b76ab9cdcb
13 changed files with 378 additions and 108 deletions
92
Cargo.lock
generated
92
Cargo.lock
generated
|
@ -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"
|
||||
|
|
2
bliss-rs
2
bliss-rs
|
@ -1 +1 @@
|
|||
Subproject commit 4db79db51f6b931066b0185180d5884f6cdfe935
|
||||
Subproject commit 2dcdd5643bc0cd055887128637abbb9bed041f77
|
|
@ -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()
|
||||
|
|
|
@ -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<Box<dyn Op>>, tokens) -> ...
|
||||
|
||||
pub trait SimpleOpFactory<T: Op + 'static> {
|
||||
fn is_op_simple(&self, tokens: &VecDeque<Token>) -> bool;
|
||||
|
||||
|
|
|
@ -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<BlissNextSorter> 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<BlissNextSorter> for BlissNextSorterFactory {
|
|||
tokens: &mut VecDeque<Token>,
|
||||
_dict: &LanguageDictionary,
|
||||
) -> 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)?;
|
||||
Ok(BlissNextSorter::default())
|
||||
}
|
||||
|
|
|
@ -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<BlissSorter> 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<BlissSorter> for BlissSorterFactory {
|
|||
tokens: &mut VecDeque<Token>,
|
||||
_dict: &LanguageDictionary,
|
||||
) -> 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)?;
|
||||
Ok(BlissSorter::default())
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
216
interpreter/src/lang/vocabulary/sorters/radio_sorter.rs
Normal file
216
interpreter/src/lang/vocabulary/sorters/radio_sorter.rs
Normal 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)
|
||||
}
|
|
@ -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};
|
||||
}
|
||||
|
|
|
@ -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<f64, RuntimeMsg>;
|
||||
|
||||
fn get_custom_distance(&mut self, from: &Item, to: &Item, compare: MusicAnalyzerDistance) -> Result<f64, RuntimeMsg>;
|
||||
|
||||
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<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> {
|
||||
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<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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -72,7 +72,7 @@ impl<I: std::iter::Iterator<Item = Result<Item, InterpreterError>>> 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(
|
||||
|
|
Loading…
Reference in a new issue