mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 11:47:25 +01:00
program: Penalize recently played composers and instruments
This commit is contained in:
parent
e5e41619f2
commit
653d5cd629
3 changed files with 136 additions and 43 deletions
|
|
@ -19,17 +19,17 @@
|
||||||
</key>
|
</key>
|
||||||
<key name="program1" type="s">
|
<key name="program1" type="s">
|
||||||
<!-- Translators: Configuration for the default programs in JSON. Please only translate the values of "title" and "description". -->
|
<!-- Translators: Configuration for the default programs in JSON. Please only translate the values of "title" and "description". -->
|
||||||
<default l10n="messages">'{"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}'</default>
|
<default l10n="messages">'{"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>
|
||||||
<summary>Default settings for program 1</summary>
|
<summary>Default settings for program 1</summary>
|
||||||
</key>
|
</key>
|
||||||
<key name="program2" type="s">
|
<key name="program2" type="s">
|
||||||
<!-- Translators: Configuration for the default programs in JSON. Please only translate the values of "title" and "description". -->
|
<!-- Translators: Configuration for the default programs in JSON. Please only translate the values of "title" and "description". -->
|
||||||
<default l10n="messages">'{"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}'</default>
|
<default l10n="messages">'{"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>
|
||||||
<summary>Default settings for program 2</summary>
|
<summary>Default settings for program 2</summary>
|
||||||
</key>
|
</key>
|
||||||
<key name="program3" type="s">
|
<key name="program3" type="s">
|
||||||
<!-- Translators: Configuration for the default programs in JSON. Please only translate the values of "title" and "description". -->
|
<!-- Translators: Configuration for the default programs in JSON. Please only translate the values of "title" and "description". -->
|
||||||
<default l10n="messages">'{"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}'</default>
|
<default l10n="messages">'{"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>
|
||||||
<summary>Default settings for program 3</summary>
|
<summary>Default settings for program 3</summary>
|
||||||
</key>
|
</key>
|
||||||
</schema>
|
</schema>
|
||||||
|
|
|
||||||
101
src/library.rs
101
src/library.rs
|
|
@ -532,8 +532,8 @@ impl Library {
|
||||||
let mut query = recordings::table
|
let mut query = recordings::table
|
||||||
.inner_join(
|
.inner_join(
|
||||||
works::table
|
works::table
|
||||||
.left_join(work_persons::table)
|
.left_join(work_persons::table.inner_join(persons::table))
|
||||||
.left_join(work_instruments::table),
|
.left_join(work_instruments::table.inner_join(instruments::table)),
|
||||||
)
|
)
|
||||||
.left_join(recording_persons::table)
|
.left_join(recording_persons::table)
|
||||||
.left_join(
|
.left_join(
|
||||||
|
|
@ -576,7 +576,6 @@ impl Library {
|
||||||
query = query.filter(album_recordings::album_id.eq(album_id));
|
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:
|
// 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
|
// - 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
|
// - weighted by the average of two scores between 0.0 and 1.0 based on
|
||||||
|
|
@ -588,30 +587,71 @@ impl Library {
|
||||||
// a user defined constant to determine the bias.
|
// a user defined constant to determine the bias.
|
||||||
query = query.order(
|
query = query.order(
|
||||||
diesel::dsl::sql::<sql_types::Untyped>("( \
|
diesel::dsl::sql::<sql_types::Untyped>("( \
|
||||||
WITH \
|
WITH global_bounds AS (
|
||||||
global_bounds AS ( \
|
SELECT MIN(UNIXEPOCH(last_played_at)) AS min_last_played_at,
|
||||||
SELECT \
|
NULLIF(
|
||||||
MIN(UNIXEPOCH(last_played_at)) AS min_last_played_at, \
|
MAX(UNIXEPOCH(last_played_at)) - MIN(UNIXEPOCH(last_played_at)),
|
||||||
NULLIF(MAX(UNIXEPOCH(last_played_at)) - MIN(UNIXEPOCH(last_played_at)), 0.0) AS last_played_at_range, \
|
0.0
|
||||||
MIN(UNIXEPOCH(created_at)) AS min_created_at, \
|
) AS last_played_at_range,
|
||||||
NULLIF(MAX(UNIXEPOCH(created_at)) - MIN(UNIXEPOCH(created_at)), 0.0) AS created_at_range \
|
MIN(UNIXEPOCH(created_at)) AS min_created_at,
|
||||||
FROM recordings \
|
NULLIF(
|
||||||
), \
|
MAX(UNIXEPOCH(created_at)) - MIN(UNIXEPOCH(created_at)),
|
||||||
normalized AS ( \
|
0.0
|
||||||
SELECT \
|
) AS created_at_range
|
||||||
IFNULL(1.0 - (UNIXEPOCH(recordings.last_played_at) - min_last_played_at) * 1.0 / last_played_at_range, 1.0) AS least_recently_played, \
|
FROM recordings
|
||||||
IFNULL((UNIXEPOCH(recordings.created_at) - min_created_at) * 1.0 / created_at_range, 1.0) AS recently_created \
|
),
|
||||||
FROM global_bounds \
|
normalized AS (
|
||||||
) \
|
SELECT IFNULL(
|
||||||
SELECT (RANDOM() / 9223372036854775808.0 + 1.0) / 2.0 * (EXP(10.0 * ")
|
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::<sql_types::Double, _>(program.prefer_least_recently_played())
|
.bind::<sql_types::Double, _>(program.prefer_least_recently_played())
|
||||||
.sql(" * (least_recently_played - 1.0)) + EXP(10.0 * ")
|
.sql(" * (least_recently_played - 1.0)) + EXP(10.0 * ")
|
||||||
.bind::<sql_types::Double, _>(program.prefer_recently_added())
|
.bind::<sql_types::Double, _>(program.prefer_recently_added())
|
||||||
.sql(" * (recently_created - 1.0))) / 2.0 FROM normalized) DESC")
|
.sql(" * (recently_created - 1.0))
|
||||||
|
) / 2.0,
|
||||||
|
FIRST_VALUE(
|
||||||
|
MIN(
|
||||||
|
IFNULL(
|
||||||
|
(
|
||||||
|
UNIXEPOCH('now', 'localtime') - UNIXEPOCH(instruments.last_played_at)
|
||||||
|
) * 1.0 / ")
|
||||||
|
.bind::<sql_types::Integer, _>(program.avoid_repeated_instruments_seconds())
|
||||||
|
.sql(",
|
||||||
|
1.0
|
||||||
|
),
|
||||||
|
IFNULL(
|
||||||
|
(
|
||||||
|
UNIXEPOCH('now', 'localtime') - UNIXEPOCH(persons.last_played_at)
|
||||||
|
) * 1.0 / ").bind::<sql_types::Integer, _>(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")
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
query = query.order(random());
|
|
||||||
}
|
|
||||||
|
|
||||||
let row = query
|
let row = query
|
||||||
.select(tables::Recording::as_select())
|
.select(tables::Recording::as_select())
|
||||||
|
|
@ -668,6 +708,21 @@ impl Library {
|
||||||
.set(works::last_played_at.eq(now))
|
.set(works::last_played_at.eq(now))
|
||||||
.execute(connection)?;
|
.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)
|
diesel::update(persons::table)
|
||||||
.filter(
|
.filter(
|
||||||
exists(
|
exists(
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ mod imp {
|
||||||
|
|
||||||
#[derive(Properties, Serialize, Deserialize, Default)]
|
#[derive(Properties, Serialize, Deserialize, Default)]
|
||||||
#[properties(wrapper_type = super::Program)]
|
#[properties(wrapper_type = super::Program)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct Program {
|
pub struct Program {
|
||||||
#[property(get, set)]
|
#[property(get, set)]
|
||||||
pub title: RefCell<Option<String>>,
|
pub title: RefCell<Option<String>>,
|
||||||
|
|
@ -45,6 +46,12 @@ mod imp {
|
||||||
#[property(get, set)]
|
#[property(get, set)]
|
||||||
pub prefer_least_recently_played: Cell<f64>,
|
pub prefer_least_recently_played: Cell<f64>,
|
||||||
|
|
||||||
|
#[property(get, set)]
|
||||||
|
pub avoid_repeated_composers_seconds: Cell<i32>,
|
||||||
|
|
||||||
|
#[property(get, set)]
|
||||||
|
pub avoid_repeated_instruments_seconds: Cell<i32>,
|
||||||
|
|
||||||
#[property(get, set)]
|
#[property(get, set)]
|
||||||
pub play_full_recordings: Cell<bool>,
|
pub play_full_recordings: Cell<bool>,
|
||||||
}
|
}
|
||||||
|
|
@ -74,12 +81,35 @@ impl Program {
|
||||||
|
|
||||||
pub fn from_query(query: LibraryQuery) -> Self {
|
pub fn from_query(query: LibraryQuery) -> Self {
|
||||||
glib::Object::builder()
|
glib::Object::builder()
|
||||||
.property("composer-id", query.composer.map(|p| p.person_id))
|
.property(
|
||||||
|
"composer-id",
|
||||||
|
query.composer.as_ref().map(|p| p.person_id.clone()),
|
||||||
|
)
|
||||||
.property("performer-id", query.performer.map(|p| p.person_id))
|
.property("performer-id", query.performer.map(|p| p.person_id))
|
||||||
.property("ensemble-id", query.ensemble.map(|e| e.ensemble_id))
|
.property("ensemble-id", query.ensemble.map(|e| e.ensemble_id))
|
||||||
.property("instrument-id", query.instrument.map(|i| i.instrument_id))
|
.property(
|
||||||
|
"instrument-id",
|
||||||
|
query.instrument.as_ref().map(|i| i.instrument_id.clone()),
|
||||||
|
)
|
||||||
|
.property("work-id", query.work.as_ref().map(|w| w.work_id.clone()))
|
||||||
.property("prefer-recently-added", 0.0)
|
.property("prefer-recently-added", 0.0)
|
||||||
.property("prefer-least-recently-played", 0.5)
|
.property("prefer-least-recently-played", 0.5)
|
||||||
|
.property(
|
||||||
|
"avoid-repeated-composers-seconds",
|
||||||
|
if query.composer.is_none() && query.work.is_none() {
|
||||||
|
3600
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.property(
|
||||||
|
"avoid-repeated-instruments-seconds",
|
||||||
|
if query.instrument.is_none() && query.work.is_none() {
|
||||||
|
3600
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
},
|
||||||
|
)
|
||||||
.property("play-full-recordings", true)
|
.property("play-full-recordings", true)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
@ -96,6 +126,14 @@ impl Program {
|
||||||
"prefer-least-recently-played",
|
"prefer-least-recently-played",
|
||||||
data.prefer_least_recently_played.get(),
|
data.prefer_least_recently_played.get(),
|
||||||
)
|
)
|
||||||
|
.property(
|
||||||
|
"avoid-repeated-composers-seconds",
|
||||||
|
data.avoid_repeated_composers_seconds.get(),
|
||||||
|
)
|
||||||
|
.property(
|
||||||
|
"avoid-repeated-instruments-seconds",
|
||||||
|
data.avoid_repeated_instruments_seconds.get(),
|
||||||
|
)
|
||||||
.property("play-full-recordings", data.play_full_recordings.get())
|
.property("play-full-recordings", data.play_full_recordings.get())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue