From 4f0c0ea5f9bb4d3dc7b671af349cf6ab87d15313 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 18:16:19 +0900 Subject: [PATCH 1/8] Fix hit samples playing while paused / seeking in the editor --- .../Objects/Drawables/DrawableHitObject.cs | 4 ++-- osu.Game/Screens/Edit/Editor.cs | 1 + osu.Game/Screens/Edit/EditorClock.cs | 22 +++++++++++++++++-- osu.Game/Screens/Play/GameplayClock.cs | 2 +- osu.Game/Screens/Play/ISeekableClock.cs | 13 +++++++++++ 5 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Screens/Play/ISeekableClock.cs diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 7c05bc9aa7..5b26607bf7 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -360,7 +360,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } [Resolved(canBeNull: true)] - private GameplayClock gameplayClock { get; set; } + private ISeekableClock seekableClock { get; set; } /// /// Calculate the position to be used for sample playback at a specified X position (0..1). @@ -377,7 +377,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Whether samples should currently be playing. Will be false during seek operations. /// - protected bool ShouldPlaySamples => gameplayClock?.IsSeeking != true; + protected bool ShouldPlaySamples => seekableClock?.IsSeeking != true; /// /// Plays all the hit sounds for this . diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index fd090e0959..1f5e261588 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -107,6 +107,7 @@ namespace osu.Game.Screens.Edit UpdateClockSource(); dependencies.CacheAs(clock); + dependencies.CacheAs(clock); AddInternal(clock); // todo: remove caching of this and consume via editorBeatmap? diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index ec203df064..ebc73c2bb8 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -7,17 +7,18 @@ using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; -using osu.Framework.Utils; using osu.Framework.Timing; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Screens.Play; namespace osu.Game.Screens.Edit { /// /// A decoupled clock which adds editor-specific functionality, such as snapping to a user-defined beat divisor. /// - public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock + public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock, ISeekableClock { public IBindable Track => track; @@ -211,8 +212,25 @@ namespace osu.Game.Screens.Edit private const double transform_time = 300; + public bool IsSeeking { get; private set; } + + protected override void Update() + { + base.Update(); + + if (IsSeeking) + { + // we are either running a seek tween or doing an immediate seek. + // in the case of an immediate seek the seeking bool will be set to false after one update. + // this allows for silencing hit sounds and the likes. + IsSeeking = Transforms.Any(); + } + } + public void SeekTo(double seekDestination) { + IsSeeking = true; + if (IsRunning) Seek(seekDestination); else diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index 4f2cf5005c..b10e50882c 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Play /// , as this should only be done once to ensure accuracy. /// /// - public class GameplayClock : IFrameBasedClock + public class GameplayClock : IFrameBasedClock, ISeekableClock { private readonly IFrameBasedClock underlyingClock; diff --git a/osu.Game/Screens/Play/ISeekableClock.cs b/osu.Game/Screens/Play/ISeekableClock.cs new file mode 100644 index 0000000000..9d992a45fd --- /dev/null +++ b/osu.Game/Screens/Play/ISeekableClock.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Screens.Play +{ + public interface ISeekableClock + { + /// + /// Whether an ongoing seek operation is active. + /// + bool IsSeeking { get; } + } +} From 585b857a0c7f417a5fdfeb877d032324b4879f13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 12:17:38 +0900 Subject: [PATCH 2/8] Handle paused state correctly --- osu.Game/Screens/Edit/EditorClock.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index ebc73c2bb8..99e5044b1f 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -220,10 +220,12 @@ namespace osu.Game.Screens.Edit if (IsSeeking) { + bool isPaused = track.Value?.IsRunning != true; + // we are either running a seek tween or doing an immediate seek. // in the case of an immediate seek the seeking bool will be set to false after one update. // this allows for silencing hit sounds and the likes. - IsSeeking = Transforms.Any(); + IsSeeking = isPaused || Transforms.Any(); } } From d6f3beffb648f1a0e059a5d641984522c799b77b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 12:45:20 +0900 Subject: [PATCH 3/8] Use existing bindable flow instead --- .../Objects/Drawables/DrawableSlider.cs | 5 +-- .../Objects/Drawables/DrawableSpinner.cs | 5 +-- .../Gameplay/TestSceneSkinnableSound.cs | 2 +- .../Objects/Drawables/DrawableHitObject.cs | 9 ++---- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/Edit/EditorClock.cs | 29 +++++++++++++---- osu.Game/Screens/Play/GameplayClock.cs | 4 ++- .../Screens/Play/ISamplePlaybackDisabler.cs | 20 ++++++++++++ osu.Game/Screens/Play/ISeekableClock.cs | 13 -------- osu.Game/Skinning/SkinnableSound.cs | 32 +++++++++++-------- 10 files changed, 70 insertions(+), 51 deletions(-) create mode 100644 osu.Game/Screens/Play/ISamplePlaybackDisabler.cs delete mode 100644 osu.Game/Screens/Play/ISeekableClock.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 07f40f763b..68f203db47 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -112,10 +112,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private void updateSlidingSample(ValueChangedEvent tracking) { - // note that samples will not start playing if exiting a seek operation in the middle of a slider. - // may be something we want to address at a later point, but not so easy to make happen right now - // (SkinnableSound would need to expose whether the sample is already playing and this logic would need to run in Update). - if (tracking.NewValue && ShouldPlaySamples) + if (tracking.NewValue) slidingSample?.Play(); else slidingSample?.Stop(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index a57bb466c7..b2a706833c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -113,10 +113,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private void updateSpinningSample(ValueChangedEvent tracking) { - // note that samples will not start playing if exiting a seek operation in the middle of a spinner. - // may be something we want to address at a later point, but not so easy to make happen right now - // (SkinnableSound would need to expose whether the sample is already playing and this logic would need to run in Update). - if (tracking.NewValue && ShouldPlaySamples) + if (tracking.NewValue) { spinningSample?.Play(); spinningSample?.VolumeTo(1, 200); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index ed75d83151..8b37cbd06f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneSkinnableSound : OsuTestScene { - [Cached] + [Cached(typeof(ISamplePlaybackDisabler))] private GameplayClock gameplayClock = new GameplayClock(new FramedClock()); private TestSkinSourceContainer skinSource; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 5b26607bf7..796b8f7aae 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -360,7 +360,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } [Resolved(canBeNull: true)] - private ISeekableClock seekableClock { get; set; } + private ISamplePlaybackDisabler samplePlaybackDisabler { get; set; } /// /// Calculate the position to be used for sample playback at a specified X position (0..1). @@ -374,18 +374,13 @@ namespace osu.Game.Rulesets.Objects.Drawables return balance_adjust_amount * (userPositionalHitSounds.Value ? position - 0.5f : 0); } - /// - /// Whether samples should currently be playing. Will be false during seek operations. - /// - protected bool ShouldPlaySamples => seekableClock?.IsSeeking != true; - /// /// Plays all the hit sounds for this . /// This is invoked automatically when this is hit. /// public virtual void PlaySamples() { - if (Samples != null && ShouldPlaySamples) + if (Samples != null) { Samples.Balance.Value = CalculateSamplePlaybackBalance(SamplePlaybackPosition); Samples.Play(); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 1f5e261588..a0692d94e6 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -107,7 +107,7 @@ namespace osu.Game.Screens.Edit UpdateClockSource(); dependencies.CacheAs(clock); - dependencies.CacheAs(clock); + dependencies.CacheAs(clock); AddInternal(clock); // todo: remove caching of this and consume via editorBeatmap? diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 99e5044b1f..4b7cd82637 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Edit /// /// A decoupled clock which adds editor-specific functionality, such as snapping to a user-defined beat divisor. /// - public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock, ISeekableClock + public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock, ISamplePlaybackDisabler { public IBindable Track => track; @@ -32,6 +32,10 @@ namespace osu.Game.Screens.Edit private readonly DecoupleableInterpolatingFramedClock underlyingClock; + public IBindable SamplePlaybackDisabled => samplePlaybackDisabled; + + private readonly Bindable samplePlaybackDisabled = new Bindable(); + public EditorClock(WorkingBeatmap beatmap, BindableBeatDivisor beatDivisor) : this(beatmap.Beatmap.ControlPointInfo, beatmap.Track.Length, beatDivisor) { @@ -167,11 +171,14 @@ namespace osu.Game.Screens.Edit public void Stop() { + samplePlaybackDisabled.Value = true; underlyingClock.Stop(); } public bool Seek(double position) { + samplePlaybackDisabled.Value = true; + ClearTransforms(); return underlyingClock.Seek(position); } @@ -212,26 +219,34 @@ namespace osu.Game.Screens.Edit private const double transform_time = 300; - public bool IsSeeking { get; private set; } - protected override void Update() { base.Update(); - if (IsSeeking) + updateSeekingState(); + } + + private void updateSeekingState() + { + if (samplePlaybackDisabled.Value) { - bool isPaused = track.Value?.IsRunning != true; + if (track.Value?.IsRunning != true) + { + // seeking in the editor can happen while the track isn't running. + // in this case we always want to expose ourselves as seeking (to avoid sample playback). + return; + } // we are either running a seek tween or doing an immediate seek. // in the case of an immediate seek the seeking bool will be set to false after one update. // this allows for silencing hit sounds and the likes. - IsSeeking = isPaused || Transforms.Any(); + samplePlaybackDisabled.Value = Transforms.Any(); } } public void SeekTo(double seekDestination) { - IsSeeking = true; + samplePlaybackDisabled.Value = true; if (IsRunning) Seek(seekDestination); diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index b10e50882c..da4648fd2b 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Play /// , as this should only be done once to ensure accuracy. /// /// - public class GameplayClock : IFrameBasedClock, ISeekableClock + public class GameplayClock : IFrameBasedClock, ISamplePlaybackDisabler { private readonly IFrameBasedClock underlyingClock; @@ -48,5 +48,7 @@ namespace osu.Game.Screens.Play public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo; public IClock Source => underlyingClock; + + public IBindable SamplePlaybackDisabled => IsPaused; } } diff --git a/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs b/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs new file mode 100644 index 0000000000..83e89d654b --- /dev/null +++ b/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs @@ -0,0 +1,20 @@ +// 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.Bindables; +using osu.Game.Skinning; + +namespace osu.Game.Screens.Play +{ + /// + /// Allows a component to disable sample playback dynamically as required. + /// Handled by . + /// + public interface ISamplePlaybackDisabler + { + /// + /// Whether sample playback should be disabled (or paused for looping samples). + /// + IBindable SamplePlaybackDisabled { get; } + } +} diff --git a/osu.Game/Screens/Play/ISeekableClock.cs b/osu.Game/Screens/Play/ISeekableClock.cs deleted file mode 100644 index 9d992a45fd..0000000000 --- a/osu.Game/Screens/Play/ISeekableClock.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Screens.Play -{ - public interface ISeekableClock - { - /// - /// Whether an ongoing seek operation is active. - /// - bool IsSeeking { get; } - } -} diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index ba14049b41..704ba099c1 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -50,25 +50,28 @@ namespace osu.Game.Skinning InternalChild = samplesContainer = new AudioContainer(); } - private Bindable gameplayClockPaused; + private readonly IBindable samplePlaybackDisabled = new Bindable(); [BackgroundDependencyLoader(true)] - private void load(GameplayClock gameplayClock) + private void load(ISamplePlaybackDisabler samplePlaybackDisabler) { // if in a gameplay context, pause sample playback when gameplay is paused. - gameplayClockPaused = gameplayClock?.IsPaused.GetBoundCopy(); - gameplayClockPaused?.BindValueChanged(paused => + if (samplePlaybackDisabler != null) { - if (requestedPlaying) + samplePlaybackDisabled.BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); + samplePlaybackDisabled.BindValueChanged(disabled => { - if (paused.NewValue) - stop(); - // it's not easy to know if a sample has finished playing (to end). - // to keep things simple only resume playing looping samples. - else if (Looping) - play(); - } - }); + if (requestedPlaying) + { + if (disabled.NewValue) + stop(); + // it's not easy to know if a sample has finished playing (to end). + // to keep things simple only resume playing looping samples. + else if (Looping) + play(); + } + }); + } } private bool looping; @@ -94,6 +97,9 @@ namespace osu.Game.Skinning private void play() { + if (samplePlaybackDisabled.Value) + return; + samplesContainer.ForEach(c => { if (PlayWhenZeroVolume || c.AggregateVolume.Value > 0) From c5f6b77bbaa03be219cf5978391852ff5019287a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 13:42:17 +0900 Subject: [PATCH 4/8] Add missing cached type --- osu.Game/Screens/Play/GameplayClockContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 7a9cb3dddd..cc25a733f1 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -51,6 +51,7 @@ namespace osu.Game.Screens.Play /// The final clock which is exposed to underlying components. /// [Cached] + [Cached(typeof(ISamplePlaybackDisabler))] public readonly GameplayClock GameplayClock; private Bindable userAudioOffset; From 74e74e1c31acdd58bfb258ca6a3dc0b291c548e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 14:20:41 +0900 Subject: [PATCH 5/8] Fix pause loop sound not working because paused --- osu.Game/Screens/Play/PauseOverlay.cs | 12 +++++++++++- osu.Game/Skinning/SkinnableSound.cs | 6 ++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 65f34aba3e..9494971f8a 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); - AddInternal(pauseLoop = new SkinnableSound(new SampleInfo("pause-loop")) + AddInternal(pauseLoop = new UnpausableSkinnableSound(new SampleInfo("pause-loop")) { Looping = true, Volume = { Value = 0 } @@ -54,5 +54,15 @@ namespace osu.Game.Screens.Play pauseLoop.VolumeTo(0, TRANSITION_DURATION, Easing.OutQuad).Finally(_ => pauseLoop.Stop()); } + + private class UnpausableSkinnableSound : SkinnableSound + { + protected override bool PlayWhenPaused => true; + + public UnpausableSkinnableSound(SampleInfo sampleInfo) + : base(sampleInfo) + { + } + } } } diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 704ba099c1..f3ab8b86bc 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -37,6 +37,8 @@ namespace osu.Game.Skinning /// protected bool PlayWhenZeroVolume => Looping; + protected virtual bool PlayWhenPaused => false; + private readonly AudioContainer samplesContainer; public SkinnableSound(ISampleInfo hitSamples) @@ -63,7 +65,7 @@ namespace osu.Game.Skinning { if (requestedPlaying) { - if (disabled.NewValue) + if (disabled.NewValue && !PlayWhenPaused) stop(); // it's not easy to know if a sample has finished playing (to end). // to keep things simple only resume playing looping samples. @@ -97,7 +99,7 @@ namespace osu.Game.Skinning private void play() { - if (samplePlaybackDisabled.Value) + if (samplePlaybackDisabled.Value && !PlayWhenPaused) return; samplesContainer.ForEach(c => From 414c40d29849932734343123a8b89bf0725381c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 15:45:14 +0900 Subject: [PATCH 6/8] Reverse inheritance order of SkinnableSound's pause logic --- .../Objects/Drawables/DrawableSlider.cs | 4 +- .../Objects/Drawables/DrawableSpinner.cs | 4 +- .../Audio/DrumSampleContainer.cs | 8 +-- .../Gameplay/TestSceneSkinnableSound.cs | 4 +- osu.Game/Rulesets/Mods/ModNightcore.cs | 16 ++--- .../Objects/Drawables/DrawableHitObject.cs | 4 +- .../Screens/Play/ISamplePlaybackDisabler.cs | 2 +- osu.Game/Screens/Play/PauseOverlay.cs | 12 +--- osu.Game/Skinning/PausableSkinnableSound.cs | 66 +++++++++++++++++++ osu.Game/Skinning/SkinnableSound.cs | 50 +------------- 10 files changed, 91 insertions(+), 79 deletions(-) create mode 100644 osu.Game/Skinning/PausableSkinnableSound.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 68f203db47..b73ae5eeb9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Tracking.BindValueChanged(updateSlidingSample); } - private SkinnableSound slidingSample; + private PausableSkinnableSound slidingSample; protected override void LoadSamples() { @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables var clone = HitObject.SampleControlPoint.ApplyTo(firstSample); clone.Name = "sliderslide"; - AddInternal(slidingSample = new SkinnableSound(clone) + AddInternal(slidingSample = new PausableSkinnableSound(clone) { Looping = true }); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index b2a706833c..6488c60acf 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables isSpinning.BindValueChanged(updateSpinningSample); } - private SkinnableSound spinningSample; + private PausableSkinnableSound spinningSample; private const float spinning_sample_initial_frequency = 1.0f; private const float spinning_sample_modulated_base_frequency = 0.5f; @@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables var clone = HitObject.SampleControlPoint.ApplyTo(firstSample); clone.Name = "spinnerspin"; - AddInternal(spinningSample = new SkinnableSound(clone) + AddInternal(spinningSample = new PausableSkinnableSound(clone) { Volume = { Value = 0 }, Looping = true, diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs index 7c39c040b1..fcf7c529f5 100644 --- a/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs +++ b/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs @@ -42,9 +42,9 @@ namespace osu.Game.Rulesets.Taiko.Audio } } - private SkinnableSound addSound(HitSampleInfo hitSampleInfo, double lifetimeStart, double lifetimeEnd) + private PausableSkinnableSound addSound(HitSampleInfo hitSampleInfo, double lifetimeStart, double lifetimeEnd) { - var drawable = new SkinnableSound(hitSampleInfo) + var drawable = new PausableSkinnableSound(hitSampleInfo) { LifetimeStart = lifetimeStart, LifetimeEnd = lifetimeEnd @@ -57,8 +57,8 @@ namespace osu.Game.Rulesets.Taiko.Audio public class DrumSample { - public SkinnableSound Centre; - public SkinnableSound Rim; + public PausableSkinnableSound Centre; + public PausableSkinnableSound Rim; } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 8b37cbd06f..8f2011e5dd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Gameplay private GameplayClock gameplayClock = new GameplayClock(new FramedClock()); private TestSkinSourceContainer skinSource; - private SkinnableSound skinnableSound; + private PausableSkinnableSound skinnableSound; [SetUp] public void SetUp() => Schedule(() => @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay { Clock = gameplayClock, RelativeSizeAxes = Axes.Both, - Child = skinnableSound = new SkinnableSound(new SampleInfo("normal-sliderslide")) + Child = skinnableSound = new PausableSkinnableSound(new SampleInfo("normal-sliderslide")) }, }; }); diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index 4004953cd1..282de3a8e1 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -52,10 +52,10 @@ namespace osu.Game.Rulesets.Mods public class NightcoreBeatContainer : BeatSyncedContainer { - private SkinnableSound hatSample; - private SkinnableSound clapSample; - private SkinnableSound kickSample; - private SkinnableSound finishSample; + private PausableSkinnableSound hatSample; + private PausableSkinnableSound clapSample; + private PausableSkinnableSound kickSample; + private PausableSkinnableSound finishSample; private int? firstBeat; @@ -69,10 +69,10 @@ namespace osu.Game.Rulesets.Mods { InternalChildren = new Drawable[] { - hatSample = new SkinnableSound(new SampleInfo("nightcore-hat")), - clapSample = new SkinnableSound(new SampleInfo("nightcore-clap")), - kickSample = new SkinnableSound(new SampleInfo("nightcore-kick")), - finishSample = new SkinnableSound(new SampleInfo("nightcore-finish")), + hatSample = new PausableSkinnableSound(new SampleInfo("nightcore-hat")), + clapSample = new PausableSkinnableSound(new SampleInfo("nightcore-clap")), + kickSample = new PausableSkinnableSound(new SampleInfo("nightcore-kick")), + finishSample = new PausableSkinnableSound(new SampleInfo("nightcore-finish")), }; } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 796b8f7aae..59a3381b8b 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public readonly Bindable AccentColour = new Bindable(Color4.Gray); - protected SkinnableSound Samples { get; private set; } + protected PausableSkinnableSound Samples { get; private set; } public virtual IEnumerable GetSamples() => HitObject.Samples; @@ -179,7 +179,7 @@ namespace osu.Game.Rulesets.Objects.Drawables + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); } - Samples = new SkinnableSound(samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s))); + Samples = new PausableSkinnableSound(samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s))); AddInternal(Samples); } diff --git a/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs b/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs index 83e89d654b..6b37021fe6 100644 --- a/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs +++ b/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs @@ -8,7 +8,7 @@ namespace osu.Game.Screens.Play { /// /// Allows a component to disable sample playback dynamically as required. - /// Handled by . + /// Handled by . /// public interface ISamplePlaybackDisabler { diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 9494971f8a..65f34aba3e 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); - AddInternal(pauseLoop = new UnpausableSkinnableSound(new SampleInfo("pause-loop")) + AddInternal(pauseLoop = new SkinnableSound(new SampleInfo("pause-loop")) { Looping = true, Volume = { Value = 0 } @@ -54,15 +54,5 @@ namespace osu.Game.Screens.Play pauseLoop.VolumeTo(0, TRANSITION_DURATION, Easing.OutQuad).Finally(_ => pauseLoop.Stop()); } - - private class UnpausableSkinnableSound : SkinnableSound - { - protected override bool PlayWhenPaused => true; - - public UnpausableSkinnableSound(SampleInfo sampleInfo) - : base(sampleInfo) - { - } - } } } diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs new file mode 100644 index 0000000000..991278fb98 --- /dev/null +++ b/osu.Game/Skinning/PausableSkinnableSound.cs @@ -0,0 +1,66 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Audio; +using osu.Game.Screens.Play; + +namespace osu.Game.Skinning +{ + public class PausableSkinnableSound : SkinnableSound + { + protected bool RequestedPlaying { get; private set; } + + public PausableSkinnableSound(ISampleInfo hitSamples) + : base(hitSamples) + { + } + + public PausableSkinnableSound(IEnumerable hitSamples) + : base(hitSamples) + { + } + + private readonly IBindable samplePlaybackDisabled = new Bindable(); + + [BackgroundDependencyLoader(true)] + private void load(ISamplePlaybackDisabler samplePlaybackDisabler) + { + // if in a gameplay context, pause sample playback when gameplay is paused. + if (samplePlaybackDisabler != null) + { + samplePlaybackDisabled.BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); + samplePlaybackDisabled.BindValueChanged(disabled => + { + if (RequestedPlaying) + { + if (disabled.NewValue) + base.Stop(); + // it's not easy to know if a sample has finished playing (to end). + // to keep things simple only resume playing looping samples. + else if (Looping) + base.Play(); + } + }); + } + } + + public override void Play() + { + RequestedPlaying = true; + + if (samplePlaybackDisabled.Value) + return; + + base.Play(); + } + + public override void Stop() + { + RequestedPlaying = false; + base.Stop(); + } + } +} diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index f3ab8b86bc..91bdcd7444 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -11,7 +11,6 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; using osu.Game.Audio; -using osu.Game.Screens.Play; namespace osu.Game.Skinning { @@ -22,8 +21,6 @@ namespace osu.Game.Skinning [Resolved] private ISampleStore samples { get; set; } - private bool requestedPlaying; - public override bool RemoveWhenNotAlive => false; public override bool RemoveCompletedTransforms => false; @@ -37,8 +34,6 @@ namespace osu.Game.Skinning /// protected bool PlayWhenZeroVolume => Looping; - protected virtual bool PlayWhenPaused => false; - private readonly AudioContainer samplesContainer; public SkinnableSound(ISampleInfo hitSamples) @@ -52,30 +47,6 @@ namespace osu.Game.Skinning InternalChild = samplesContainer = new AudioContainer(); } - private readonly IBindable samplePlaybackDisabled = new Bindable(); - - [BackgroundDependencyLoader(true)] - private void load(ISamplePlaybackDisabler samplePlaybackDisabler) - { - // if in a gameplay context, pause sample playback when gameplay is paused. - if (samplePlaybackDisabler != null) - { - samplePlaybackDisabled.BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); - samplePlaybackDisabled.BindValueChanged(disabled => - { - if (requestedPlaying) - { - if (disabled.NewValue && !PlayWhenPaused) - stop(); - // it's not easy to know if a sample has finished playing (to end). - // to keep things simple only resume playing looping samples. - else if (Looping) - play(); - } - }); - } - } - private bool looping; public bool Looping @@ -91,17 +62,8 @@ namespace osu.Game.Skinning } } - public void Play() + public virtual void Play() { - requestedPlaying = true; - play(); - } - - private void play() - { - if (samplePlaybackDisabled.Value && !PlayWhenPaused) - return; - samplesContainer.ForEach(c => { if (PlayWhenZeroVolume || c.AggregateVolume.Value > 0) @@ -109,13 +71,7 @@ namespace osu.Game.Skinning }); } - public void Stop() - { - requestedPlaying = false; - stop(); - } - - private void stop() + public virtual void Stop() { samplesContainer.ForEach(c => c.Stop()); } @@ -150,7 +106,7 @@ namespace osu.Game.Skinning // Start playback internally for the new samples if the previous ones were playing beforehand. if (wasPlaying) - play(); + Play(); } #region Re-expose AudioContainer From 6cceb42ad5f21861b839fca68f358183ad1b3da6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 15:50:53 +0900 Subject: [PATCH 7/8] Remove unused DI resolution --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 59a3381b8b..eb12c1cdfc 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -17,7 +17,6 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osu.Game.Configuration; -using osu.Game.Screens.Play; using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables @@ -359,9 +358,6 @@ namespace osu.Game.Rulesets.Objects.Drawables { } - [Resolved(canBeNull: true)] - private ISamplePlaybackDisabler samplePlaybackDisabler { get; set; } - /// /// Calculate the position to be used for sample playback at a specified X position (0..1). /// From b0f8e11bd46b54ab14ee678a10b576daa7ed4278 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 17:34:14 +0900 Subject: [PATCH 8/8] Fix incorrect caching --- osu.Game/Screens/Play/GameplayClockContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index e404d7ca42..9f8e55f577 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Play /// public GameplayClock GameplayClock => localGameplayClock; - [Cached] + [Cached(typeof(GameplayClock))] [Cached(typeof(ISamplePlaybackDisabler))] private readonly LocalGameplayClock localGameplayClock;