public override IEnumerable <Issue> GetIssues(BeatmapSet beatmapSet) { foreach (string hsFile in beatmapSet.hitSoundFiles) { HitObject hitObjectActiveAt = GetHitObjectActiveAt(beatmapSet, hsFile); if (hitObjectActiveAt == null) { // Hit sound is never active, so delay does not matter. continue; } string hsPath = Path.Combine(beatmapSet.songPath, hsFile); List <float[]> peaks = null; Exception exception = null; try { peaks = AudioBASS.GetPeaks(hsPath); } catch (Exception ex) { exception = ex; } if (exception == null) { if (!(peaks?.Count > 0) || !(peaks.Sum(peak => peak.Sum()) > 0)) { // Muted files don't have anything to be delayed, hence ignore. continue; } double maxStrength = peaks.Select(value => Math.Abs(value.Sum())).Max(); int delay = 0; int pureDelay = 0; double strength = 0; while (delay + pureDelay < peaks.Count) { strength += Math.Abs(peaks[delay].Sum()); if (strength >= maxStrength / 2) { break; } strength *= 0.95; // The delay added by MP3 encoding still has very slight volume where it's basically silent. if (strength < 0.001) { strength = 0; ++pureDelay; ++delay; } else { ++delay; } } if (pureDelay >= 5) { yield return(new Issue(GetTemplate("Pure Delay"), null, hsFile, $"{pureDelay:0.##}")); } else if (delay + pureDelay >= 5) { yield return(new Issue(GetTemplate("Delay"), null, hsFile, $"{pureDelay:0.##}", $"{delay:0.##}", Timestamp.Get(hitObjectActiveAt), hitObjectActiveAt.beatmap)); } else if (delay + pureDelay >= 1) { yield return(new Issue(GetTemplate("Minor Delay"), null, hsFile, $"{pureDelay:0.##}", $"{delay:0.##}")); } } else { yield return(new Issue(GetTemplate("Unable to check"), null, hsFile, Common.ExceptionTag(exception))); } } }
public override IEnumerable <Issue> GetIssues(BeatmapSet beatmapSet) { foreach (string hsFile in beatmapSet.hitSoundFiles) { string hsPath = Path.Combine(beatmapSet.songPath, hsFile); int channels = 0; List <float[]> peaks = null; Exception exception = null; try { channels = AudioBASS.GetChannels(hsPath); peaks = AudioBASS.GetPeaks(hsPath); } catch (Exception ex) { exception = ex; } // Cannot yield in catch statements, hence externally handled. if (exception != null) { yield return(new Issue(GetTemplate("Unable to check"), null, hsFile, Common.ExceptionTag(exception))); continue; } // Mono cannot be imbalanced; same audio on both sides. if (channels < 2) { continue; } // Silent audio cannot be imbalanced. if (peaks.Count == 0) { continue; } float leftSum = peaks.Sum(peak => peak?[0] ?? 0); float rightSum = peaks.Sum(peak => peak.Count() > 1 ? peak?[1] ?? 0 : 0); if (leftSum == 0 || rightSum == 0) { yield return(new Issue(GetTemplate("Warning Silent"), null, hsFile, leftSum - rightSum > 0 ? "left" : "right")); continue; } // 2 would mean one is double the sum of the other. float relativeVolume = leftSum > rightSum ? leftSum / rightSum : rightSum / leftSum; if (relativeVolume < 2) { continue; } // Imbalance is only an issue if it is used frequently in a short timespan or it's overall common. Common.CollectHitSoundFrequency(beatmapSet, hsFile, scoreThreshold: 14 / relativeVolume, out string mostFrequentTimestamp, out Dictionary <Beatmap, int> uses); if (mostFrequentTimestamp != null) { yield return(new Issue(GetTemplate("Warning Timestamp"), null, hsFile, leftSum - rightSum > 0 ? "left" : "right", mostFrequentTimestamp)); } else { Beatmap mapCommonlyUsedIn = Common.GetBeatmapCommonlyUsedIn(beatmapSet, uses, commonUsageThreshold: 10000); if (mapCommonlyUsedIn != null) { yield return(new Issue(GetTemplate("Warning Common"), null, hsFile, leftSum - rightSum > 0 ? "left" : "right", mapCommonlyUsedIn)); } else { yield return(new Issue(GetTemplate("Minor"), null, hsFile, leftSum - rightSum > 0 ? "left" : "right")); } } } }