diff --git a/data/de.johrpan.Musicus.gschema.xml.in b/data/de.johrpan.Musicus.gschema.xml.in
index 4d36e4e..a8bb49a 100644
--- a/data/de.johrpan.Musicus.gschema.xml.in
+++ b/data/de.johrpan.Musicus.gschema.xml.in
@@ -19,17 +19,17 @@
- '{"title":"Just play some music","description":"Randomly select some music. Customize programs using the button in the top right.","design":"Program1","prefer_recently_added":0.0,"prefer_least_recently_played":0.1,"play_full_recordings":true}'
+ '{"title":"Just play some music","description":"Randomly select some music. Customize programs using the button in the top right.","design":"Program1","prefer_recently_added":0.0,"prefer_least_recently_played":0.1,"avoid_repeated_composers_seconds":3600,"avoid_repeated_instruments_seconds":3600,"play_full_recordings":true}'Default settings for program 1
- '{"title":"What\'s new?","description":"Recordings that you recently added to your music library.","design":"Program2","prefer_recently_added":1.0,"prefer_least_recently_played":0.0,"play_full_recordings":true}'
+ '{"title":"What\'s new?","description":"Recordings that you recently added to your music library.","design":"Program2","prefer_recently_added":1.0,"prefer_least_recently_played":0.0,"avoid_repeated_composers_seconds":3600,"avoid_repeated_instruments_seconds":3600,"play_full_recordings":true}'Default settings for program 2
- '{"title":"A long time ago","description":"Works that you haven\'t listened to for a long time.","design":"Program3","prefer_recently_added":0.0,"prefer_least_recently_played":1.0,"play_full_recordings":true}'
+ '{"title":"A long time ago","description":"Works that you haven\'t listened to for a long time.","design":"Program3","prefer_recently_added":0.0,"prefer_least_recently_played":1.0,"avoid_repeated_composers_seconds":3600,"avoid_repeated_instruments_seconds":3600,"play_full_recordings":true}'Default settings for program 3
diff --git a/src/library.rs b/src/library.rs
index 393dc87..a7f511e 100644
--- a/src/library.rs
+++ b/src/library.rs
@@ -532,8 +532,8 @@ impl Library {
let mut query = recordings::table
.inner_join(
works::table
- .left_join(work_persons::table)
- .left_join(work_instruments::table),
+ .left_join(work_persons::table.inner_join(persons::table))
+ .left_join(work_instruments::table.inner_join(instruments::table)),
)
.left_join(recording_persons::table)
.left_join(
@@ -576,42 +576,82 @@ impl Library {
query = query.filter(album_recordings::album_id.eq(album_id));
}
- if program.prefer_least_recently_played() > 0.0 || program.prefer_recently_added() > 0.0 {
- // Orders recordings using a dynamically calculated priority score that includes:
- // - a random base value between 0.0 and 1.0 giving equal probability to each recording
- // - weighted by the average of two scores between 0.0 and 1.0 based on
- // 1. how long ago the last playback is
- // 2. how recently the recording was added to the library
- // Both scores are individually modified based on the following formula:
- // e^(10 * a * (score - 1))
- // This assigns a new score between 0.0 and 1.0 that favors higher scores with "a" being
- // a user defined constant to determine the bias.
- query = query.order(
- diesel::dsl::sql::("( \
- WITH \
- global_bounds AS ( \
- SELECT \
- MIN(UNIXEPOCH(last_played_at)) AS min_last_played_at, \
- NULLIF(MAX(UNIXEPOCH(last_played_at)) - MIN(UNIXEPOCH(last_played_at)), 0.0) AS last_played_at_range, \
- MIN(UNIXEPOCH(created_at)) AS min_created_at, \
- NULLIF(MAX(UNIXEPOCH(created_at)) - MIN(UNIXEPOCH(created_at)), 0.0) AS created_at_range \
- FROM recordings \
- ), \
- normalized AS ( \
- SELECT \
- IFNULL(1.0 - (UNIXEPOCH(recordings.last_played_at) - min_last_played_at) * 1.0 / last_played_at_range, 1.0) AS least_recently_played, \
- IFNULL((UNIXEPOCH(recordings.created_at) - min_created_at) * 1.0 / created_at_range, 1.0) AS recently_created \
- FROM global_bounds \
- ) \
- SELECT (RANDOM() / 9223372036854775808.0 + 1.0) / 2.0 * (EXP(10.0 * ")
- .bind::(program.prefer_least_recently_played())
- .sql(" * (least_recently_played - 1.0)) + EXP(10.0 * ")
- .bind::(program.prefer_recently_added())
- .sql(" * (recently_created - 1.0))) / 2.0 FROM normalized) DESC")
- );
- } else {
- query = query.order(random());
- }
+ // Orders recordings using a dynamically calculated priority score that includes:
+ // - a random base value between 0.0 and 1.0 giving equal probability to each recording
+ // - weighted by the average of two scores between 0.0 and 1.0 based on
+ // 1. how long ago the last playback is
+ // 2. how recently the recording was added to the library
+ // Both scores are individually modified based on the following formula:
+ // e^(10 * a * (score - 1))
+ // This assigns a new score between 0.0 and 1.0 that favors higher scores with "a" being
+ // a user defined constant to determine the bias.
+ query = query.order(
+ diesel::dsl::sql::("( \
+ WITH global_bounds AS (
+ SELECT MIN(UNIXEPOCH(last_played_at)) AS min_last_played_at,
+ NULLIF(
+ MAX(UNIXEPOCH(last_played_at)) - MIN(UNIXEPOCH(last_played_at)),
+ 0.0
+ ) AS last_played_at_range,
+ MIN(UNIXEPOCH(created_at)) AS min_created_at,
+ NULLIF(
+ MAX(UNIXEPOCH(created_at)) - MIN(UNIXEPOCH(created_at)),
+ 0.0
+ ) AS created_at_range
+ FROM recordings
+ ),
+ normalized AS (
+ SELECT IFNULL(
+ 1.0 - (
+ UNIXEPOCH(recordings.last_played_at) - min_last_played_at
+ ) * 1.0 / last_played_at_range,
+ 1.0
+ ) AS least_recently_played,
+ IFNULL(
+ (
+ UNIXEPOCH(recordings.created_at) - min_created_at
+ ) * 1.0 / created_at_range,
+ 1.0
+ ) AS recently_created
+ FROM global_bounds
+ )
+ SELECT (RANDOM() / 9223372036854775808.0 + 1.0) / 2.0 * MIN(
+ (
+ EXP(10.0 * ")
+ .bind::(program.prefer_least_recently_played())
+ .sql(" * (least_recently_played - 1.0)) + EXP(10.0 * ")
+ .bind::(program.prefer_recently_added())
+ .sql(" * (recently_created - 1.0))
+ ) / 2.0,
+ FIRST_VALUE(
+ MIN(
+ IFNULL(
+ (
+ UNIXEPOCH('now', 'localtime') - UNIXEPOCH(instruments.last_played_at)
+ ) * 1.0 / ")
+ .bind::(program.avoid_repeated_instruments_seconds())
+ .sql(",
+ 1.0
+ ),
+ IFNULL(
+ (
+ UNIXEPOCH('now', 'localtime') - UNIXEPOCH(persons.last_played_at)
+ ) * 1.0 / ").bind::(program.avoid_repeated_composers_seconds()).sql(",
+ 1.0
+ ),
+ 1.0
+ )
+ ) OVER (
+ PARTITION BY recordings.recording_id
+ ORDER BY MAX(
+ IFNULL(instruments.last_played_at, 0),
+ IFNULL(persons.last_played_at, 0)
+ )
+ )
+ )
+ FROM normalized
+ ) DESC")
+ );
let row = query
.select(tables::Recording::as_select())
@@ -668,6 +708,21 @@ impl Library {
.set(works::last_played_at.eq(now))
.execute(connection)?;
+ diesel::update(instruments::table)
+ .filter(exists(
+ work_instruments::table
+ .inner_join(
+ works::table.inner_join(recordings::table.inner_join(tracks::table)),
+ )
+ .filter(
+ tracks::track_id
+ .eq(track_id)
+ .and(work_instruments::instrument_id.eq(instruments::instrument_id)),
+ ),
+ ))
+ .set(instruments::last_played_at.eq(now))
+ .execute(connection)?;
+
diesel::update(persons::table)
.filter(
exists(
diff --git a/src/program.rs b/src/program.rs
index fdb91b9..804e71c 100644
--- a/src/program.rs
+++ b/src/program.rs
@@ -11,6 +11,7 @@ mod imp {
#[derive(Properties, Serialize, Deserialize, Default)]
#[properties(wrapper_type = super::Program)]
+ #[serde(default)]
pub struct Program {
#[property(get, set)]
pub title: RefCell