public override IEnumerable <Issue> GetIssues(Beatmap beatmap)
        {
            List <ObservedDistance> observedDistances = new List <ObservedDistance>();

            double ratioProblemThreshold = 15.0;
            double ratioWarningThreshold = 4.0;
            double ratioMinorThreshold   = 2.0;

            double snapLeniencyMs = 5;

            double deltaTime;
            double distance;

            foreach (HitObject hitObject in beatmap.hitObjects)
            {
                HitObject nextObject = hitObject.Next();

                // Ignore spinners, since they have no clear start or end.
                if (hitObject is Spinner || nextObject is Spinner || nextObject == null)
                {
                    continue;
                }

                deltaTime = nextObject.GetPrevDeltaTime();
                // Ignore objects ~1/2 or more beats apart (assuming 160 bpm), since they're unlikely to be an issue.
                if (deltaTime > 180)
                {
                    continue;
                }

                distance = nextObject.GetPrevDistance();

                if (distance < 20)
                {
                    distance = 20;
                }

                List <ObservedDistance> sameSnappedDistances =
                    observedDistances
                    .FindAll(observedDistance =>
                             deltaTime <= observedDistance.deltaTime + snapLeniencyMs &&
                             deltaTime >= observedDistance.deltaTime - snapLeniencyMs &&
                             // Count the distances of sliders separately, as these have leniency unlike circles.
                             observedDistance.hitObject is Slider == hitObject is Slider);

                ObservedDistance observedDistance = new ObservedDistance(deltaTime, distance, hitObject);
                observedDistances.Add(observedDistance);

                if (!sameSnappedDistances.Any() ||
                    distance / deltaTime < sameSnappedDistances.Max(obvDist => obvDist.distance / obvDist.deltaTime * Decay(hitObject, obvDist)))
                {
                    continue;
                }

                if (distance <= beatmap.difficultySettings.GetCircleRadius() * 4)
                {
                    continue;
                }

                if (sameSnappedDistances.Count < 3)
                {
                    // Too few samples, probably going to get inaccurate readings.
                    continue;
                }

                double expectedDistance  = sameSnappedDistances.Sum(obvDist => obvDist.distance * Decay(hitObject, obvDist)) / sameSnappedDistances.Count();
                double expectedDeltaTime = sameSnappedDistances.Sum(obvDist => obvDist.deltaTime * Decay(hitObject, obvDist)) / sameSnappedDistances.Count();

                if (hitObject is Slider)
                {
                    // Account for slider follow circle leniency.
                    distance -= Math.Min(beatmap.difficultySettings.GetCircleRadius() * 3, distance);
                }

                double actualExpectedRatio = (distance / deltaTime) / (expectedDistance / expectedDeltaTime);

                if (actualExpectedRatio <= ratioMinorThreshold)
                {
                    continue;
                }

                IEnumerable <string> comparisonTimestamps = sameSnappedDistances.Select(obvDist =>
                                                                                        Timestamp.Get(obvDist.hitObject, obvDist.hitObject.Next())
                                                                                        ).TakeLast(3);

                string templateName = "Minor";
                if (actualExpectedRatio > ratioProblemThreshold)
                {
                    templateName = "Problem";
                }
                else if (actualExpectedRatio > ratioWarningThreshold)
                {
                    templateName = "Warning";
                }

                yield return(new Issue(GetTemplate(templateName), beatmap,
                                       Timestamp.Get(hitObject, nextObject), Math.Round(actualExpectedRatio * 10) / 10,
                                       string.Join("", comparisonTimestamps)));
            }
        }