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",
|
"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"
|
||||||
|
|
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::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()
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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> {
|
||||||
|
if tokens[0].is_tilde() {
|
||||||
|
assert_token_raw(Token::Tilde, tokens)?;
|
||||||
|
} else {
|
||||||
assert_name("advanced", tokens)?;
|
assert_name("advanced", tokens)?;
|
||||||
|
}
|
||||||
assert_name("bliss_next", tokens)?;
|
assert_name("bliss_next", tokens)?;
|
||||||
Ok(BlissNextSorter::default())
|
Ok(BlissNextSorter::default())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> {
|
||||||
|
if tokens[0].is_tilde() {
|
||||||
|
assert_token_raw(Token::Tilde, tokens)?;
|
||||||
|
} else {
|
||||||
assert_name("advanced", tokens)?;
|
assert_name("advanced", tokens)?;
|
||||||
|
}
|
||||||
assert_name("bliss_first", tokens)?;
|
assert_name("bliss_first", tokens)?;
|
||||||
Ok(BlissSorter::default())
|
Ok(BlissSorter::default())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
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")]
|
#[cfg(feature = "advanced")]
|
||||||
pub mod 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")]
|
#[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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
Loading…
Reference in a new issue