/// <summary> Returns an issue if this time is very close behind to a timing line which would modify objects. </summary> private IEnumerable <Issue> GetIssue(string aType, double aTime, Beatmap aBeatmap) { double unsnap = aBeatmap.GetPracticalUnsnap(aTime); TimingLine curLine = aBeatmap.GetTimingLine(aTime); TimingLine nextLine = aBeatmap.GetNextTimingLine(aTime); if (nextLine != null) { double curEffectiveBPM = curLine.svMult * aBeatmap.GetTimingLine <UninheritedLine>(aTime).bpm; double nextEffectiveBPM = nextLine.svMult * aBeatmap.GetTimingLine <UninheritedLine>(nextLine.offset).bpm; double deltaEffectiveBPM = curEffectiveBPM - nextEffectiveBPM; double timeDiff = nextLine.offset - aTime; if (timeDiff > 0 && timeDiff <= 5 && Math.Abs(unsnap) <= 1 && Math.Abs(deltaEffectiveBPM) > 1) { yield return(new Issue(GetTemplate("Behind"), aBeatmap, Timestamp.Get(aTime), aType, $"{timeDiff:0.##}")); } if (aBeatmap.generalSettings.mode == Beatmap.Mode.Taiko && timeDiff < 0 && timeDiff >= -5 && Math.Abs(unsnap) <= 1 && Math.Abs(deltaEffectiveBPM) > 1) { yield return(new Issue(GetTemplate("After"), aBeatmap, Timestamp.Get(aTime), aType, $"{timeDiff:0.##}")); } } }
/// <summary> Returns an issue if this time is very close behind to a timing line which would modify objects. </summary> private IEnumerable <Issue> GetIssue(string type, double time, Beatmap beatmap) { double unsnap = beatmap.GetPracticalUnsnap(time); TimingLine curLine = beatmap.GetTimingLine(time); TimingLine nextLine = curLine.Next(skipConcurrent: true); if (nextLine != null) { double curEffectiveBPM = curLine.svMult * beatmap.GetTimingLine <UninheritedLine>(time).bpm; double nextEffectiveBPM = nextLine.svMult * beatmap.GetTimingLine <UninheritedLine>(nextLine.offset).bpm; double deltaEffectiveBPM = curEffectiveBPM - nextEffectiveBPM; double timeDiff = nextLine.offset - time; if (timeDiff > 0 && timeDiff <= 5 && Math.Abs(unsnap) <= 1 && Math.Abs(deltaEffectiveBPM) > 1) { yield return(new Issue(GetTemplate("Before"), beatmap, Timestamp.Get(time), type, $"{timeDiff:0.##}")); } if (beatmap.generalSettings.mode == Beatmap.Mode.Taiko && timeDiff < 0 && timeDiff >= -5 && Math.Abs(unsnap) <= 1 && Math.Abs(deltaEffectiveBPM) > 1) { yield return(new Issue(GetTemplate("After"), beatmap, Timestamp.Get(time), type, $"{timeDiff:0.##}")); } } }
private IEnumerable <Issue> GetInheritedLineIssues(Beatmap aBeatmap) { List <TimingLine> lines = aBeatmap.timingLines.ToList(); for (int i = 1; i < lines.Count; ++i) { if (!(lines[i] is InheritedLine currentLine)) { continue; } TimingLine previousLine = lines[i - 1]; TimingLine nextLine = aBeatmap.GetNextTimingLine(currentLine.offset); double timingSectionEnd = nextLine?.offset ?? aBeatmap.GetPlayTime(); double prevEndTime = aBeatmap.GetPrevHitObject(timingSectionEnd)?.GetEndTime() ?? 0; double prevSliderStart = aBeatmap.GetPrevHitObject <Slider>(timingSectionEnd)?.time ?? 0; bool containsObjects = prevEndTime >= currentLine.offset; bool canAffectSV = prevSliderStart >= currentLine.offset || aBeatmap.generalSettings.mode == Beatmap.Mode.Mania; bool sampleSettingsDiffer = currentLine.sampleset != previousLine.sampleset || currentLine.customIndex != previousLine.customIndex || currentLine.volume != previousLine.volume; // Conditions for an inherited line being used (simplified, only false positives, e.g. hold notes/spinners) // - Changes sampleset, custom index, or volume and there is an edge/body within the time frame // - Changes SV and there are sliders starting within the time frame or changes SV and the mode is mania // - Changes kiai (causes effects on screen during duration) bool used = containsObjects && sampleSettingsDiffer || canAffectSV && currentLine.svMult != previousLine.svMult || currentLine.kiai != previousLine.kiai; // Since "used" only includes false positives, this only includes false negatives, // hence the check will never say that a used line is unused. if (!used) { // Avoids confusion in case the line actually does change something // from the previous, but just doesn't apply to anything. string changesDesc = ""; if (!canAffectSV && currentLine.svMult != previousLine.svMult) { changesDesc += "SV"; } if (!containsObjects && sampleSettingsDiffer) { changesDesc += (changesDesc.Length > 0 ? " and " : "") + "sample settings"; } changesDesc += changesDesc.Length > 0 ? ", but affects nothing" : "nothing"; yield return(new Issue(GetTemplate("Minor Inherited"), aBeatmap, Timestamp.Get(currentLine.offset), changesDesc)); } } }
private static double GetBeatsPerMinute(this TimingLine timingLine) { var msPerBeatString = timingLine.code.Split(",")[1]; var msPerBeat = double.Parse(msPerBeatString, CultureInfo.InvariantCulture); return(60000 / msPerBeat); }
/// <summary> Returns whether this section contains the respective hit object type. /// Only counts the start of objects. </summary> private bool SectionContainsObject <T>(Beatmap beatmap, TimingLine line) where T : HitObject { TimingLine nextLine = line.Next(skipConcurrent: true); double nextSectionEnd = nextLine?.offset ?? beatmap.GetPlayTime(); double objectTimeBeforeEnd = beatmap.GetPrevHitObject <T>(nextSectionEnd)?.time ?? 0; return(objectTimeBeforeEnd >= line.offset); }
public override IEnumerable <Issue> GetIssues(Beatmap aBeatmap) { List <UninheritedLine> lines = aBeatmap.timingLines.OfType <UninheritedLine>().ToList(); for (int i = 1; i < lines.Count; ++i) { // Uninherited lines 4 beats apart (varying up to 1 ms for rounding errors), // with the same bpm and meter, have the same downbeat structure. // At which point the latter could be replaced by an inherited line and // function identically (other than the finish in the nightcore mod). if (lines[i - 1].bpm == lines[i].bpm && lines[i - 1].meter == lines[i].meter && GetBeatOffset(lines[i - 1], lines[i], 4) <= 1) { // Check the lines in effect both here and before to see if an inherited // line is placed on top of the red line negating its changes. TimingLine prevLine = aBeatmap.GetTimingLine(lines[i].offset - 1); TimingLine curLine = aBeatmap.GetTimingLine <UninheritedLine>(lines[i].offset); // If a line omits the first bar line we just treat it as used. if (curLine.omitsBarLine) { continue; } if (prevLine.kiai == curLine.kiai && prevLine.sampleset == curLine.sampleset && prevLine.volume == curLine.volume) { // In the nightcore mod, every 4th downbeat is inherently a // finish sound, so that technically changes things if (GetBeatOffset(lines[i - 1], lines[i], 16) <= 1) { yield return(new Issue(GetTemplate("Problem Nothing"), aBeatmap, Timestamp.Get(lines[i].offset))); } else { yield return(new Issue(GetTemplate("Warning Nothing"), aBeatmap, Timestamp.Get(lines[i].offset))); } } else { if (GetBeatOffset(lines[i - 1], lines[i], 16) <= 1) { yield return(new Issue(GetTemplate("Problem Inherited"), aBeatmap, Timestamp.Get(lines[i].offset))); } else { yield return(new Issue(GetTemplate("Warning Inherited"), aBeatmap, Timestamp.Get(lines[i].offset))); } } } } }
public override IEnumerable <Issue> GetIssues(Beatmap beatmap) { foreach (TimingLine line in beatmap.timingLines.Where(line => line.kiai)) { // If we're inside of kiai, a new line with kiai won't cause kiai to start again. if (beatmap.GetTimingLine(line.offset - 1).kiai) { continue; } double unsnap = beatmap.GetPracticalUnsnap(line.offset); // In taiko the screen changes color more drastically, so timing is more noticable. int warningThreshold = beatmap.generalSettings.mode == Beatmap.Mode.Taiko ? 5 : 10; if (Math.Abs(unsnap) >= warningThreshold) { yield return(new Issue(GetTemplate("Warning"), beatmap, Timestamp.Get(line.offset), unsnap)); } else if (Math.Abs(unsnap) >= 1) { yield return(new Issue(GetTemplate("Minor"), beatmap, Timestamp.Get(line.offset), unsnap)); } // Prevents duplicate issues occuring from both red and green line on same tick picking next line. if (beatmap.timingLines.Any( otherLine => otherLine.offset == line.offset && !otherLine.uninherited && line.uninherited)) { continue; } TimingLine nextLine = line.Next(skipConcurrent: true); if (nextLine == null || nextLine.kiai) { continue; } unsnap = beatmap.GetPracticalUnsnap(nextLine.offset); if (Math.Abs(unsnap) >= 1) { yield return(new Issue(GetTemplate("Minor End"), beatmap, Timestamp.Get(nextLine.offset), unsnap)); } } }
public override IEnumerable <Issue> GetIssues(Beatmap aBeatmap) { foreach (TimingLine line in aBeatmap.timingLines.Where(aLine => aLine.kiai)) { // If we're inside of kiai, a new line with kiai won't cause kiai to start again. if (aBeatmap.GetTimingLine(line.offset - 1).kiai) { continue; } double unsnap = aBeatmap.GetPracticalUnsnap(line.offset); if (Math.Abs(unsnap) >= 10) { yield return(new Issue(GetTemplate("Warning"), aBeatmap, Timestamp.Get(line.offset), unsnap)); } else if (Math.Abs(unsnap) >= 1) { yield return(new Issue(GetTemplate("Minor"), aBeatmap, Timestamp.Get(line.offset), unsnap)); } // Prevents duplicate issues occuring from both red and green line on same tick picking next line. if (aBeatmap.timingLines.Any(aLine => aLine.offset == line.offset && !aLine.uninherited && line.uninherited)) { continue; } TimingLine nextLine = aBeatmap.GetNextTimingLine(line.offset); if (nextLine != null && !nextLine.kiai) { unsnap = aBeatmap.GetPracticalUnsnap(nextLine.offset); if (Math.Abs(unsnap) >= 1) { yield return(new Issue(GetTemplate("Minor End"), aBeatmap, Timestamp.Get(nextLine.offset), unsnap)); } } } }
public override IEnumerable <Issue> GetIssues(Beatmap aBeatmap) { if (aBeatmap.timingLines.Count > 0) { TimingLine line = aBeatmap.timingLines[0]; if (!line.uninherited) { yield return(new Issue(GetTemplate("Inherited"), aBeatmap, Timestamp.Get(line.offset))); } else if (line.kiai) { yield return(new Issue(GetTemplate("Toggles Kiai"), aBeatmap, Timestamp.Get(line.offset))); } } else { yield return(new Issue(GetTemplate("No Lines"), aBeatmap)); } }
private IEnumerable <Issue> GetInheritedLineIssues(Beatmap beatmap) { List <TimingLine> lines = beatmap.timingLines.ToList(); for (int i = 1; i < lines.Count; ++i) { if (!(lines[i] is InheritedLine currentLine)) { continue; } TimingLine previousLine = lines[i - 1]; // Since "used" only includes false positives, this will only result in false negatives, // hence the check will never say that a used line is unused. if (IsLineUsed(beatmap, currentLine, previousLine)) { continue; } // Avoids confusion in case the line actually does change something from the // previous, but just doesn't apply to anything. string changesDesc = ""; if (!UsesSV(beatmap, currentLine, previousLine) && currentLine.svMult != previousLine.svMult) { changesDesc += "SV"; } if (!UsesSamples(beatmap, currentLine, previousLine) && SamplesDiffer(currentLine, previousLine)) { changesDesc += (changesDesc.Length > 0 ? " and " : "") + "sample settings"; } changesDesc += changesDesc.Length > 0 ? ", but affects nothing" : "nothing"; yield return(new Issue(GetTemplate("Minor Inherited"), beatmap, Timestamp.Get(currentLine.offset), changesDesc)); } }
/// <summary> Returns an issue if this time is very close behind to a timing line which would modify objects. </summary> private IEnumerable <Issue> GetIssue(string type, double time, Beatmap beatmap) { double unsnap = beatmap.GetPracticalUnsnap(time); TimingLine curLine = beatmap.GetTimingLine(time); TimingLine nextLine = curLine.Next(skipConcurrent: true); if (nextLine == null) { yield break; } double curEffectiveBPM = curLine.svMult * beatmap.GetTimingLine <UninheritedLine>(time).bpm; double nextEffectiveBPM = nextLine.svMult * beatmap.GetTimingLine <UninheritedLine>(nextLine.offset).bpm; double deltaEffectiveBPM = curEffectiveBPM - nextEffectiveBPM; double timeDiff = nextLine.offset - time; if (timeDiff > 0 && timeDiff <= 5 && Math.Abs(unsnap) <= 1 && Math.Abs(deltaEffectiveBPM) > 1) { yield return(new Issue(GetTemplate("Before"), beatmap, Timestamp.Get(time), type, $"{timeDiff:0.##}")); } // Modes where SV affects AR would be impacted even if the object was right after the line. if (IsSVAffectingAR(beatmap) && timeDiff < 0 && timeDiff >= -5 && Math.Abs(unsnap) <= 1 && Math.Abs(deltaEffectiveBPM) > 1) { yield return(new Issue(GetTemplate("After"), beatmap, Timestamp.Get(time), type, $"{timeDiff:0.##}")); } }
private IEnumerable <Issue> GetUninheritedLineIssues(Beatmap aBeatmap) { // If the previous line omits the first barline in taiko and is less than a beat apart from the new one, // then the new one does change things even if it's just a ms ahead (prevents the barline from being // thicker than normal). bool canOmitBarLine = aBeatmap.generalSettings.mode == Beatmap.Mode.Taiko || aBeatmap.generalSettings.mode == Beatmap.Mode.Mania; List <UninheritedLine> lines = aBeatmap.timingLines.OfType <UninheritedLine>().ToList(); for (int i = 1; i < lines.Count; ++i) { bool negligibleDownbeatOffset = GetBeatOffset(lines[i - 1], lines[i], lines[i - 1].meter) <= 1; bool negligibleNightcoreCymbalOffset = GetBeatOffset(lines[i - 1], lines[i], 4 * lines[i - 1].meter) <= 1; if (canOmitBarLine && lines[i - 1].omitsBarLine) { negligibleDownbeatOffset = GetBeatOffset(lines[i - 1], lines[i], lines[i - 1].meter) == 0; } // Uninherited lines 4 (or whatever the meter is) beats apart (varying up to 1 ms for rounding errors), // with the same bpm and meter, have the same downbeat structure. At which point the latter could be // replaced by an inherited line and function identically (other than the finish in the nightcore mod). if (lines[i - 1].bpm != lines[i].bpm || lines[i - 1].meter != lines[i].meter || !negligibleDownbeatOffset) { continue; } // Check the lines in effect both here and before to see if an inherited // line is placed on top of the red line negating its changes. TimingLine previousLine = aBeatmap.GetTimingLine(lines[i].offset - 1); TimingLine currentLine = aBeatmap.GetTimingLine <UninheritedLine>(lines[i].offset); // If a line omits the first bar line we just treat it as used. if (canOmitBarLine && currentLine.omitsBarLine) { continue; } if (previousLine.kiai == currentLine.kiai && previousLine.sampleset == currentLine.sampleset && previousLine.customIndex == currentLine.customIndex && previousLine.volume == currentLine.volume) { // In the nightcore mod, every 4th (or whatever the meter is) downbeat // has an added cymbal, so that technically changes things. if (negligibleNightcoreCymbalOffset) { yield return(new Issue(GetTemplate("Problem Nothing"), aBeatmap, Timestamp.Get(lines[i].offset))); } else { yield return(new Issue(GetTemplate("Warning Nothing"), aBeatmap, Timestamp.Get(lines[i].offset))); } } else { if (negligibleNightcoreCymbalOffset) { yield return(new Issue(GetTemplate("Problem Inherited"), aBeatmap, Timestamp.Get(lines[i].offset))); } else { yield return(new Issue(GetTemplate("Warning Inherited"), aBeatmap, Timestamp.Get(lines[i].offset))); } } } }
/// <summary> Returns whether changes to SV for the line will be used. </summary> private bool CanUseSV(Beatmap beatmap, TimingLine line) => SectionContainsObject <Slider>(beatmap, line) || // Taiko and mania affect approach rate through SV. beatmap.generalSettings.mode == Beatmap.Mode.Taiko || beatmap.generalSettings.mode == Beatmap.Mode.Mania;
/// <summary> Returns whether this section is affected by SV changes. </summary> private bool UsesSV(Beatmap beatmap, TimingLine currentLine, TimingLine previousLine) => CanUseSV(beatmap, currentLine) && currentLine.svMult != previousLine.svMult;
/// <summary> Returns whether this section changes sample settings (i.e. volume, sampleset, or custom index). </summary> private bool SamplesDiffer(TimingLine currentLine, TimingLine previousLine) => currentLine.sampleset != previousLine.sampleset || currentLine.customIndex != previousLine.customIndex || currentLine.volume != previousLine.volume;
/// <summary> Returns whether this section makes use of sample changes (i.e. volume, sampleset, or custom index). </summary> private bool UsesSamples(Beatmap beatmap, TimingLine currentLine, TimingLine previousLine) => SectionContainsObject <HitObject>(beatmap, currentLine) && SamplesDiffer(currentLine, previousLine);
/// <summary> Returns whether a line is considered used. Only partially covers uninherited lines. /// <br></br><br></br> /// Conditions for an inherited line being used (simplified, only false positives, e.g. hold notes/spinners) <br></br> /// - Changes sampleset, custom index, or volume and there is an edge/body within the time frame <br></br> /// - Changes SV and there are sliders starting within the time frame or changes SV and the mode is mania <br></br> /// - Changes kiai (causes effects on screen during duration) </summary> private bool IsLineUsed(Beatmap beatmap, TimingLine currentLine, TimingLine previousLine) => UsesSamples(beatmap, currentLine, previousLine) || UsesSV(beatmap, currentLine, previousLine) || currentLine.kiai != previousLine.kiai;
private IEnumerable <Issue> GetUninheritedLineIssues(Beatmap beatmap) { List <TimingLine> lines = beatmap.timingLines.ToList(); for (int i = 1; i < lines.Count; ++i) { if (!(lines[i] is UninheritedLine currentLine)) { continue; } // Can't do lines[i - 1] since that could give a green line on the same offset, which we don't want. TimingLine previousLine = beatmap.GetTimingLine(currentLine.offset - 1); UninheritedLine previousUninheritedLine = beatmap.GetTimingLine <UninheritedLine>(currentLine.offset - 1); if (!DownbeatsAlign(beatmap, currentLine, previousUninheritedLine)) { continue; } bool changesNCCymbals = false; if (!NightcoreCymbalsAlign(beatmap, currentLine, previousUninheritedLine)) { changesNCCymbals = true; } bool omittingBarline = false; bool correctingBarline = false; if (CanOmitBarLine(beatmap)) { // e.g. red line used mid-measure to account for bpm change shouldn't create a barline, so it's omitted, but the // end of the measure won't have a barline unless another red line is placed there to correct it, hence both used. omittingBarline = currentLine.omitsBarLine; correctingBarline = previousUninheritedLine.omitsBarLine && !BarLinesAlign(beatmap, currentLine, previousUninheritedLine); // Omitting bar lines isn't commonly seen in standard, so it's likely that people will // miss incorrect usages of it, hence warn if it's the only thing keeping it used. if ((omittingBarline || correctingBarline) && beatmap.generalSettings.mode != Beatmap.Mode.Standard) { continue; } } List <string> notImmediatelyObvious = new List <string>(); if (omittingBarline) { notImmediatelyObvious.Add("omitting first barline"); } if (correctingBarline) { notImmediatelyObvious.Add($"correcting the omitted barline at {Timestamp.Get(previousUninheritedLine.offset)}"); } if (changesNCCymbals) { notImmediatelyObvious.Add("nightcore mod cymbals"); } string notImmediatelyObviousStr = string.Join(" and ", notImmediatelyObvious); if (!IsLineUsed(beatmap, currentLine, previousLine)) { if (notImmediatelyObvious.Count == 0) { yield return(new Issue(GetTemplate("Problem"), beatmap, Timestamp.Get(currentLine.offset))); } else { yield return(new Issue(GetTemplate("Warning"), beatmap, Timestamp.Get(currentLine.offset), notImmediatelyObviousStr)); } } else { if (notImmediatelyObvious.Count == 0) { yield return(new Issue(GetTemplate("Problem Inherited"), beatmap, Timestamp.Get(currentLine.offset))); } else { yield return(new Issue(GetTemplate("Warning Inherited"), beatmap, Timestamp.Get(currentLine.offset), notImmediatelyObviousStr)); } } } }
public override IEnumerable <DiffInstance> Translate(IEnumerable <DiffInstance> aDiffs) { List <Tuple <DiffInstance, TimingLine> > addedTimingLines = new List <Tuple <DiffInstance, TimingLine> >(); List <Tuple <DiffInstance, TimingLine> > removedTimingLines = new List <Tuple <DiffInstance, TimingLine> >(); foreach (DiffInstance diff in aDiffs) { TimingLine timingLine = null; try { timingLine = new TimingLine(diff.difference.Split(','), beatmap: null); } catch { // Failing to parse a changed line shouldn't stop it from showing. } if (timingLine != null) { if (diff.diffType == DiffType.Added) { addedTimingLines.Add(new Tuple <DiffInstance, TimingLine>(diff, timingLine)); } else { removedTimingLines.Add(new Tuple <DiffInstance, TimingLine>(diff, timingLine)); } } else { // Shows the raw .osu line change. yield return(diff); } } foreach (Tuple <DiffInstance, TimingLine> addedTuple in addedTimingLines) { DiffInstance addedDiff = addedTuple.Item1; TimingLine addedLine = addedTuple.Item2; string stamp = Timestamp.Get(addedLine.offset); string type = addedLine.uninherited ? "Uninherited line" : "Inherited line"; bool found = false; foreach (TimingLine removedLine in removedTimingLines.Select(aTuple => aTuple.Item2).ToList()) { if (!addedLine.offset.AlmostEqual(removedLine.offset)) { continue; } string removedType = removedLine.uninherited ? "Uninherited line" : "Inherited line"; if (type != removedType) { continue; } List <string> changes = new List <string>(); if (addedLine.kiai != removedLine.kiai) { changes.Add("Kiai changed from " + (removedLine.kiai ? "enabled" : "disabled") + " to " + (addedLine.kiai ? "enabled" : "disabled") + "."); } if (addedLine.meter != removedLine.meter) { changes.Add("Timing signature changed from " + removedLine.meter + "/4" + " to " + addedLine.meter + "/4."); } if (addedLine.sampleset != removedLine.sampleset) { changes.Add("Sampleset changed from " + removedLine.sampleset.ToString().ToLower() + " to " + addedLine.sampleset.ToString().ToLower() + "."); } if (addedLine.customIndex != removedLine.customIndex) { changes.Add("Custom sampleset index changed from " + removedLine.customIndex.ToString().ToLower() + " to " + addedLine.customIndex.ToString().ToLower() + "."); } if (!addedLine.volume.AlmostEqual(removedLine.volume)) { changes.Add("Volume changed from " + removedLine.volume + " to " + addedLine.volume + "."); } if (type == "Uninherited line") { UninheritedLine addedUninherited = new UninheritedLine(addedLine.code.Split(','), beatmap: null); UninheritedLine removedUninherited = new UninheritedLine(removedLine.code.Split(','), beatmap: null); if (!addedUninherited.bpm.AlmostEqual(removedUninherited.bpm)) { changes.Add("BPM changed from " + removedUninherited.bpm + " to " + addedUninherited.bpm + "."); } } else if (!addedLine.svMult.AlmostEqual(removedLine.svMult)) { changes.Add("Slider velocity multiplier changed from " + removedLine.svMult + " to " + addedLine.svMult + "."); } if (changes.Count == 1) { yield return(new DiffInstance(stamp + changes[0], Section, DiffType.Changed, new List <string>(), addedDiff.snapshotCreationDate)); } else if (changes.Count > 1) { yield return(new DiffInstance(stamp + type + " changed.", Section, DiffType.Changed, changes, addedDiff.snapshotCreationDate)); } found = true; removedTimingLines.RemoveAll(aTuple => aTuple.Item2.code == removedLine.code); } if (!found) { yield return(new DiffInstance(stamp + type + " added.", Section, DiffType.Added, new List <string>(), addedDiff.snapshotCreationDate)); } } foreach (Tuple <DiffInstance, TimingLine> removedTuple in removedTimingLines) { DiffInstance removedDiff = removedTuple.Item1; TimingLine removedLine = removedTuple.Item2; string stamp = Timestamp.Get(removedLine.offset); string type = removedLine.uninherited ? "Uninherited line" : "Inherited line"; yield return(new DiffInstance(stamp + type + " removed.", Section, DiffType.Removed, new List <string>(), removedDiff.snapshotCreationDate)); } }
/// <summary> Returns whether this section changes sample settings (i.e. volume, sampleset, or custom index). </summary> private static bool SamplesDiffer(TimingLine currentLine, TimingLine previousLine) => currentLine.sampleset != previousLine.sampleset || currentLine.customIndex != previousLine.customIndex || !currentLine.volume.AlmostEqual(previousLine.volume);
/// <summary> Returns whether this section is affected by SV changes. </summary> private static bool UsesSV(Beatmap beatmap, TimingLine currentLine, TimingLine previousLine) => CanUseSV(beatmap, currentLine) && !currentLine.svMult.AlmostEqual(previousLine.svMult);