diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
index 5aff4e200b..9ac223a0d7 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
@@ -6,7 +6,6 @@ using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Rulesets.Mania.UI;
@@ -29,11 +28,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
[Resolved(canBeNull: true)]
private ManiaPlayfield playfield { get; set; }
- ///
- /// Gets the samples that are played by this object during gameplay.
- ///
- public ISampleInfo[] GetGameplaySamples() => Samples.Samples;
-
protected override float SamplePlaybackPosition
{
get
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index 9b5893b268..f5e30efd91 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Linq;
using osuTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -19,6 +18,7 @@ using osuTK;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania.UI
{
@@ -28,12 +28,6 @@ namespace osu.Game.Rulesets.Mania.UI
public const float COLUMN_WIDTH = 80;
public const float SPECIAL_COLUMN_WIDTH = 70;
- ///
- /// For hitsounds played by this (i.e. not as a result of hitting a hitobject),
- /// a certain number of samples are allowed to be played concurrently so that it feels better when spam-pressing the key.
- ///
- private const int max_concurrent_hitsounds = OsuGameBase.SAMPLE_CONCURRENCY;
-
///
/// The index of this column as part of the whole playfield.
///
@@ -45,10 +39,10 @@ namespace osu.Game.Rulesets.Mania.UI
internal readonly Container TopLevelContainer;
private readonly DrawablePool hitExplosionPool;
private readonly OrderedHitPolicy hitPolicy;
- private readonly Container hitSounds;
-
public Container UnderlayElements => HitObjectArea.UnderlayElements;
+ private readonly GameplaySampleTriggerSource sampleTriggerSource;
+
public Column(int index)
{
Index = index;
@@ -64,6 +58,7 @@ namespace osu.Game.Rulesets.Mania.UI
InternalChildren = new[]
{
hitExplosionPool = new DrawablePool(5),
+ sampleTriggerSource = new GameplaySampleTriggerSource(HitObjectContainer),
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
background.CreateProxy(),
HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both },
@@ -72,12 +67,6 @@ namespace osu.Game.Rulesets.Mania.UI
RelativeSizeAxes = Axes.Both
},
background,
- hitSounds = new Container
- {
- Name = "Column samples pool",
- RelativeSizeAxes = Axes.Both,
- Children = Enumerable.Range(0, max_concurrent_hitsounds).Select(_ => new SkinnableSound()).ToArray()
- },
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
};
@@ -133,29 +122,12 @@ namespace osu.Game.Rulesets.Mania.UI
HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result)));
}
- private int nextHitSoundIndex;
-
public bool OnPressed(ManiaAction action)
{
if (action != Action.Value)
return false;
- var nextObject =
- HitObjectContainer.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ??
- // fallback to non-alive objects to find next off-screen object
- HitObjectContainer.Objects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ??
- HitObjectContainer.Objects.LastOrDefault();
-
- if (nextObject is DrawableManiaHitObject maniaObject)
- {
- var hitSound = hitSounds[nextHitSoundIndex];
-
- hitSound.Samples = maniaObject.GetGameplaySamples();
- hitSound.Play();
-
- nextHitSoundIndex = (nextHitSoundIndex + 1) % max_concurrent_hitsounds;
- }
-
+ sampleTriggerSource.Play();
return true;
}
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index 29d8a475ef..b3e1b24d8d 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -54,6 +54,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
public readonly Bindable AccentColour = new Bindable(Color4.Gray);
+ ///
+ /// Gets the samples that are played by this object during gameplay.
+ ///
+ public ISampleInfo[] GetGameplaySamples() => Samples.Samples;
+
protected PausableSkinnableSound Samples { get; private set; }
public virtual IEnumerable GetSamples() => HitObject.Samples;
diff --git a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs
new file mode 100644
index 0000000000..fedbcd541c
--- /dev/null
+++ b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.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.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Audio;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.UI
+{
+ ///
+ /// A component which can trigger the most appropriate hit sound for a given point in time, based on the state of a
+ ///
+ public class GameplaySampleTriggerSource : CompositeDrawable
+ {
+ private readonly HitObjectContainer hitObjectContainer;
+
+ private int nextHitSoundIndex;
+
+ ///
+ /// The number of concurrent samples allowed to be played concurrently so that it feels better when spam-pressing a key.
+ ///
+ private const int max_concurrent_hitsounds = OsuGameBase.SAMPLE_CONCURRENCY;
+
+ private readonly Container hitSounds;
+
+ [Resolved]
+ private DrawableRuleset drawableRuleset { get; set; }
+
+ public GameplaySampleTriggerSource(HitObjectContainer hitObjectContainer)
+ {
+ this.hitObjectContainer = hitObjectContainer;
+ InternalChildren = new Drawable[]
+ {
+ hitSounds = new Container
+ {
+ Name = "concurrent sample pool",
+ RelativeSizeAxes = Axes.Both,
+ Children = Enumerable.Range(0, max_concurrent_hitsounds).Select(_ => new SkinnableSound()).ToArray()
+ },
+ };
+ }
+
+ private ISampleInfo[] playableSampleInfo;
+
+ ///
+ /// Play the most appropriate hit sound for the current point in time.
+ ///
+ public void Play()
+ {
+ var nextObject =
+ hitObjectContainer.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current)?.HitObject ??
+ // fallback to non-alive objects to find next off-screen object
+ // TODO: make lookup more efficient?
+ drawableRuleset.Objects.FirstOrDefault(h => h.StartTime > Time.Current) ??
+ drawableRuleset.Objects.LastOrDefault();
+
+ if (nextObject != null)
+ {
+ var hitSound = getNextSample();
+ playableSampleInfo = GetPlayableSampleInfo(nextObject);
+ hitSound.Samples = playableSampleInfo;
+ hitSound.Play();
+ }
+ }
+
+ protected virtual ISampleInfo[] GetPlayableSampleInfo(HitObject nextObject) =>
+ // TODO: avoid cast somehow?
+ nextObject.Samples.Cast().ToArray();
+
+ private SkinnableSound getNextSample()
+ {
+ var hitSound = hitSounds[nextHitSoundIndex];
+
+ // round robin over available samples to allow for concurrent playback.
+ nextHitSoundIndex = (nextHitSoundIndex + 1) % max_concurrent_hitsounds;
+
+ return hitSound;
+ }
+ }
+}