Skip to content

Commit

Permalink
Move the balanced shuffle to the general Performance settings, and us…
Browse files Browse the repository at this point in the history
…e it to shuffle the current playlist, too.
  • Loading branch information
mherger committed May 3, 2021
1 parent 54b31f4 commit 6c66e23
Show file tree
Hide file tree
Showing 10 changed files with 88 additions and 139 deletions.
2 changes: 1 addition & 1 deletion Changelog8.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ <h2><a name="v8.2.0" id="v8.2.0"></a>Version 8.2.0</h2>
<li>New Features:</li>
<ul>
<li>Try to group online artists with local artists by ignoring slightly different spelling (eg. "The Beatles" vs. "Beatles", "Amy Macdonald" vs. "Amy MacDonald").</li>
<li><a href="https://github.com/Logitech/slimserver/issues/510">#510</a> - Add (optional) "balanced" shuffling method to Random Play, which is less random, but hopefully more pleasing to the listener.</li>
<li><a href="https://github.com/Logitech/slimserver/issues/510">#510</a> - Add (optional) "balanced" track shuffling method, which is less random, but hopefully more pleasing to the listener.</li>
<li><a href="https://github.com/Logitech/slimserver/pull/537">#537</a> - Add audio option to combine channels to build a mono signal (whether player is synchronized or not).</li>
<li><a href="https://github.com/Logitech/slimserver/pull/538">#538</a> - Add Balance setting for players which support it (thanks philippe44!).</li>
<li>Enable basic track statistics (play count, last played, ratings) for online tracks imported into the library.</li>
Expand Down
7 changes: 7 additions & 0 deletions HTML/EN/settings/server/performance.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@
</select>
[% END %]

[% WRAPPER setting title="SETUP_SHUFFLE_METHOD" desc="" %]
<select class="stdedit" name="pref_useBalancedShuffle" id="useBalancedShuffle">
<option [% IF !prefs.pref_useBalancedShuffle %]selected[% END %] value="0">[% "SETUP_USE_FASTER_SHUFFLE" | string %]</option>
<option [% IF prefs.pref_useBalancedShuffle %]selected[% END %] value="1">[% "SETUP_USE_BALANCED_SHUFFLE" | string %]</option>
</select>
[% END %]

[% WRAPPER setting title="SETUP_DISABLESTATISTICS" desc="SETUP_DISABLESTATISTICS_DESC" %]
<select class="stdedit" name="pref_disableStatistics" id="disableStatistics">

Expand Down
64 changes: 63 additions & 1 deletion Slim/Player/Playlist.pm
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,50 @@ sub fischer_yates_shuffle {
}
}

# balancedShuffle is inspired by https://engineering.atspotify.com/2014/02/28/how-to-shuffle-songs/
# It tries to not shuffle really randomly, which often is considered "unnatural". But it distributes
# a group's items (eg. an artist's tracks) evenly along the full list.
# $list must be a listref of tupels, where the first element of the tuple would be the item's key
# (eg. track ID) and the second value would be the ID of the group (eg. the artist ID)
# see also https://codegolf.stackexchange.com/questions/198094/spotify-shuffle-music-playlist-shuffle-algorithm
sub balancedShuffle {
my ($list, $sortAlphabetically) = @_;

my %grouped;
foreach my $item (@$list) {
my $group = $grouped{$item->[1]} ||= [];
push @$group, $item->[0];
}

my $count = scalar @$list;
my %weighed;

while (my ($group, $items) = each %grouped) {
my $itemsCount = scalar @$items;

# shuffle items within the group
fischer_yates_shuffle($items);

# define initial offset - randomize to spread across range so not all start at 0
my $offset = rand(1/$itemsCount)*$count;
my $spacer = $count / $itemsCount;

my $i = 0;
foreach (@$items) {
my $jitter = -($itemsCount/10) + rand($itemsCount/10*2);
$weighed{$_} = $offset + $i * $spacer + $jitter;
$i++;
}
}


my $sorter = $sortAlphabetically
? sub { $weighed{$a} cmp $weighed{$b} }
: sub { $weighed{$a} <=> $weighed{$b} };

return [ sort $sorter keys %weighed ];
}

