/// <summary> Returns the custom index for the object, if any, otherwise for the line, if any, otherwise 1. </summary> public int GetCustomIndex(TimingLine line = null) { if (line == null) { line = beatmap.GetTimingLine(time); } return(customIndex ?? line?.customIndex ?? 1); }
private List <TimingLine> GetTimingLines(string[] aLines) { // find the [TimingPoints] section and parse each timing line return(ParserStatic.ParseSection(aLines, "TimingPoints", aLine => { string[] args = aLine.Split(','); return TimingLine.IsUninherited(args) ? new UninheritedLine(args) : (TimingLine) new InheritedLine(args); }).ToList()); }
private HitSample GetEdgeSample(double time, Beatmap.Sampleset?sampleset, HitSound?hitSound) { TimingLine line = beatmap.GetTimingLine(time, hitSoundLeniency: true); return (new HitSample( line.customIndex, sampleset ?? line.sampleset, hitSound, HitSample.HitSource.Edge, time)); }
/// <summary> Returns the previous timing line in the timing line list, if any, /// otherwise null, O(1). Optionally skips concurrent objects. </summary> public TimingLine Prev(bool skipConcurrent = false) { TimingLine prev = null; for (int i = timingLineIndex; i >= 0; --i) { prev = beatmap.timingLines[i]; if (!skipConcurrent || prev.offset != offset) { break; } } return(prev); }
/// <summary> Returns the next timing line in the timing line list, if any, /// otherwise null, O(1). Optionally skips concurrent lines. </summary> public TimingLine Next(bool skipConcurrent = false) { TimingLine next = null; for (int i = timingLineIndex; i < beatmap.timingLines.Count; ++i) { next = beatmap.timingLines[i]; if (!skipConcurrent || next.offset != offset) { break; } } return(next); }
/// <summary> Returns all used combinations of customs, samplesets and hit sounds for this object. /// Assumes the game mode is taiko (special rules apply). /// <br></br><br></br> /// Special Rules:<br></br> /// - taiko-hitwhistle plays on big kat <br></br> /// - taiko-hitfinish plays on big don <br></br> /// - taiko-hitclap and taiko-hitnormal are always used as they play whenever the user presses keys /// </summary> public IEnumerable <HitSample> GetUsedHitSamplesTaiko() { TimingLine line = beatmap.GetTimingLine(time, hitSoundLeniency: true); yield return(new HitSample(line?.customIndex ?? 1, line.sampleset, HitSound.Clap, HitSample.HitSource.Edge, line.offset, true)); yield return(new HitSample(line?.customIndex ?? 1, line.sampleset, HitSound.Normal, HitSample.HitSource.Edge, line.offset, true)); bool isKat = HasHitSound(HitSound.Clap) || HasHitSound(HitSound.Whistle); bool isBig = HasHitSound(HitSound.Finish); HitSound hitSound; if (isBig) { if (isKat) { hitSound = HitSound.Whistle; } else { hitSound = HitSound.Finish; } } else if (isKat) { hitSound = HitSound.Clap; } else { hitSound = HitSound.Normal; } // In case the hit object's custom index/sampleset/additions are different from the timing line's. yield return(new HitSample(GetCustomIndex(line), GetSampleset(true), hitSound, HitSample.HitSource.Edge, time, true)); }
/// <summary> Returns all used combinations of customs, samplesets and hit sounds for this object. /// This assumes the game mode is not taiko (special rules apply to taiko only). </summary> private IEnumerable <HitSample> GetUsedHitSamplesNonTaiko() { // Spinners have no impact sound. if (!(this is Spinner)) { // Head foreach (HitSound splitStartHitSound in SplitHitSound(GetStartHitSound().GetValueOrDefault())) { yield return(GetEdgeSample(time, GetStartSampleset(true), splitStartHitSound)); } yield return(GetEdgeSample(time, GetStartSampleset(false), HitSound.Normal)); } // Hold notes can not have a hit sounds on their tails. if (!(this is HoldNote)) { // Tail foreach (HitSound splitEndHitSound in SplitHitSound(GetEndHitSound().GetValueOrDefault())) { yield return(GetEdgeSample(GetEndTime(), GetEndSampleset(true), splitEndHitSound)); } yield return(GetEdgeSample(GetEndTime(), GetEndSampleset(false), HitSound.Normal)); } if (this is Slider slider) { // Reverse for (int i = 0; i < slider.reverseHitSounds.Count; ++i) { HitSound?reverseHitSound = slider.reverseHitSounds.ElementAt(i); double theoreticalStart = time - beatmap.GetTheoreticalUnsnap(time); double reverseTime = Timestamp.Round(theoreticalStart + slider.GetCurveDuration() * (i + 1)); foreach (HitSound splitReverseHitSound in SplitHitSound(reverseHitSound.GetValueOrDefault())) { yield return(GetEdgeSample(reverseTime, slider.GetReverseSampleset(i, true), splitReverseHitSound)); } yield return(GetEdgeSample(reverseTime, slider.GetReverseSampleset(i), HitSound.Normal)); } List <TimingLine> lines = beatmap.timingLines.Where(line => line.offset > slider.time && line.offset <= slider.endTime).ToList(); lines.Add(beatmap.GetTimingLine(slider.time, hitSoundLeniency: true)); // Body, only applies to standard. Catch has droplets instead of body. Taiko and mania have a body but play no background sound. if (beatmap.generalSettings.mode == Beatmap.Mode.Standard) { foreach (TimingLine line in lines) { // Priority: object sampleset > line sampleset // The addition is ignored for sliderslides, it seems. Beatmap.Sampleset effectiveSampleset = sampleset != Beatmap.Sampleset.Auto ? sampleset : line.sampleset; // Additions are not ignored for sliderwhistles, however. if (slider.hitSound == HitSound.Whistle) { effectiveSampleset = addition != Beatmap.Sampleset.Auto ? addition : effectiveSampleset; } // The regular sliderslide will always play regardless of using sliderwhistle. yield return(new HitSample( line.customIndex, effectiveSampleset, HitSound.None, HitSample.HitSource.Body, line.offset)); if (hitSound != HitSound.None) { yield return(new HitSample( line.customIndex, effectiveSampleset, hitSound, HitSample.HitSource.Body, line.offset)); } } } // Tick, only applies to standard and catch. Mania has no ticks, taiko sliders play regular impacts. if (beatmap.generalSettings.mode == Beatmap.Mode.Standard || beatmap.generalSettings.mode == Beatmap.Mode.Catch) { foreach (double tickTime in slider.sliderTickTimes) { TimingLine line = beatmap.GetTimingLine(tickTime); // If no line exists, we use the default settings. int customIndex = line?.customIndex ?? 1; // Unlike the slider body (for sliderwhistles) and edges, slider ticks are unaffected by additions. Beatmap.Sampleset sampleset = GetSampleset(false, tickTime); // Defaults to normal if none is set (before any timing line). if (sampleset == Beatmap.Sampleset.Auto) { sampleset = Beatmap.Sampleset.Normal; } yield return(new HitSample(customIndex, sampleset, null, HitSample.HitSource.Tick, tickTime)); } } } }
/// <summary> Returns all used combinations of customs, samplesets and hit sounds for this object. </summary> public IEnumerable <HitSample> GetUsedHitSamples() { // Head foreach (HitSound splitStartHitSound in SplitHitSound(GetStartHitSound().GetValueOrDefault())) { yield return(GetEdgeSample(time, GetStartSampleset(true), splitStartHitSound)); } yield return(GetEdgeSample(time, GetStartSampleset(false), HitSound.Normal)); // Hold notes can not have a hit sounds on their tails. if (!(this is HoldNote)) { // Tail foreach (HitSound splitEndHitSound in SplitHitSound(GetEndHitSound().GetValueOrDefault())) { yield return(GetEdgeSample(GetEndTime(), GetEndSampleset(true), splitEndHitSound)); } yield return(GetEdgeSample(GetEndTime(), GetEndSampleset(false), HitSound.Normal)); } if (this is Slider slider) { // Reverse for (int i = 0; i < slider.reverseHitSounds.Count; ++i) { HitSound? reverseHitSound = slider.reverseHitSounds.ElementAt(i); Beatmap.Sampleset?reverseSampleset = slider.GetReverseSampleset(i); Beatmap.Sampleset?reverseAddition = slider.reverseAdditions.Any() ? // not a thing in file version 9 slider.reverseAdditions.ElementAt(i) : (Beatmap.Sampleset?)null; double reverseTime = slider.GetCurveDuration() * (i + 1); foreach (HitSound splitReverseHitSound in SplitHitSound(reverseHitSound.GetValueOrDefault())) { yield return(GetEdgeSample(reverseTime, reverseAddition ?? reverseSampleset, splitReverseHitSound)); } yield return(GetEdgeSample(reverseTime, reverseSampleset, HitSound.Normal)); } List <TimingLine> lines = beatmap.timingLines.Where(aLine => aLine.offset > slider.time && aLine.offset <= slider.endTime).ToList(); lines.Add(beatmap.GetTimingLine(slider.time, true)); // Body foreach (TimingLine line in lines) { yield return(new HitSample(line.customIndex, line.sampleset, hitSound, HitSample.HitSource.Body, line.offset)); } // Tick foreach (double tickTime in slider.sliderTickTimes) { TimingLine line = beatmap.GetTimingLine(tickTime); // If no line exists, we use the default settings. int customIndex = line?.customIndex ?? 1; Beatmap.Sampleset sampleset = GetSampleset(true, tickTime); // Defaults to normal if none is set (before any timing line). if (sampleset == Beatmap.Sampleset.Auto) { sampleset = Beatmap.Sampleset.Normal; } yield return(new HitSample(customIndex, sampleset, null, HitSample.HitSource.Tick, tickTime)); } } }