mirror of
https://github.com/ppy/osu.git
synced 2025-01-30 23:23:18 +08:00
Shuffle playback order in global playlist by default
RFC. Closes https://github.com/ppy/osu/issues/18169. Implements the given proposal of keeping the current stable order but adding a shuffle facility to the now playing overlay, and enabling it by default. There are more changes I want to make here but I'd like this to get discussion first, because I am likely to continue putting this sort of selection logic into `MusicController` and I just want to confirm nobody is going to have a problem with that. In particular this is not sharing the randomisation implementation with beatmap carousel because it doesn't generalise nicely (song select cares about the particular *beatmap difficulties* selected to rewind properly, while the music controller only cares about picking a *beatmap set*).
This commit is contained in:
parent
cf9f8c7f66
commit
12bd516a57
@ -34,6 +34,8 @@ namespace osu.Game.Tests.Visual.Menus
|
|||||||
{
|
{
|
||||||
Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null!;
|
Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null!;
|
||||||
|
|
||||||
|
AddStep("disable shuffle", () => Game.MusicController.Shuffle.Value = false);
|
||||||
|
|
||||||
// ensure we have at least two beatmaps available to identify the direction the music controller navigated to.
|
// ensure we have at least two beatmaps available to identify the direction the music controller navigated to.
|
||||||
AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()), 5);
|
AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()), 5);
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
@ -13,8 +14,10 @@ using osu.Framework.Graphics.Audio;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Audio.Effects;
|
using osu.Game.Audio.Effects;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
@ -43,6 +46,8 @@ namespace osu.Game.Overlays
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly BindableBool AllowTrackControl = new BindableBool(true);
|
public readonly BindableBool AllowTrackControl = new BindableBool(true);
|
||||||
|
|
||||||
|
public readonly BindableBool Shuffle = new BindableBool(true);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired when the global <see cref="WorkingBeatmap"/> has changed.
|
/// Fired when the global <see cref="WorkingBeatmap"/> has changed.
|
||||||
/// Includes direction information for display purposes.
|
/// Includes direction information for display purposes.
|
||||||
@ -66,12 +71,18 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
private AudioFilter audioDuckFilter = null!;
|
private AudioFilter audioDuckFilter = null!;
|
||||||
|
|
||||||
|
private readonly Bindable<RandomSelectAlgorithm> randomSelectAlgorithm = new Bindable<RandomSelectAlgorithm>();
|
||||||
|
private readonly List<BeatmapSetInfo> previousRandomSets = new List<BeatmapSetInfo>();
|
||||||
|
private int randomHistoryDirection;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio)
|
private void load(AudioManager audio, OsuConfigManager configManager)
|
||||||
{
|
{
|
||||||
AddInternal(audioDuckFilter = new AudioFilter(audio.TrackMixer));
|
AddInternal(audioDuckFilter = new AudioFilter(audio.TrackMixer));
|
||||||
audio.Tracks.AddAdjustment(AdjustableProperty.Volume, audioDuckVolume);
|
audio.Tracks.AddAdjustment(AdjustableProperty.Volume, audioDuckVolume);
|
||||||
sampleVolume = audio.VolumeSample.GetBoundCopy();
|
sampleVolume = audio.VolumeSample.GetBoundCopy();
|
||||||
|
|
||||||
|
configManager.BindWith(OsuSetting.RandomSelectAlgorithm, randomSelectAlgorithm);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -238,8 +249,15 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
queuedDirection = TrackChangeDirection.Prev;
|
queuedDirection = TrackChangeDirection.Prev;
|
||||||
|
|
||||||
var playableSet = getBeatmapSets().AsEnumerable().TakeWhile(i => !i.Equals(current?.BeatmapSetInfo)).LastOrDefault(s => !s.Protected || allowProtectedTracks)
|
BeatmapSetInfo? playableSet;
|
||||||
|
|
||||||
|
if (Shuffle.Value)
|
||||||
|
playableSet = getNextRandom(-1, allowProtectedTracks);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
playableSet = getBeatmapSets().AsEnumerable().TakeWhile(i => !i.Equals(current?.BeatmapSetInfo)).LastOrDefault(s => !s.Protected || allowProtectedTracks)
|
||||||
?? getBeatmapSets().AsEnumerable().LastOrDefault(s => !s.Protected || allowProtectedTracks);
|
?? getBeatmapSets().AsEnumerable().LastOrDefault(s => !s.Protected || allowProtectedTracks);
|
||||||
|
}
|
||||||
|
|
||||||
if (playableSet != null)
|
if (playableSet != null)
|
||||||
{
|
{
|
||||||
@ -327,8 +345,15 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
queuedDirection = TrackChangeDirection.Next;
|
queuedDirection = TrackChangeDirection.Next;
|
||||||
|
|
||||||
var playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo) || (i.Protected && !allowProtectedTracks)).ElementAtOrDefault(1)
|
BeatmapSetInfo? playableSet;
|
||||||
|
|
||||||
|
if (Shuffle.Value)
|
||||||
|
playableSet = getNextRandom(1, allowProtectedTracks);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo) || (i.Protected && !allowProtectedTracks)).ElementAtOrDefault(1)
|
||||||
?? getBeatmapSets().AsEnumerable().FirstOrDefault(i => !i.Protected || allowProtectedTracks);
|
?? getBeatmapSets().AsEnumerable().FirstOrDefault(i => !i.Protected || allowProtectedTracks);
|
||||||
|
}
|
||||||
|
|
||||||
var playableBeatmap = playableSet?.Beatmaps.FirstOrDefault();
|
var playableBeatmap = playableSet?.Beatmaps.FirstOrDefault();
|
||||||
|
|
||||||
@ -342,6 +367,58 @@ namespace osu.Game.Overlays
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BeatmapSetInfo? getNextRandom(int direction, bool allowProtectedTracks)
|
||||||
|
{
|
||||||
|
BeatmapSetInfo result;
|
||||||
|
|
||||||
|
var possibleSets = getBeatmapSets().AsEnumerable().Where(s => !s.Protected || allowProtectedTracks).ToArray();
|
||||||
|
|
||||||
|
if (possibleSets.Length == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// condition below checks if the signs of `randomHistoryDirection` and `direction` are opposite and not zero.
|
||||||
|
// if that is the case, it means that the user had previously chosen next track `randomHistoryDirection` times and wants to go back,
|
||||||
|
// or that the user had previously chosen previous track `randomHistoryDirection` times and wants to go forward.
|
||||||
|
// in both cases, it means that we have a history of previous random selections that we can rewind.
|
||||||
|
if (randomHistoryDirection * direction < 0)
|
||||||
|
{
|
||||||
|
Debug.Assert(Math.Abs(randomHistoryDirection) == previousRandomSets.Count);
|
||||||
|
result = previousRandomSets[^1];
|
||||||
|
previousRandomSets.RemoveAt(previousRandomSets.Count - 1);
|
||||||
|
randomHistoryDirection += direction;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the early-return above didn't cover it, it means that we have no history to fall back on
|
||||||
|
// and need to actually choose something random.
|
||||||
|
switch (randomSelectAlgorithm.Value)
|
||||||
|
{
|
||||||
|
case RandomSelectAlgorithm.Random:
|
||||||
|
result = possibleSets[RNG.Next(possibleSets.Length)];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RandomSelectAlgorithm.RandomPermutation:
|
||||||
|
var notYetPlayedSets = possibleSets.Except(previousRandomSets).ToArray();
|
||||||
|
|
||||||
|
if (notYetPlayedSets.Length == 0)
|
||||||
|
{
|
||||||
|
notYetPlayedSets = possibleSets;
|
||||||
|
previousRandomSets.Clear();
|
||||||
|
randomHistoryDirection = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = notYetPlayedSets[RNG.Next(notYetPlayedSets.Length)];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(randomSelectAlgorithm), randomSelectAlgorithm.Value, "Unsupported random select algorithm");
|
||||||
|
}
|
||||||
|
|
||||||
|
previousRandomSets.Add(result);
|
||||||
|
randomHistoryDirection += direction;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private void restartTrack()
|
private void restartTrack()
|
||||||
{
|
{
|
||||||
// if not scheduled, the previously track will be stopped one frame later (see ScheduleAfterChildren logic in GameBase).
|
// if not scheduled, the previously track will be stopped one frame later (see ScheduleAfterChildren logic in GameBase).
|
||||||
|
@ -47,6 +47,7 @@ namespace osu.Game.Overlays
|
|||||||
private IconButton prevButton = null!;
|
private IconButton prevButton = null!;
|
||||||
private IconButton playButton = null!;
|
private IconButton playButton = null!;
|
||||||
private IconButton nextButton = null!;
|
private IconButton nextButton = null!;
|
||||||
|
private MusicIconButton shuffleButton = null!;
|
||||||
private IconButton playlistButton = null!;
|
private IconButton playlistButton = null!;
|
||||||
|
|
||||||
private ScrollingTextContainer title = null!, artist = null!;
|
private ScrollingTextContainer title = null!, artist = null!;
|
||||||
@ -69,6 +70,7 @@ namespace osu.Game.Overlays
|
|||||||
private OsuColour colours { get; set; } = null!;
|
private OsuColour colours { get; set; } = null!;
|
||||||
|
|
||||||
private Bindable<bool> allowTrackControl = null!;
|
private Bindable<bool> allowTrackControl = null!;
|
||||||
|
private BindableBool shuffle = new BindableBool(true);
|
||||||
|
|
||||||
public NowPlayingOverlay()
|
public NowPlayingOverlay()
|
||||||
{
|
{
|
||||||
@ -162,6 +164,13 @@ namespace osu.Game.Overlays
|
|||||||
Action = () => musicController.NextTrack(),
|
Action = () => musicController.NextTrack(),
|
||||||
Icon = FontAwesome.Solid.StepForward,
|
Icon = FontAwesome.Solid.StepForward,
|
||||||
},
|
},
|
||||||
|
shuffleButton = new MusicIconButton
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Action = shuffle.Toggle,
|
||||||
|
Icon = FontAwesome.Solid.Random,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
playlistButton = new MusicIconButton
|
playlistButton = new MusicIconButton
|
||||||
@ -227,6 +236,9 @@ namespace osu.Game.Overlays
|
|||||||
allowTrackControl = musicController.AllowTrackControl.GetBoundCopy();
|
allowTrackControl = musicController.AllowTrackControl.GetBoundCopy();
|
||||||
allowTrackControl.BindValueChanged(_ => Scheduler.AddOnce(updateEnabledStates), true);
|
allowTrackControl.BindValueChanged(_ => Scheduler.AddOnce(updateEnabledStates), true);
|
||||||
|
|
||||||
|
shuffle.BindTo(musicController.Shuffle);
|
||||||
|
shuffle.BindValueChanged(s => shuffleButton.FadeColour(s.NewValue ? colours.Yellow : Color4.White, 200, Easing.OutQuint), true);
|
||||||
|
|
||||||
musicController.TrackChanged += trackChanged;
|
musicController.TrackChanged += trackChanged;
|
||||||
trackChanged(beatmap.Value);
|
trackChanged(beatmap.Value);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user