Add bliss_next sorting algorithm, rename bliss sort to bliss_first
This commit is contained in:
parent
bd3d1465df
commit
bde7ead3b6
11 changed files with 269 additions and 16 deletions
|
@ -137,9 +137,13 @@ Operations to sort the items in an iterable: iterable~(sorter) OR iterable.sort(
|
|||
|
||||
Sort by a MpsItem field. Valid field names change depending on what information is available when the MpsItem is populated, but usually title, artist, album, genre, track, filename are valid fields. Items with a missing/incomparable fields will be sorted to the end.
|
||||
|
||||
#### advanced bliss -- e.g. iterable~(advanced bliss)
|
||||
#### advanced bliss_first -- e.g. iterable~(advanced bliss_first)
|
||||
|
||||
Sort by the distance (similarity) between songs. Songs which are more similar (lower distance) to the first song in the iterator will be placed closer to the first song, while less similar songs will be sorted to the end. This uses the [bliss music analyser](https://github.com/polochon-street/bliss-rs), which is a very slow operation and can cause music playback interruptions for large iterators. This requires the `advanced` feature to be enabled (without the feature enabled this is still valid syntax but doesn't change the order).
|
||||
Sort by the distance (similarity) from the first song in the iterator. Songs which are more similar (lower distance) to the first song in the iterator will be placed closer to the first song, while less similar songs will be sorted to the end. This uses the [bliss music analyser](https://github.com/polochon-street/bliss-rs), which is a very slow operation and can cause music playback interruptions for large iterators. This requires the `advanced` feature to be enabled (without the feature enabled this is still valid syntax but doesn't change the order).
|
||||
|
||||
#### advanced bliss_next -- e.g. iterable~(advanced bliss_next)
|
||||
|
||||
Sort by the distance (similarity) between the last played song in the iterator. Similar to bliss_first. The song which is the most similar (lower distance) to the previous song in the iterator will be placed next to it, then the process is repeated. This uses the [bliss music analyser](https://github.com/polochon-street/bliss-rs), which is a very slow operation and can cause music playback interruptions for large iterators. This requires the `advanced` feature to be enabled (without the feature enabled this is still valid syntax but doesn't change the order).
|
||||
|
||||
|
||||
License: LGPL-2.1-only OR GPL-2.0-or-later
|
||||
|
|
|
@ -167,6 +167,7 @@ pub(crate) fn standard_vocab(vocabulary: &mut MpsLanguageDictionary) {
|
|||
.add(crate::lang::vocabulary::sorters::empty_sort())
|
||||
.add(crate::lang::vocabulary::sorters::field_sort())
|
||||
.add(crate::lang::vocabulary::sorters::bliss_sort())
|
||||
.add(crate::lang::vocabulary::sorters::bliss_next_sort())
|
||||
// functions and misc
|
||||
.add(crate::lang::vocabulary::sql_function_factory())
|
||||
.add(crate::lang::vocabulary::simple_sql_function_factory())
|
||||
|
|
|
@ -16,7 +16,7 @@ impl MpsItem {
|
|||
Self::default()
|
||||
}
|
||||
|
||||
pub fn field(&self, name: &str) -> Option<&MpsTypePrimitive> {
|
||||
pub fn field(&self, name: &str) -> Option<&'_ MpsTypePrimitive> {
|
||||
self.fields.get(name)
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@ pub trait MpsSorter: Clone + Debug + Display {
|
|||
item_buf: &mut VecDeque<MpsIteratorItem>,
|
||||
op: &'a mut OpGetter,
|
||||
) -> Result<(), RuntimeError>;
|
||||
|
||||
fn reset(&mut self) {}
|
||||
}
|
||||
|
||||
pub trait MpsSorterFactory<S: MpsSorter + 'static> {
|
||||
|
@ -75,6 +77,7 @@ impl<S: MpsSorter + 'static> MpsOp for MpsSortStatement<S> {
|
|||
|
||||
fn reset(&mut self) -> Result<(), RuntimeError> {
|
||||
self.item_cache.clear();
|
||||
self.orderer.reset();
|
||||
self.iterable.try_real()?.reset()
|
||||
}
|
||||
}
|
||||
|
|
227
mps-interpreter/src/lang/vocabulary/sorters/bliss_next_sorter.rs
Normal file
227
mps-interpreter/src/lang/vocabulary/sorters/bliss_next_sorter.rs
Normal file
|
@ -0,0 +1,227 @@
|
|||
use std::collections::VecDeque;
|
||||
#[cfg(feature = "bliss-audio")]
|
||||
use std::fmt::{Debug, Display, Error, Formatter};
|
||||
#[cfg(feature = "bliss-audio")]
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
|
||||
#[cfg(feature = "bliss-audio")]
|
||||
use bliss_audio::Song;
|
||||
|
||||
use crate::lang::utility::{assert_name, check_name};
|
||||
use crate::lang::SyntaxError;
|
||||
#[cfg(feature = "bliss-audio")]
|
||||
use crate::lang::{MpsIteratorItem, MpsOp, MpsSorter, MpsTypePrimitive, RuntimeError};
|
||||
use crate::lang::{MpsLanguageDictionary, MpsSortStatementFactory, MpsSorterFactory};
|
||||
#[cfg(feature = "bliss-audio")]
|
||||
use crate::processing::OpGetter;
|
||||
use crate::tokens::MpsToken;
|
||||
#[cfg(feature = "bliss-audio")]
|
||||
use crate::MpsItem;
|
||||
|
||||
#[cfg(feature = "bliss-audio")]
|
||||
#[derive(Debug)]
|
||||
pub struct BlissNextSorter {
|
||||
up_to: usize,
|
||||
rx: Option<Receiver<Option<Result<MpsItem, bliss_audio::BlissError>>>>,
|
||||
algorithm_done: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "bliss-audio")]
|
||||
impl BlissNextSorter {
|
||||
fn get_maybe(&mut self, op: &mut OpGetter) -> Option<MpsIteratorItem> {
|
||||
if self.algorithm_done {
|
||||
None
|
||||
} else {
|
||||
if let Ok(Some(item)) = self.rx.as_ref().unwrap().recv() {
|
||||
Some(item.map_err(|e| bliss_err(e, op)))
|
||||
} else {
|
||||
self.algorithm_done = true;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn algorithm(mut items: VecDeque<MpsItem>, results: Sender<Option<Result<MpsItem, bliss_audio::BlissError>>>) {
|
||||
let mut song_cache: Option<(Song, String)> = None;
|
||||
let items_len = items.len();
|
||||
for i in 0..items_len {
|
||||
let item = items.pop_front().unwrap();
|
||||
if let Some(MpsTypePrimitive::String(path)) = item.field("filename") {
|
||||
if let Err(_) = results.send(Some(Ok(item.clone()))) {break;}
|
||||
if i+2 < items_len {
|
||||
let target_song = if let Some((_, ref cached_filename)) = song_cache {
|
||||
if cached_filename == path {
|
||||
Ok(song_cache.take().unwrap().0)
|
||||
} else {
|
||||
Song::new(path)
|
||||
}
|
||||
} else {
|
||||
Song::new(path)
|
||||
};
|
||||
let target_song = match target_song {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
results.send(Some(Err(e))).unwrap_or(());
|
||||
break;
|
||||
}
|
||||
};
|
||||
match Self::find_best(&items, target_song) {
|
||||
Err(e) => {
|
||||
results.send(Some(Err(e))).unwrap_or(());
|
||||
break;
|
||||
},
|
||||
Ok((next_song, index)) => {
|
||||
if let Some(next_song) = next_song {
|
||||
if index != 0 {
|
||||
items.swap(0, index);
|
||||
}
|
||||
song_cache = Some((next_song, path.to_owned()));
|
||||
} else {break;}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
results.send(None).unwrap_or(());
|
||||
}
|
||||
|
||||
fn find_best(items: &VecDeque<MpsItem>, target: Song) -> Result<(Option<Song>, usize), bliss_audio::BlissError> {
|
||||
let mut best = None;
|
||||
let mut best_index = 0;
|
||||
let mut best_distance = f32::MAX;
|
||||
let (tx, rx) = channel();
|
||||
let mut threads_spawned = 0;
|
||||
for i in 0..items.len() {
|
||||
if let Some(MpsTypePrimitive::String(path)) = items[i].field("filename") {
|
||||
let result_chann = tx.clone();
|
||||
let target_clone = target.clone();
|
||||
let path_clone = path.to_owned();
|
||||
std::thread::spawn(move || {
|
||||
match Song::new(path_clone) {
|
||||
Err(e) => result_chann
|
||||
.send(Err(e))
|
||||
.unwrap_or(()),
|
||||
Ok(song) => result_chann
|
||||
.send(Ok((i, target_clone.distance(&song), song)))
|
||||
.unwrap_or(()),
|
||||
}
|
||||
});
|
||||
threads_spawned += 1;
|
||||
}
|
||||
}
|
||||
for _ in 0..threads_spawned {
|
||||
if let Ok(result) = rx.recv() {
|
||||
let (index, distance, song) = result?;
|
||||
if distance < best_distance {
|
||||
best = Some(song);
|
||||
best_index = index;
|
||||
best_distance = distance;
|
||||
}
|
||||
} else {break;}
|
||||
}
|
||||
Ok((best, best_index))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bliss-audio")]
|
||||
impl std::clone::Clone for BlissNextSorter {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
up_to: self.up_to.clone(),
|
||||
rx: None,
|
||||
algorithm_done: self.algorithm_done,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bliss-audio")]
|
||||
impl Default for BlissNextSorter {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
up_to: usize::MAX,
|
||||
rx: None,
|
||||
algorithm_done: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bliss-audio")]
|
||||
impl MpsSorter for BlissNextSorter {
|
||||
fn sort(
|
||||
&mut self,
|
||||
iterator: &mut dyn MpsOp,
|
||||
item_buf: &mut VecDeque<MpsIteratorItem>,
|
||||
op: &mut OpGetter,
|
||||
) -> Result<(), RuntimeError> {
|
||||
if self.rx.is_none() {
|
||||
// first run
|
||||
let mut items = VecDeque::new();
|
||||
for item in iterator {
|
||||
match item {
|
||||
Ok(item) => items.push_back(item),
|
||||
Err(e) => item_buf.push_back(Err(e)),
|
||||
}
|
||||
if items.len() + item_buf.len() >= self.up_to {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// start algorithm
|
||||
let (tx, rx) = channel();
|
||||
std::thread::spawn(move || Self::algorithm(items, tx));
|
||||
self.rx = Some(rx);
|
||||
}
|
||||
if let Some(item) = self.get_maybe(op) {
|
||||
item_buf.push_back(item);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.algorithm_done = false;
|
||||
self.rx = None;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bliss-audio")]
|
||||
fn bliss_err<D: Display>(error: D, op: &mut OpGetter) -> RuntimeError {
|
||||
RuntimeError {
|
||||
line: 0,
|
||||
op: op(),
|
||||
msg: format!("Bliss error: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "bliss-audio"))]
|
||||
pub type BlissNextSorter = crate::lang::vocabulary::sorters::EmptySorter;
|
||||
|
||||
#[cfg(feature = "bliss-audio")]
|
||||
impl Display for BlissNextSorter {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||
write!(f, "advanced bliss_next")
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BlissNextSorterFactory;
|
||||
|
||||
impl MpsSorterFactory<BlissNextSorter> for BlissNextSorterFactory {
|
||||
fn is_sorter(&self, tokens: &VecDeque<&MpsToken>) -> bool {
|
||||
tokens.len() == 2 && check_name("advanced", &tokens[0]) && check_name("bliss_next", &tokens[1])
|
||||
}
|
||||
|
||||
fn build_sorter(
|
||||
&self,
|
||||
tokens: &mut VecDeque<MpsToken>,
|
||||
_dict: &MpsLanguageDictionary,
|
||||
) -> Result<BlissNextSorter, SyntaxError> {
|
||||
assert_name("advanced", tokens)?;
|
||||
assert_name("bliss_next", tokens)?;
|
||||
Ok(BlissNextSorter::default())
|
||||
}
|
||||
}
|
||||
|
||||
pub type BlissNextSorterStatementFactory = MpsSortStatementFactory<BlissNextSorter, BlissNextSorterFactory>;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn bliss_next_sort() -> BlissNextSorterStatementFactory {
|
||||
BlissNextSorterStatementFactory::new(BlissNextSorterFactory)
|
||||
}
|
|
@ -202,7 +202,7 @@ pub type BlissSorter = crate::lang::vocabulary::sorters::EmptySorter;
|
|||
#[cfg(feature = "bliss-audio")]
|
||||
impl Display for BlissSorter {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||
write!(f, "distance bliss")
|
||||
write!(f, "advanced bliss_first")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,7 +210,7 @@ pub struct BlissSorterFactory;
|
|||
|
||||
impl MpsSorterFactory<BlissSorter> for BlissSorterFactory {
|
||||
fn is_sorter(&self, tokens: &VecDeque<&MpsToken>) -> bool {
|
||||
tokens.len() == 2 && check_name("advanced", &tokens[0]) && check_name("bliss", &tokens[1])
|
||||
tokens.len() == 2 && check_name("advanced", &tokens[0]) && check_name("bliss_first", &tokens[1])
|
||||
}
|
||||
|
||||
fn build_sorter(
|
||||
|
@ -219,7 +219,7 @@ impl MpsSorterFactory<BlissSorter> for BlissSorterFactory {
|
|||
_dict: &MpsLanguageDictionary,
|
||||
) -> Result<BlissSorter, SyntaxError> {
|
||||
assert_name("advanced", tokens)?;
|
||||
assert_name("bliss", tokens)?;
|
||||
assert_name("bliss_first", tokens)?;
|
||||
Ok(BlissSorter::default())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
mod bliss_sorter;
|
||||
mod bliss_next_sorter;
|
||||
mod empty_sorter;
|
||||
mod field_sorter;
|
||||
|
||||
pub use bliss_sorter::{bliss_sort, BlissSorter, BlissSorterFactory, BlissSorterStatementFactory};
|
||||
pub use bliss_next_sorter::{bliss_next_sort, BlissNextSorter, BlissNextSorterFactory, BlissNextSorterStatementFactory};
|
||||
pub use empty_sorter::{empty_sort, EmptySorter, EmptySorterFactory, EmptySorterStatementFactory};
|
||||
pub use field_sorter::{field_sort, FieldSorter, FieldSorterFactory, FieldSorterStatementFactory};
|
||||
|
|
|
@ -135,9 +135,13 @@
|
|||
//!
|
||||
//! Sort by a MpsItem field. Valid field names change depending on what information is available when the MpsItem is populated, but usually title, artist, album, genre, track, filename are valid fields. Items with a missing/incomparable fields will be sorted to the end.
|
||||
//!
|
||||
//! ### advanced bliss -- e.g. iterable~(advanced bliss)
|
||||
//! ### advanced bliss_first -- e.g. iterable~(advanced bliss_first)
|
||||
//!
|
||||
//! Sort by the distance (similarity) between songs. Songs which are more similar (lower distance) to the first song in the iterator will be placed closer to the first song, while less similar songs will be sorted to the end. This uses the [bliss music analyser](https://github.com/polochon-street/bliss-rs), which is a very slow operation and can cause music playback interruptions for large iterators. This requires the `advanced` feature to be enabled (without the feature enabled this is still valid syntax but doesn't change the order).
|
||||
//! Sort by the distance (similarity) from the first song in the iterator. Songs which are more similar (lower distance) to the first song in the iterator will be placed closer to the first song, while less similar songs will be sorted to the end. This uses the [bliss music analyser](https://github.com/polochon-street/bliss-rs), which is a very slow operation and can cause music playback interruptions for large iterators. This requires the `advanced` feature to be enabled (without the feature enabled this is still valid syntax but doesn't change the order).
|
||||
//!
|
||||
//! ### advanced bliss_next -- e.g. iterable~(advanced bliss_next)
|
||||
//!
|
||||
//! Sort by the distance (similarity) between the last played song in the iterator. Similar to bliss_first. The song which is the most similar (lower distance) to the previous song in the iterator will be placed next to it, then the process is repeated. This uses the [bliss music analyser](https://github.com/polochon-street/bliss-rs), which is a very slow operation and can cause music playback interruptions for large iterators. This requires the `advanced` feature to be enabled (without the feature enabled this is still valid syntax but doesn't change the order).
|
||||
//!
|
||||
|
||||
mod context;
|
||||
|
|
|
@ -24,7 +24,7 @@ impl Display for MpsType {
|
|||
}
|
||||
|
||||
pub trait MpsVariableStorer: Debug {
|
||||
fn get(&self, name: &str, op: &mut OpGetter) -> Result<&MpsType, RuntimeError> {
|
||||
fn get(&self, name: &str, op: &mut OpGetter) -> Result<&'_ MpsType, RuntimeError> {
|
||||
match self.get_opt(name) {
|
||||
Some(item) => Ok(item),
|
||||
None => Err(RuntimeError {
|
||||
|
@ -35,9 +35,9 @@ pub trait MpsVariableStorer: Debug {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_opt(&self, name: &str) -> Option<&MpsType>;
|
||||
fn get_opt(&self, name: &str) -> Option<&'_ MpsType>;
|
||||
|
||||
fn get_mut(&mut self, name: &str, op: &mut OpGetter) -> Result<&mut MpsType, RuntimeError> {
|
||||
fn get_mut(&mut self, name: &str, op: &mut OpGetter) -> Result<&'_ mut MpsType, RuntimeError> {
|
||||
match self.get_mut_opt(name) {
|
||||
Some(item) => Ok(item),
|
||||
None => Err(RuntimeError {
|
||||
|
@ -48,7 +48,7 @@ pub trait MpsVariableStorer: Debug {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_mut_opt(&mut self, name: &str) -> Option<&mut MpsType>;
|
||||
fn get_mut_opt(&mut self, name: &str) -> Option<&'_ mut MpsType>;
|
||||
|
||||
fn assign(&mut self, name: &str, value: MpsType, op: &mut OpGetter)
|
||||
-> Result<(), RuntimeError>;
|
||||
|
|
|
@ -360,9 +360,18 @@ fn execute_fieldsort_line() -> Result<(), Box<dyn MpsLanguageError>> {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn execute_blisssort_line() -> Result<(), Box<dyn MpsLanguageError>> {
|
||||
fn execute_blissfirstsort_line() -> Result<(), Box<dyn MpsLanguageError>> {
|
||||
execute_single_line(
|
||||
"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`)~(advanced bliss)",
|
||||
"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`)~(advanced bliss_first)",
|
||||
false,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute_blissnextsort_line() -> Result<(), Box<dyn MpsLanguageError>> {
|
||||
execute_single_line(
|
||||
"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`)~(advanced bliss_next)",
|
||||
false,
|
||||
true,
|
||||
)
|
||||
|
|
|
@ -74,5 +74,8 @@ Operations to sort the items in an iterable: iterable~(sorter) OR iterable.sort(
|
|||
field -- e.g. iterable~(filename)
|
||||
Sort by a MpsItem field. Valid field names change depending on what information is available when the MpsItem is populated, but usually title, artist, album, genre, track, filename are valid fields. Items with a missing/incomparable fields will be sorted to the end.
|
||||
|
||||
advanced bliss -- e.g. iterable~(advanced bliss)
|
||||
Sort by the distance (similarity) between songs. Songs which are more similar (lower distance) to the first song in the iterator will be placed closer to the first song, while less similar songs will be sorted to the end. This uses the bliss music analyser, which is a very slow operation and can cause music playback interruptions for large iterators. Requires `advanced` mps-interpreter feature.";
|
||||
advanced bliss_first -- e.g. iterable~(advanced bliss_first)
|
||||
Sort by the distance (similarity) from the first song in the iterator. Songs which are more similar (lower distance) to the first song in the iterator will be placed closer to the first song, while less similar songs will be sorted to the end. This uses the bliss music analyser, which is a very slow operation and can cause music playback interruptions for large iterators. Requires `advanced` mps-interpreter feature.
|
||||
|
||||
advanced bliss_next -- e.g. iterable~(advanced bliss_next)
|
||||
Sort by the distance (similarity) between the last played song in the iterator. Similar to bliss_first. Songs which are more similar (lower distance) to the first song in the iterator will be placed closer to the first song, while less similar songs will be sorted to the end. This uses the bliss music analyser, which is a very slow operation and can cause music playback interruptions for large iterators. Requires `advanced` mps-interpreter feature.";
|
||||
|
|
Loading…
Reference in a new issue