diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index f62ba21827..607e6b8399 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -71,7 +71,6 @@ namespace osu.Game.Rulesets.Mods // Apply a fixed rate change when missing, allowing the player to catch up when the rate is too fast. private const double rate_change_on_miss = 0.95d; - private IAdjustableAudioComponent? track; private double targetRate = 1d; /// @@ -123,24 +122,27 @@ namespace osu.Game.Rulesets.Mods /// private readonly Dictionary ratesForRewinding = new Dictionary(); + private readonly RateAdjustModHelper rateAdjustHelper; + public ModAdaptiveSpeed() { + rateAdjustHelper = new RateAdjustModHelper(SpeedChange); + rateAdjustHelper.HandleAudioAdjustments(AdjustPitch); + InitialRate.BindValueChanged(val => { SpeedChange.Value = val.NewValue; targetRate = val.NewValue; }); - AdjustPitch.BindValueChanged(adjustPitchChanged); } public void ApplyToTrack(IAdjustableAudioComponent track) { - this.track = track; - InitialRate.TriggerChange(); - AdjustPitch.TriggerChange(); recentRates.Clear(); recentRates.AddRange(Enumerable.Repeat(InitialRate.Value, recent_rate_count)); + + rateAdjustHelper.ApplyToTrack(track); } public void ApplyToSample(IAdjustableAudioComponent sample) @@ -199,15 +201,6 @@ namespace osu.Game.Rulesets.Mods } } - private void adjustPitchChanged(ValueChangedEvent adjustPitchSetting) - { - track?.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange); - track?.AddAdjustment(adjustmentForPitchSetting(adjustPitchSetting.NewValue), SpeedChange); - } - - private AdjustableProperty adjustmentForPitchSetting(bool adjustPitchSettingValue) - => adjustPitchSettingValue ? AdjustableProperty.Frequency : AdjustableProperty.Tempo; - private IEnumerable getAllApplicableHitObjects(IEnumerable hitObjects) { foreach (var hitObject in hitObjects) diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index de1a5ab56c..39ebd1fe4c 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -5,21 +5,36 @@ using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Configuration; namespace osu.Game.Rulesets.Mods { - public abstract class ModDaycore : ModHalfTime + public abstract class ModDaycore : ModRateAdjust { public override string Name => "Daycore"; public override string Acronym => "DC"; public override IconUsage? Icon => null; + public override ModType Type => ModType.DifficultyReduction; public override LocalisableString Description => "Whoaaaaa..."; + [SettingSource("Speed decrease", "The actual decrease to apply")] + public override BindableNumber SpeedChange { get; } = new BindableDouble(0.75) + { + MinValue = 0.5, + MaxValue = 0.99, + Precision = 0.01, + }; + private readonly BindableNumber tempoAdjust = new BindableDouble(1); private readonly BindableNumber freqAdjust = new BindableDouble(1); + private readonly RateAdjustModHelper rateAdjustHelper; protected ModDaycore() { + rateAdjustHelper = new RateAdjustModHelper(SpeedChange); + + // intentionally not deferring the speed change handling to `RateAdjustModHelper` + // as the expected result of operation is not the same (daycore should preserve constant pitch). SpeedChange.BindValueChanged(val => { freqAdjust.Value = SpeedChange.Default; @@ -29,9 +44,10 @@ namespace osu.Game.Rulesets.Mods public override void ApplyToTrack(IAdjustableAudioComponent track) { - // base.ApplyToTrack() intentionally not called (different tempo adjustment is applied) track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); } + + public override double ScoreMultiplier => rateAdjustHelper.ScoreMultiplier; } } diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 27e594edfe..789291772d 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; @@ -26,21 +27,22 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - public override double ScoreMultiplier + [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] + public virtual BindableBool AdjustPitch { get; } = new BindableBool(); + + private readonly RateAdjustModHelper rateAdjustHelper; + + protected ModDoubleTime() { - get - { - // Round to the nearest multiple of 0.1. - double value = (int)(SpeedChange.Value * 10) / 10.0; - - // Offset back to 0. - value -= 1; - - // Each 0.1 multiple changes score multiplier by 0.02. - value /= 5; - - return 1 + value; - } + rateAdjustHelper = new RateAdjustModHelper(SpeedChange); + rateAdjustHelper.HandleAudioAdjustments(AdjustPitch); } + + public override void ApplyToTrack(IAdjustableAudioComponent track) + { + rateAdjustHelper.ApplyToTrack(track); + } + + public override double ScoreMultiplier => rateAdjustHelper.ScoreMultiplier; } } diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 7415c94cd8..8b5dd39584 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; @@ -26,18 +27,22 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - public override double ScoreMultiplier + [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] + public virtual BindableBool AdjustPitch { get; } = new BindableBool(); + + private readonly RateAdjustModHelper rateAdjustHelper; + + protected ModHalfTime() { - get - { - // Round to the nearest multiple of 0.1. - double value = (int)(SpeedChange.Value * 10) / 10.0; - - // Offset back to 0. - value -= 1; - - return 1 + value; - } + rateAdjustHelper = new RateAdjustModHelper(SpeedChange); + rateAdjustHelper.HandleAudioAdjustments(AdjustPitch); } + + public override void ApplyToTrack(IAdjustableAudioComponent track) + { + rateAdjustHelper.ApplyToTrack(track); + } + + public override double ScoreMultiplier => rateAdjustHelper.ScoreMultiplier; } } diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index 9b1f7d5cf7..b519ab4db7 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -11,6 +11,7 @@ using osu.Framework.Localisation; using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Objects; @@ -19,22 +20,33 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Mods { - public abstract class ModNightcore : ModDoubleTime + public abstract class ModNightcore : ModRateAdjust { public override string Name => "Nightcore"; public override string Acronym => "NC"; public override IconUsage? Icon => OsuIcon.ModNightcore; + public override ModType Type => ModType.DifficultyIncrease; public override LocalisableString Description => "Uguuuuuuuu..."; - } - public abstract partial class ModNightcore : ModNightcore, IApplicableToDrawableRuleset - where TObject : HitObject - { + [SettingSource("Speed increase", "The actual increase to apply")] + public override BindableNumber SpeedChange { get; } = new BindableDouble(1.5) + { + MinValue = 1.01, + MaxValue = 2, + Precision = 0.01, + }; + private readonly BindableNumber tempoAdjust = new BindableDouble(1); private readonly BindableNumber freqAdjust = new BindableDouble(1); + private readonly RateAdjustModHelper rateAdjustHelper; + protected ModNightcore() { + rateAdjustHelper = new RateAdjustModHelper(SpeedChange); + + // intentionally not deferring the speed change handling to `RateAdjustModHelper` + // as the expected result of operation is not the same (nightcore should preserve constant pitch). SpeedChange.BindValueChanged(val => { freqAdjust.Value = SpeedChange.Default; @@ -44,11 +56,16 @@ namespace osu.Game.Rulesets.Mods public override void ApplyToTrack(IAdjustableAudioComponent track) { - // base.ApplyToTrack() intentionally not called (different tempo adjustment is applied) track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); } + public override double ScoreMultiplier => rateAdjustHelper.ScoreMultiplier; + } + + public abstract partial class ModNightcore : ModNightcore, IApplicableToDrawableRuleset + where TObject : HitObject + { public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { drawableRuleset.Overlays.Add(new NightcoreBeatContainer()); diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index 5dad01e015..fa1c143585 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -13,10 +13,7 @@ namespace osu.Game.Rulesets.Mods public abstract BindableNumber SpeedChange { get; } - public virtual void ApplyToTrack(IAdjustableAudioComponent track) - { - track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange); - } + public abstract void ApplyToTrack(IAdjustableAudioComponent track); public virtual void ApplyToSample(IAdjustableAudioComponent sample) { diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 54ee0cd3bf..d2772417db 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -44,21 +44,21 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - private IAdjustableAudioComponent? track; + private readonly RateAdjustModHelper rateAdjustHelper; protected ModTimeRamp() { + rateAdjustHelper = new RateAdjustModHelper(SpeedChange); + rateAdjustHelper.HandleAudioAdjustments(AdjustPitch); + // for preview purpose at song select. eventually we'll want to be able to update every frame. FinalRate.BindValueChanged(_ => applyRateAdjustment(double.PositiveInfinity), true); - AdjustPitch.BindValueChanged(applyPitchAdjustment); } public void ApplyToTrack(IAdjustableAudioComponent track) { - this.track = track; - + rateAdjustHelper.ApplyToTrack(track); FinalRate.TriggerChange(); - AdjustPitch.TriggerChange(); } public void ApplyToSample(IAdjustableAudioComponent sample) @@ -95,16 +95,5 @@ namespace osu.Game.Rulesets.Mods /// Adjust the rate along the specified ramp. /// private void applyRateAdjustment(double time) => SpeedChange.Value = ApplyToRate(time); - - private void applyPitchAdjustment(ValueChangedEvent adjustPitchSetting) - { - // remove existing old adjustment - track?.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange); - - track?.AddAdjustment(adjustmentForPitchSetting(adjustPitchSetting.NewValue), SpeedChange); - } - - private AdjustableProperty adjustmentForPitchSetting(bool adjustPitchSettingValue) - => adjustPitchSettingValue ? AdjustableProperty.Frequency : AdjustableProperty.Tempo; } } diff --git a/osu.Game/Rulesets/Mods/RateAdjustModHelper.cs b/osu.Game/Rulesets/Mods/RateAdjustModHelper.cs new file mode 100644 index 0000000000..ffd4de0e90 --- /dev/null +++ b/osu.Game/Rulesets/Mods/RateAdjustModHelper.cs @@ -0,0 +1,84 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Audio; +using osu.Framework.Bindables; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// Provides common functionality shared across various rate adjust mods. + /// + public class RateAdjustModHelper : IApplicableToTrack + { + public readonly IBindableNumber SpeedChange; + + private IAdjustableAudioComponent? track; + + private BindableBool? adjustPitch; + + /// + /// The score multiplier for the current . + /// + public double ScoreMultiplier + { + get + { + // Round to the nearest multiple of 0.1. + double value = (int)(SpeedChange.Value * 10) / 10.0; + + // Offset back to 0. + value -= 1; + + if (SpeedChange.Value >= 1) + value /= 5; + + return 1 + value; + } + } + + /// + /// Construct a new . + /// + /// The main speed adjust parameter which is exposed to the user. + public RateAdjustModHelper(IBindableNumber speedChange) + { + SpeedChange = speedChange; + } + + /// + /// Setup audio track adjustments for a rate adjust mod. + /// Importantly, must be called when a track is obtained/changed for this to work. + /// + /// The "adjust pitch" setting as exposed to the user. + public void HandleAudioAdjustments(BindableBool adjustPitch) + { + this.adjustPitch = adjustPitch; + + // When switching between pitch adjust, we need to update adjustments to time-shift or frequency-scale. + adjustPitch.BindValueChanged(adjustPitchSetting => + { + track?.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange); + track?.AddAdjustment(adjustmentForPitchSetting(adjustPitchSetting.NewValue), SpeedChange); + + AdjustableProperty adjustmentForPitchSetting(bool adjustPitchSettingValue) + => adjustPitchSettingValue ? AdjustableProperty.Frequency : AdjustableProperty.Tempo; + }); + } + + /// + /// Should be invoked when a track is obtained / changed. + /// + /// The new track. + /// If this method is called before . + public void ApplyToTrack(IAdjustableAudioComponent track) + { + if (adjustPitch == null) + throw new InvalidOperationException($"Must call {nameof(HandleAudioAdjustments)} first"); + + this.track = track; + adjustPitch.TriggerChange(); + } + } +}