From a56751511e34d67b1eaf4ebebb2566f6184d94fb Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Sat, 22 Jun 2024 01:36:30 +0900 Subject: [PATCH] Apply a ducking effect to the currently playing track when switching ruleset --- osu.Game/Overlays/MusicController.cs | 57 +++++++++++++++++++ .../Toolbar/ToolbarRulesetSelector.cs | 31 +++++++++- .../Toolbar/ToolbarRulesetTabButton.cs | 12 ---- 3 files changed, 87 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 0986c0513c..678ae92d4b 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -16,6 +16,7 @@ using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Framework.Threading; +using osu.Game.Audio.Effects; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Rulesets.Mods; @@ -63,6 +64,16 @@ namespace osu.Game.Overlays [Resolved] private RealmAccess realm { get; set; } + private AudioFilter audioDuckFilter; + private readonly BindableDouble audioDuckVolume = new BindableDouble(1); + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + AddInternal(audioDuckFilter = new AudioFilter(audio.TrackMixer)); + audio.Tracks.AddAdjustment(AdjustableProperty.Volume, audioDuckVolume); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -243,6 +254,52 @@ namespace osu.Game.Overlays onSuccess?.Invoke(); }); + /// + /// Attenuates the volume and/or filters the currently playing track. + /// + /// Duration of the ducking transition, in ms. + /// Level to drop volume to (1.0 = 100%). + /// Cutoff frequency to drop `AudioFilter` to. Use `AudioFilter.MAX_LOWPASS_CUTOFF` to skip filter effect. + /// Easing for the ducking transition. + public void Duck(int duration = 0, float duckVolumeTo = 0.25f, int duckCutoffTo = 300, Easing easing = Easing.InCubic) + { + Schedule(() => + { + audioDuckFilter?.CutoffTo(duckCutoffTo, duration, easing); + this.TransformBindableTo(audioDuckVolume, duckVolumeTo, duration, easing); + }); + } + + /// + /// Restores the volume to full and stops filtering the currently playing track after having used . + /// + /// Duration of the unducking transition, in ms. + /// Easing for the unducking transition. + public void Unduck(int duration = 500, Easing easing = Easing.InCubic) + { + Schedule(() => + { + audioDuckFilter?.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, duration, easing); + this.TransformBindableTo(audioDuckVolume, 1, duration, easing); + }); + } + + /// + /// A convenience method that ducks the currently playing track, then after a delay, unducks it. + /// + /// Delay after audio is ducked before unducking begins, in ms. + /// Duration of the unducking transition, in ms. + /// Easing for the unducking transition. + /// Level to drop volume to (1.0 = 100%). + /// Cutoff frequency to drop `AudioFilter` to. Use `AudioFilter.MAX_LOWPASS_CUTOFF` to skip filter effect. + /// Duration of the ducking transition, in ms. + /// Easing for the ducking transition. + public void TimedDuck(int delay, int unduckDuration = 500, Easing unduckEasing = Easing.InCubic, float duckVolumeTo = 0.25f, int duckCutoffTo = 300, int duckDuration = 0, Easing duckEasing = Easing.InCubic) + { + Duck(duckDuration, duckVolumeTo, duckCutoffTo, duckEasing); + Scheduler.AddDelayed(() => Unduck(unduckDuration, unduckEasing), delay); + } + private bool next() { if (beatmap.Disabled || !AllowTrackControl.Value) diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index d49c340ed4..93be108b71 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -3,8 +3,12 @@ #nullable disable +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -21,6 +25,12 @@ namespace osu.Game.Overlays.Toolbar { protected Drawable ModeButtonLine { get; private set; } + [Resolved] + private MusicController musicController { get; set; } + + private readonly Dictionary rulesetSelectionSample = new Dictionary(); + private readonly Dictionary rulesetSelectionChannel = new Dictionary(); + public ToolbarRulesetSelector() { RelativeSizeAxes = Axes.Y; @@ -28,7 +38,7 @@ namespace osu.Game.Overlays.Toolbar } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { AddRangeInternal(new[] { @@ -54,6 +64,11 @@ namespace osu.Game.Overlays.Toolbar } }, }); + + foreach (var r in Rulesets.AvailableRulesets) + rulesetSelectionSample[r] = audio.Samples.Get($@"UI/ruleset-select-{r.ShortName}"); + + Current.ValueChanged += playRulesetSelectionSample; } protected override void LoadComplete() @@ -84,6 +99,20 @@ namespace osu.Game.Overlays.Toolbar } } + private void playRulesetSelectionSample(ValueChangedEvent r) + { + if (r.OldValue == null) + return; + + if (rulesetSelectionChannel.TryGetValue(r.OldValue, out var sampleChannel)) + sampleChannel?.Stop(); + + rulesetSelectionChannel[r.NewValue] = rulesetSelectionSample[r.NewValue].GetChannel(); + rulesetSelectionChannel[r.NewValue]?.Play(); + + musicController?.TimedDuck(600); + } + public override bool HandleNonPositionalInput => !Current.Disabled && base.HandleNonPositionalInput; public override bool HandlePositionalInput => !Current.Disabled && base.HandlePositionalInput; diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs index 0315bede64..3287ac6eaa 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; @@ -19,8 +17,6 @@ namespace osu.Game.Overlays.Toolbar { private readonly RulesetButton ruleset; - private Sample? selectSample; - public ToolbarRulesetTabButton(RulesetInfo value) : base(value) { @@ -38,18 +34,10 @@ namespace osu.Game.Overlays.Toolbar ruleset.SetIcon(rInstance.CreateIcon()); } - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - selectSample = audio.Samples.Get($@"UI/ruleset-select-{Value.ShortName}"); - } - protected override void OnActivated() => ruleset.Active = true; protected override void OnDeactivated() => ruleset.Active = false; - protected override void OnActivatedByUser() => selectSample?.Play(); - private partial class RulesetButton : ToolbarButton { protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();