#reshuffle - every time the playlist is modified, the shufflelist should be updated
# We also invalidate the htmlplaylist at this point
sub reshuffle {
Expand Down Expand Up @@ -747,8 +791,26 @@ sub reshuffle {
# 1 is shuffle by song
# 2 is shuffle by album
if (shuffle($client) == 1) {
if ($prefs->get('useBalancedShuffle')) {
main::DEBUGLOG && $log->is_debug && $log->debug("Using balanced shuffle");
$listRef = balancedShuffle([ map {
my $track = playList($client)->[$_];
my $artist = $track->artistName;

my $handler = Slim::Player::ProtocolHandlers->handlerForURL($track->url) if !$artist;

fischer_yates_shuffle($listRef);
if ( $handler && $handler->can('getMetadataFor') && (my $meta = $handler->getMetadataFor($client, $track->url)) ) {
$artist = $meta->{artist};
}

[$_, $artist || ''];
} @$listRef ], 'alpha');

@{$client->shufflelist} = @$listRef;
}
else {
fischer_yates_shuffle($listRef);
}

# If we're preserving the current song
# this places it at the top of the playlist
Expand Down
8 changes: 0 additions & 8 deletions Slim/Plugin/RandomPlay/HTML/EN/plugins/RandomPlay/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -211,14 +211,6 @@
</p>
[% END %]

<p>
[% "PLUGIN_RANDOM_SHUFFLE_METHOD" | string %][% "COLON" | string %]
<select class="stdedit" name="useBalancedShuffle" id="useBalancedShuffle">
<option [% IF !useBalancedShuffle %]selected[% END %] value="0">[% "PLUGIN_RANDOM_USE_FASTER_SHUFFLE" | string %]</option>
<option [% IF useBalancedShuffle %]selected[% END %] value="1">[% "PLUGIN_RANDOM_USE_BALANCED_SHUFFLE" | string %]</option>
</select>
</p>

<p>
[% "PLUGIN_RANDOM_BEFORE_NUM_TRACKS" | string %]
<input class="stdedit" type="text" size="2" name="numTracks" value="[% pluginRandomNumTracks %]">
Expand Down
45 changes: 2 additions & 43 deletions Slim/Plugin/RandomPlay/Mixer.pm
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,9 @@ sub getIdList {
libraryId => $queryLibrary,
} );

if ($prefs->get('useBalancedShuffle')) {
if (preferences('server')->get('useBalancedShuffle')) {
main::DEBUGLOG && $log->is_debug && $log->debug("Using balanced shuffle");
$idList = balancedShuffle([ map { [$_, $results->{$_}->{'tracks.primary_artist'}] } keys %$results ]);
$idList = Slim::Player::Playlist::balancedShuffle([ map { [$_, $results->{$_}->{'tracks.primary_artist'}] } keys %$results ]);
}
else {
# shuffle ID list
Expand Down Expand Up @@ -151,47 +151,6 @@ sub getIdList {
return $idList;
}

# balancedShuffle is inspired by https://engineering.atspotify.com/2014/02/28/how-to-shuffle-songs/
# It tries to not shuffle really randomly, which often is considered "unnatural". But it distributes
# a group's items (eg. an artist's tracks) evenly along the full list.
# $list must be a listref of tupels, where the first element of the tuple would be the item's key
# (eg. track ID) and the second value would be the ID of the group (eg. the artist ID)
# see also https://codegolf.stackexchange.com/questions/198094/spotify-shuffle-music-playlist-shuffle-algorithm
sub balancedShuffle {
my ($list) = @_;

my %grouped;
foreach my $item (@$list) {
my $group = $grouped{$item->[1]} ||= [];
push @$group, $item->[0];
}

my $count = scalar @$list;
my %weighed;

while (my ($group, $items) = each %grouped) {
my $itemsCount = scalar @$items;

# shuffle items within the group
Slim::Player::Playlist::fischer_yates_shuffle($items);

# define initial offset - randomize to spread across range so not all start at 0
my $offset = rand(1/$itemsCount)*$count;
my $spacer = $count / $itemsCount;

my $i = 0;
foreach (@$items) {
my $jitter = -($itemsCount/10) + rand($itemsCount/10*2);
$weighed{$_} = $offset + $i * $spacer + $jitter;
$i++;
}
}

return [ sort {
$weighed{$a} <=> $weighed{$b}
} keys %weighed ];
}

sub getRandomYear {
my $client = shift;
my $filteredGenres = shift;
Expand Down
72 changes: 0 additions & 72 deletions Slim/Plugin/RandomPlay/Plugin.pm
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,6 @@ sub initPlugin {
[1, 1, 0, \&chooseLibrariesMenu]);
Slim::Control::Request::addDispatch(['randomplaychooselibrary', '_library'],
[1, 0, 0, \&chooseLibrary]);
Slim::Control::Request::addDispatch(['randomplayshufflemethodlist', '_index', '_quantity'],
[1, 0, 0, \&chooseShuffleMethod]);
Slim::Control::Request::addDispatch(['randomplayshufflemethod', '_value'],
[1, 0, 0, \&shuffleMethod]);
Slim::Control::Request::addDispatch(['randomplaygenreselectall', '_value'],
[1, 0, 0, \&genreSelectAllOrNone]);
Slim::Control::Request::addDispatch(['randomplayisactive'],
Expand Down Expand Up @@ -298,19 +294,6 @@ sub initPlugin {
},
},
},
{
stringToken => 'PLUGIN_RANDOM_SHUFFLE_METHOD',
id => 'randomshufflemethod',
weight => 65,
window => { titleStyle => 'random' },
node => 'randomplay',
actions => {
go => {
player => 0,
cmd => [ 'randomplayshufflemethodlist' ],
},
},
},
{
stringToken => 'PLUGIN_RANDOM_DISABLE',
id => 'randomdisable',
Expand Down Expand Up @@ -355,10 +338,6 @@ sub initPlugin {
{ title => '{PLUGIN_RANDOM_YEAR}', url => 'randomplay://year' },
]
);

$prefs->init({
useBalancedShuffle => sub { Slim::Utils::OSDetect->getOS()->canDBHighMem() ? 1 : 0 },
});
}

sub postinitPlugin {
Expand Down Expand Up @@ -631,55 +610,6 @@ sub chooseLibrariesMenu {
Slim::Control::Jive::sliceAndShip($request, $client, \@menu);
}

sub shuffleMethod {
my $request = shift;

if (!$initialized) {
$request->setStatusBadConfig();
return;
}

$prefs->set('useBalancedShuffle', $request->getParam('_value') ? 1 : 0);

$request->setStatusDone();
}

sub chooseShuffleMethod {
my $request = shift;

if (!$initialized) {
$request->setStatusBadConfig();
return;
}

my $client = $request->client();

my $useBalancedShuffle = $prefs->get('useBalancedShuffle');

my @menu = ({
text => cstring($client, 'PLUGIN_RANDOM_USE_FASTER_SHUFFLE'),
radio => ($useBalancedShuffle ? 0 : 1),
actions => {
'do' => {
player => 0,
cmd => ['randomplayshufflemethod', 0],
},
},
},{
text => cstring($client, 'PLUGIN_RANDOM_USE_BALANCED_SHUFFLE'),
radio => ($useBalancedShuffle ? 1 : 0),
actions => {
'do' => {
player => 0,
cmd => ['randomplayshufflemethod', 1],
},
},
});

Slim::Control::Jive::sliceAndShip($request, $client, \@menu);
}


# Returns a hash whose keys are the genres in the db
sub getGenres {
my ($client, $useIncludeGenres) = @_;
Expand Down Expand Up @@ -1209,7 +1139,6 @@ sub handleWebList {
$params->{'pluginRandomContinuousMode'}= $prefs->get('continuous');
$params->{'pluginRandomNowPlaying'} = $client->master->pluginData('type');
$params->{'pluginRandomUseLibrary'} = $prefs->get('library');
$params->{'useBalancedShuffle'} = $prefs->get('useBalancedShuffle');

$params->{'mixTypes'} = \@mixTypes;
$params->{'favorites'} = {};
Expand Down Expand Up @@ -1258,7 +1187,6 @@ sub handleWebSettings {
$prefs->set('oldtracks', $params->{'numOldTracks'});
$prefs->set('continuous', $params->{'continuousMode'} ? 1 : 0);
$prefs->set('library', $params->{'useLibrary'});
$prefs->set('useBalancedShuffle', $params->{'useBalancedShuffle'} ? 1 : 0);

# Pass on to check if the user requested a new mix as well
handleWebMix($client, $params);
Expand Down
12 changes: 0 additions & 12 deletions Slim/Plugin/RandomPlay/strings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -344,18 +344,6 @@ PLUGIN_RANDOM_LIBRARY_FILTER
NL Gebruik deelcollectie
NO Bruk delbiblioteker

PLUGIN_RANDOM_SHUFFLE_METHOD
DE Mischmethode
EN Shuffle method

PLUGIN_RANDOM_USE_BALANCED_SHUFFLE
DE Ausbalancierter, aber langsamer mischen
EN Use more balanced, but slower shuffle

PLUGIN_RANDOM_USE_FASTER_SHUFFLE
DE Schneller, aber weniger ausbalanciert mischen
EN Use faster, but less balanced shuffle

PLUGIN_RANDOM_CHOOSE_BELOW
CS Vybrat náhodně zvolený mix hudby z vaší knihovny:
DA Vælg en tilfældig blanding af musik fra dit bibliotek:
Expand Down
1 change: 1 addition & 0 deletions Slim/Utils/Prefs.pm
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ sub init {
'precacheArtwork' => 1,
'customArtSpecs' => {},
'maxPlaylistLength' => sub { $os->canDBHighMem() ? 2500 : 500 },
'useBalancedShuffle' => sub { $os->canDBHighMem() ? 1 : 0 },
# Server Settings - Security
'filterHosts' => 0,
'allowedHosts' => sub {
Expand Down
2 changes: 1 addition & 1 deletion Slim/Web/Settings/Server/Performance.pm
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ sub page {
}

sub prefs {
my @prefs = ( $prefs, qw(dbhighmem disableStatistics serverPriority scannerPriority
my @prefs = ( $prefs, qw(dbhighmem disableStatistics serverPriority scannerPriority useBalancedShuffle
precacheArtwork maxPlaylistLength useLocalImageproxy dontTriggerScanOnPrefChange) );
push @prefs, qw(autorescan autorescan_stat_interval) if Slim::Utils::OSDetect::getOS->canAutoRescan;
return @prefs;
Expand Down
14 changes: 13 additions & 1 deletion strings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8426,7 +8426,7 @@ SETUP_ENHANCEDHTTP_DESC
SETUP_ENABLE_PERSISTENTHTTP
EN Persistent mode
FR Mode persistent

SETUP_ENABLE_BUFFEREDHTTP
DE HTTP(S) Datenströme zwischenspeichern
EN Cache HTTP(S) streams on disk
Expand Down Expand Up @@ -19334,6 +19334,18 @@ SETUP_SERVERPRIORITY
SV Serverprioritet
ZH_CN Logitech Media Server服务器流程优先值

SETUP_SHUFFLE_METHOD
DE Mischmethode
EN Shuffle method

SETUP_USE_BALANCED_SHUFFLE
DE Ausbalancierter, aber langsamer mischen
EN Use more balanced, but slower shuffle

SETUP_USE_FASTER_SHUFFLE
DE Schneller, aber weniger ausbalanciert mischen
EN Use faster, but less balanced shuffle

SETUP_SERVERPRIORITY_DESC
CS Můžete zadat prioritu pro Logitech Media Server.
DA Du kan specificere hvilken prioritet Logitech Media Server skal have.
Expand Down

0 comments on commit 6c66e23

Please sign in to comment.