diff --git a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs index fedbcd541c..51f3052509 100644 --- a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs +++ b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs @@ -44,32 +44,44 @@ namespace osu.Game.Rulesets.UI }; } - private ISampleInfo[] playableSampleInfo; + private HitObject fallbackObject; /// /// 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(); + var nextObject = hitObjectContainer.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current)?.HitObject; + + if (nextObject == null) + { + if (fallbackObject == null || fallbackObject.StartTime < Time.Current) + { + // in the case a next object isn't available in drawable form, we need to do a somewhat expensive traversal to get a valid sound to play. + // note that we don't want to cache the object if it is an alive object, as once it is hit we don't want to continue playing its sound. + // check whether we can use the previous computed sample. + + // fallback to non-alive objects to find next off-screen object + // TODO: make lookup more efficient? + fallbackObject = hitObjectContainer.Entries + .Where(e => e.Result?.HasResult != true && e.HitObject.StartTime > Time.Current)? + .OrderBy(e => e.HitObject.StartTime) + .FirstOrDefault()?.HitObject ?? hitObjectContainer.Entries.FirstOrDefault()?.HitObject; + } + + nextObject = fallbackObject; + } if (nextObject != null) { var hitSound = getNextSample(); - playableSampleInfo = GetPlayableSampleInfo(nextObject); - hitSound.Samples = playableSampleInfo; + hitSound.Samples = GetPlayableSampleInfo(nextObject).Select(s => nextObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); hitSound.Play(); } } - protected virtual ISampleInfo[] GetPlayableSampleInfo(HitObject nextObject) => - // TODO: avoid cast somehow? - nextObject.Samples.Cast().ToArray(); + protected virtual HitSampleInfo[] GetPlayableSampleInfo(HitObject nextObject) => + nextObject.Samples.ToArray(); private SkinnableSound getNextSample() {