mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-25 20:37:24 +02: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 name="program1" type="s">
|
||||
<!-- 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>
|
||||
</key>
|
||||
<key name="program2" type="s">
|
||||
<!-- 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>
|
||||
</key>
|
||||
<key name="program3" type="s">
|
||||
<!-- 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>
|
||||
</key>
|
||||
</schema>
|
||||
|
|
|
|||
131
src/library.rs
131
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::<sql_types::Untyped>("( \
|
||||
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::<sql_types::Double, _>(program.prefer_least_recently_played())
|
||||
.sql(" * (least_recently_played - 1.0)) + EXP(10.0 * ")
|
||||
.bind::<sql_types::Double, _>(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::<sql_types::Untyped>("( \
|
||||
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::<sql_types::Double, _>(program.prefer_least_recently_played())
|
||||
.sql(" * (least_recently_played - 1.0)) + EXP(10.0 * ")
|
||||
.bind::<sql_types::Double, _>(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::<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")
|
||||
);
|
||||
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -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<Option<String>>,
|
||||
|
|
@ -45,6 +46,12 @@ mod imp {
|
|||
#[property(get, set)]
|
||||
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)]
|
||||
pub play_full_recordings: Cell<bool>,
|
||||
}
|
||||
|
|
@ -74,12 +81,35 @@ impl Program {
|
|||
|
||||
pub fn from_query(query: LibraryQuery) -> Self {
|
||||
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("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-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)
|
||||
.build()
|
||||
}
|
||||
|
|
@ -96,6 +126,14 @@ impl Program {
|
|||
"prefer-least-recently-played",
|
||||
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())
|
||||
.build();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue