/// <summary> /// Returns true if the two maps are equal by value. /// </summary> /// <param name="other">the Qua to compare to</param> /// <returns></returns> public bool EqualByValue(Qua other) { return(AudioFile == other.AudioFile && SongPreviewTime == other.SongPreviewTime && BackgroundFile == other.BackgroundFile && BannerFile == other.BannerFile && MapId == other.MapId && MapSetId == other.MapSetId && Mode == other.Mode && Title == other.Title && Artist == other.Artist && Source == other.Source && Tags == other.Tags && Creator == other.Creator && DifficultyName == other.DifficultyName && Description == other.Description && Genre == other.Genre && TimingPoints.SequenceEqual(other.TimingPoints, TimingPointInfo.ByValueComparer) && SliderVelocities.SequenceEqual(other.SliderVelocities, SliderVelocityInfo.ByValueComparer) && HitObjects.SequenceEqual(other.HitObjects, HitObjectInfo.ByValueComparer) && CustomAudioSamples.SequenceEqual(other.CustomAudioSamples, CustomAudioSampleInfo.ByValueComparer) && SoundEffects.SequenceEqual(other.SoundEffects, SoundEffectInfo.ByValueComparer) && EditorLayers.SequenceEqual(other.EditorLayers, EditorLayerInfo.ByValueComparer) && RandomizeModifierSeed == other.RandomizeModifierSeed); }
/// <summary> /// Returns true if the two maps are equal by value. /// </summary> /// <param name="other">the Qua to compare to</param> /// <returns></returns> public bool EqualByValue(Qua other) { return(AudioFile == other.AudioFile && SongPreviewTime == other.SongPreviewTime && BackgroundFile == other.BackgroundFile && BannerFile == other.BannerFile && MapId == other.MapId && MapSetId == other.MapSetId && Mode == other.Mode && Title == other.Title && Artist == other.Artist && Source == other.Source && Tags == other.Tags && Creator == other.Creator && DifficultyName == other.DifficultyName && Description == other.Description && Genre == other.Genre && TimingPoints.SequenceEqual(other.TimingPoints, TimingPointInfo.ByValueComparer) && SliderVelocities.SequenceEqual(other.SliderVelocities, SliderVelocityInfo.ByValueComparer) // ReSharper disable once CompareOfFloatsByEqualityOperator && InitialScrollVelocity == other.InitialScrollVelocity && BPMDoesNotAffectScrollVelocity == other.BPMDoesNotAffectScrollVelocity && HasScratchKey == other.HasScratchKey && HitObjects.SequenceEqual(other.HitObjects, HitObjectInfo.ByValueComparer) && CustomAudioSamples.SequenceEqual(other.CustomAudioSamples, CustomAudioSampleInfo.ByValueComparer) && SoundEffects.SequenceEqual(other.SoundEffects, SoundEffectInfo.ByValueComparer) && EditorLayers.SequenceEqual(other.EditorLayers, EditorLayerInfo.ByValueComparer) && RandomizeModifierSeed == other.RandomizeModifierSeed); }
/// <summary> /// Does some sorting of the Qua /// </summary> public void Sort() { HitObjects = HitObjects.OrderBy(x => x.StartTime).ToList(); TimingPoints = TimingPoints.OrderBy(x => x.StartTime).ToList(); SliderVelocities = SliderVelocities.OrderBy(x => x.StartTime).ToList(); SoundEffects = SoundEffects.OrderBy(x => x.StartTime).ToList(); }
/// <summary> /// Calculates the size of the effective time range of a given timing point. /// This range stops at the next timing point, so it just returns the offset of the next timing point. /// </summary> /// <param name="timingPoint"></param> /// <returns>The timing point after specified timing point.</returns> public double GetTimingPointEffectiveRange(TimingPoint timingPoint) { foreach (var tp in TimingPoints.Where(tp => Precision.DefinitelyBigger(tp.Offset, timingPoint.Offset))) { return(tp.Offset); } return(double.PositiveInfinity); // Being the last timingpoint, the effective range is infinite (very big) }
/// <summary> /// Gets the timing point at a particular time in the map. /// </summary> /// <param name="time"></param> /// <returns></returns> public TimingPointInfo GetTimingPointAt(double time) { var index = TimingPoints.FindLastIndex(x => x.StartTime <= time); // If the point can't be found, we want to return either null if there aren't // any points, or the first timing point, since it'll be considered as apart of it anyway. if (index == -1) { return(TimingPoints.Count == 0 ? null : TimingPoints.First()); } return(TimingPoints[index]); }
//todo: not optimized public void WriteOsuFile(string path, string newDiffName = null) { File.WriteAllText(path, string.Format("osu file format v{0}\r\n\r\n{1}{2}{3}{4}{5}{6}{7}{8}", Version, General?.ToSerializedString(), Editor?.ToSerializedString(), Metadata?.ToSerializedString(newDiffName), Difficulty?.ToSerializedString(), Events?.ToSerializedString(), TimingPoints?.ToSerializedString(), Colours?.ToSerializedString(), HitObjects?.ToSerializedString())); }
public static void UpdateActiveTimingPoint(bool runSort) { if (TimingPoints != null && TimingPoints.Count > 0) { activeTimingPointIndex = Math.Max(0, TimingPoints.FindLastIndex(delegate(TimingPoint t) { return(t.offset <= Time + 50); })); if (runSort) { TimingPoints.Sort(); } } else { activeTimingPointIndex = -1; } }
public void Dispose() { if (_disposed) { return; } _disposed = true; _currentTiming = CurrentFrameTiming.OutOfFrameLoop; Layers.AbortAllLayers(); TimingPoints.AbortAllEvents(); Lights.Release(); ContextAssociatedMemorySafety.EnsureCollect(OwnerScreen); // Must be called before the opengl context is deleted. Disposed?.Invoke(); }
/// <summary> /// Finds the length of a timing point. /// </summary> /// <param name="point"></param> /// <returns></returns> public double GetTimingPointLength(TimingPointInfo point) { // Find the index of the current timing point. var index = TimingPoints.IndexOf(point); // ?? if (index == -1) { throw new ArgumentException(); } // There is another timing point ahead of this one // so we'll need to get the length of the two points. if (index + 1 < TimingPoints.Count) { return(TimingPoints[index + 1].StartTime - TimingPoints[index].StartTime); } // Only one timing point, so we can assume that it goes to the end of the map. return(Length - point.StartTime); }
public TimingPoint GetFirstTimingPointExtended() { // Add an extra timingpoint that is the same as the first redline but like 10 x meter beats earlier so any objects before the first redline can use that thing // When you have a greenline before the first redline, the greenline will act like the first redline and you can snap objects to the greenline's bpm. // The value in the greenline will be used as the milliseconds per beat, so for example a 1x SliderVelocity slider will be 600 bpm. // The timeline will work like a redline on 0 offset and 1000 milliseconds per beat TimingPoint firstTp = TimingPoints.FirstOrDefault(); if (firstTp != null && firstTp.Uninherited) { return(new TimingPoint(firstTp.Offset - firstTp.MpB * firstTp.Meter.TempoDenominator * 10, firstTp.MpB, firstTp.Meter, firstTp.SampleSet, firstTp.SampleIndex, firstTp.Volume, firstTp.Uninherited, false, false)); } if (firstTp != null) { return(new TimingPoint(0, 1000, firstTp.Meter, firstTp.SampleSet, firstTp.SampleIndex, firstTp.Volume, firstTp.Uninherited, false, false)); } return(new TimingPoint(0, 0, 0, SampleSet.Auto, 0, 0, true, false, false)); }
private Chart GenerateTimingLineChart() { if (TimingPoints.Count == 0) { return(null); } Chart chart; double timingMax = Math.Ceiling(TimingPoints.Select(x => (x.Value == null ? 0 : (double)x.Value)).Max()); double bandwidthMax = Math.Ceiling((SpeedBandwidth == null ? 0 : (double)SpeedBandwidth)); ChartArea area; ChartSeries series; area = new ChartArea(); // style x axis area.AxisX.MajorGrid.Enabled = false; area.AxisX.IsLabelAutoFit = false; //area.AxisX.LabelAutoFitMinFontSize = 20; //area.AxisX.LabelStyle.Format = "MM/dd/yyyy"; area.AxisX.LabelStyle.Font = new Font(FontFamily.GenericSansSerif, 15); area.AxisX.IntervalAutoMode = IntervalAutoMode.VariableCount; area.AxisX.Maximum = TimingPoints.Count + 1; area.AxisX.Minimum = 0; //area.AxisX.Interval = 1; //area.AxisX.MajorTickMark.Interval = 1; area.AxisX.TextOrientation = TextOrientation.Rotated90; area.AxisX.LabelStyle.Angle = -45; //area.AxisX.LabelStyle.IsEndLabelVisible = true; //area.AxisX.LabelStyle.IsStaggered = true; // style y axis //area.AxisY.MajorGrid.LineColor = Color.LightGray; area.AxisY.Maximum = (bandwidthMax > timingMax ? bandwidthMax : timingMax); area.AxisY.Minimum = 0; area.AxisY.Interval = 1; area.AxisY.LabelAutoFitMinFontSize = 15; area.AxisY.LabelStyle.Format = "0"; //create chart chart = new Chart(); chart.Width = 1200; chart.Height = 500; chart.Name = "Waveform Timing"; Legend legend = new Legend("Legend"); legend.Font = new Font(FontFamily.GenericSansSerif, 15); chart.Legends.Add(legend); chart.ChartAreas.Add(area); series = new ChartSeries("Waveform Timing"); series.ChartType = SeriesChartType.Line; series.BorderWidth = 2; series.Color = Color.Black; series.IsValueShownAsLabel = true; int index = 1; foreach (var point in TimingPoints) { area.AxisX.CustomLabels.Add(index, index + .9, point.Time.ToString("MM/dd/yyyy")); if (point.Value == null) { //series.Points.AddY(index++); } else { DataPoint dataPoint = new DataPoint(index++, (double)point.Value); series.Points.Add(dataPoint); } } chart.Series.Add(series); if (SpeedBandwidth != null) { chart.Series.Add(MakeLimitSeries((double)SpeedBandwidth, Color.Red, "12% Bandwidth", 0, TimingPoints.Count + 1)); } if (Speed != null) { chart.Series.Add(MakeLimitSeries((double)Speed, Color.Orange, "MFR Speed", 0, TimingPoints.Count + 1)); } return(chart); }
/// <summary> /// Ctor - Automatically parses a Peppy beatmap /// </summary> public OsuBeatmap(string filePath) { Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; if (!File.Exists(filePath.Trim())) { IsValid = false; return; } // Create a new beatmap object and default the validity to true. IsValid = true; OriginalFileName = filePath; // This will hold the section of the beatmap that we are parsing. var section = ""; try { foreach (var raw_line in File.ReadAllLines(filePath)) { // Skip empty lines and comments. if (string.IsNullOrWhiteSpace(raw_line) || raw_line.StartsWith("//", StringComparison.Ordinal) || raw_line.StartsWith(" ", StringComparison.Ordinal) || raw_line.StartsWith("_", StringComparison.Ordinal)) { continue; } var line = StripComments(raw_line); switch (line.Trim()) { case "[General]": section = "[General]"; break; case "[Editor]": section = "[Editor]"; break; case "[Metadata]": section = "[Metadata]"; break; case "[Difficulty]": section = "[Difficulty]"; break; case "[Events]": section = "[Events]"; break; case "[TimingPoints]": section = "[TimingPoints]"; break; case "[HitObjects]": section = "[HitObjects]"; break; case "[Colours]": section = "[Colours]"; break; default: break; } // Parse Peppy file format if (line.StartsWith("osu file format")) { PeppyFileFormat = line; } // Parse [General] Section if (section.Equals("[General]")) { if (line.Contains(":")) { var key = line.Substring(0, line.IndexOf(':')); var value = line.Split(':').Last().Trim(); switch (key.Trim()) { case "AudioFilename": AudioFilename = Encoding.ASCII.GetString(Encoding.ASCII.GetBytes(value)); break; case "AudioLeadIn": AudioLeadIn = int.Parse(value, CultureInfo.InvariantCulture); break; case "PreviewTime": PreviewTime = int.Parse(value, CultureInfo.InvariantCulture); break; case "Countdown": Countdown = int.Parse(value, CultureInfo.InvariantCulture); break; case "SampleSet": SampleSet = Encoding.ASCII.GetString(Encoding.ASCII.GetBytes(value));; break; case "StackLeniency": StackLeniency = float.Parse(value, CultureInfo.InvariantCulture); break; case "Mode": Mode = int.Parse(value, CultureInfo.InvariantCulture); if (Mode != 3) { IsValid = false; } break; case "LetterboxInBreaks": LetterboxInBreaks = int.Parse(value, CultureInfo.InvariantCulture); break; case "SpecialStyle": SpecialStyle = int.Parse(value, CultureInfo.InvariantCulture); break; case "WidescreenStoryboard": WidescreenStoryboard = int.Parse(value, CultureInfo.InvariantCulture); break; } } } // Parse [Editor] Data if (section.Equals("[Editor]")) { if (line.Contains(":")) { var key = line.Substring(0, line.IndexOf(':')); var value = line.Split(':').Last(); switch (key.Trim()) { case "Bookmarks": Bookmarks = value; break; case "DistanceSpacing": DistanceSpacing = float.Parse(value, CultureInfo.InvariantCulture); break; case "BeatDivisor": BeatDivisor = int.Parse(value, CultureInfo.InvariantCulture); break; case "GridSize": GridSize = int.Parse(value, CultureInfo.InvariantCulture); break; case "TimelineZoom": TimelineZoom = float.Parse(value, CultureInfo.InvariantCulture); break; } } } // Parse [Metadata] Data if (section.Equals("[Metadata]")) { if (line.Contains(":")) { var key = line.Substring(0, line.IndexOf(':')); var value = line.Substring(line.IndexOf(':') + 1); switch (key.Trim()) { case "Title": Title = Encoding.ASCII.GetString(Encoding.ASCII.GetBytes(value)); break; case "TitleUnicode": TitleUnicode = value; break; case "Artist": Artist = Encoding.ASCII.GetString(Encoding.ASCII.GetBytes(value)); break; case "ArtistUnicode": ArtistUnicode = value; break; case "Creator": Creator = Encoding.ASCII.GetString(Encoding.ASCII.GetBytes(value)); break; case "Version": Version = Encoding.ASCII.GetString(Encoding.ASCII.GetBytes(value)); break; case "Source": Source = Encoding.ASCII.GetString(Encoding.ASCII.GetBytes(value)); break; case "Tags": Tags = Encoding.ASCII.GetString(Encoding.ASCII.GetBytes(value)); break; case "BeatmapID": BeatmapID = int.Parse(value, CultureInfo.InvariantCulture); break; case "BeatmapSetID": BeatmapSetID = int.Parse(value, CultureInfo.InvariantCulture); break; default: break; } } } // Parse [Difficulty] Data if (section.Equals("[Difficulty]")) { if (line.Contains(":")) { var key = line.Substring(0, line.IndexOf(':')); var value = line.Split(':').Last(); switch (key.Trim()) { case "HPDrainRate": HPDrainRate = float.Parse(value, CultureInfo.InvariantCulture); break; case "CircleSize": KeyCount = int.Parse(value, CultureInfo.InvariantCulture); if (KeyCount != 4 && KeyCount != 7 && KeyCount != 5 && KeyCount != 8) { IsValid = false; } break; case "OverallDifficulty": OverallDifficulty = float.Parse(value, CultureInfo.InvariantCulture); break; case "ApproachRate": ApproachRate = float.Parse(value, CultureInfo.InvariantCulture); break; case "SliderMultiplier": SliderMultiplier = float.Parse(value, CultureInfo.InvariantCulture); break; case "SliderTickRate": SliderTickRate = float.Parse(value, CultureInfo.InvariantCulture); break; } } } // Parse [Events] Data if (section.Equals("[Events]")) { var values = line.Split(','); // Background if (line.ToLower().Contains("png") || line.ToLower().Contains("jpg") || line.ToLower().Contains("jpeg")) { Background = values[2].Replace("\"", ""); } // Sound effects if (values[0] == "Sample" || values[0] == "5") { var path = values[3].Trim('"').Replace(Path.DirectorySeparatorChar, '/'); SoundEffects.Add(new OsuSampleInfo() { StartTime = int.Parse(values[1]), Layer = int.Parse(values[2]), Volume = Math.Max(0, Math.Min(100, values.Length >= 5 ? int.Parse(values[4], CultureInfo.InvariantCulture) : 100)), Sample = CustomAudioSampleIndex(path) }); } } try { // Parse [TimingPoints] Data if (section.Equals("[TimingPoints]")) { if (line.Contains(",")) { var values = line.Split(','); // Parse as double because there are some maps which have this value too large to fit in // a float, for example https://osu.ppy.sh/beatmapsets/681731#mania/1441497. Parsing as // double and then casting to float results in the correct outcome though. var msecPerBeat = double.Parse(values[1], CultureInfo.InvariantCulture); var timingPoint = new OsuTimingPoint { Offset = float.Parse(values[0], CultureInfo.InvariantCulture), MillisecondsPerBeat = (float)msecPerBeat, Signature = values[2][0] == '0' ? TimeSignature.Quadruple : (TimeSignature)int.Parse(values[2], CultureInfo.InvariantCulture), SampleType = int.Parse(values[3], CultureInfo.InvariantCulture), SampleSet = int.Parse(values[4], CultureInfo.InvariantCulture), Volume = Math.Max(0, int.Parse(values[5], CultureInfo.InvariantCulture)), Inherited = int.Parse(values[6], CultureInfo.InvariantCulture), KiaiMode = int.Parse(values[7], CultureInfo.InvariantCulture) }; TimingPoints.Add(timingPoint); } } } catch (Exception e) { IsValid = false; } // Parse [HitObjects] Data if (section.Equals("[HitObjects]")) { if (line.Contains(",")) { var values = line.Split(','); // We'll need to parse LNs differently than normal HitObjects, // as they have a different syntax. 128 in the object's type // signifies that it is an LN var osuHitObject = new OsuHitObject { X = int.Parse(values[0], CultureInfo.InvariantCulture), Y = int.Parse(values[1], CultureInfo.InvariantCulture), StartTime = int.Parse(values[2], CultureInfo.InvariantCulture), Type = (HitObjectType)int.Parse(values[3], CultureInfo.InvariantCulture), HitSound = (HitSoundType)int.Parse(values[4], CultureInfo.InvariantCulture), Additions = "0:0:0:0:", KeySound = -1 }; // If it's an LN, we'll want to add the object's EndTime as well. if (osuHitObject.Type.HasFlag(HitObjectType.Hold)) { var endTime = values[5].Substring(0, values[5].IndexOf(":", StringComparison.Ordinal)); osuHitObject.EndTime = int.Parse(endTime, CultureInfo.InvariantCulture); } // Parse the key sound. if (values.Length > 5) { var additions = values[5].Split(':'); var volumeField = osuHitObject.Type.HasFlag(HitObjectType.Hold) ? 4 : 3; if (additions.Length > volumeField && additions[volumeField].Length > 0) { osuHitObject.Volume = Math.Max(0, int.Parse(additions[volumeField], CultureInfo.InvariantCulture)); } var keySoundField = volumeField + 1; if (additions.Length > keySoundField && additions[keySoundField].Length > 0) { osuHitObject.KeySound = CustomAudioSampleIndex(additions[keySoundField]); } } HitObjects.Add(osuHitObject); } } } } catch (Exception e) { IsValid = false; } }
/// <summary> /// </summary> public void SortTimingPoints() => TimingPoints = TimingPoints.OrderBy(x => x.StartTime).ToList();
/// <summary> /// Replaces regular notes with long notes and vice versa. /// /// HitObjects and TimingPoints MUST be sorted by StartTime prior to calling this method, /// see <see cref="Sort()"/>. /// </summary> public void ApplyInverse() { // Minimal LN and gap lengths in milliseconds. // // Ideally this should be computed in a smart way using the judgements so that it is always possible to get // perfects, but making map mods depend on the judgements (affected by strict/chill/accuracy adjustments) is // a really bad idea. I'm setting these to values that will probably work fine for the majority of the // cases. const int MINIMAL_LN_LENGTH = 36; const int MINIMAL_GAP_LENGTH = 36; var newHitObjects = new List <HitObjectInfo>(); // An array indicating whether the currently processed HitObject is the first in its lane. var firstInLane = new bool[GetKeyCount()]; for (var i = 0; i < firstInLane.Length; i++) { firstInLane[i] = true; } for (var i = 0; i < HitObjects.Count; i++) { var currentObject = HitObjects[i]; // Find the next and second next hit object in the lane. HitObjectInfo nextObjectInLane = null, secondNextObjectInLane = null; for (var j = i + 1; j < HitObjects.Count; j++) { if (HitObjects[j].Lane == currentObject.Lane) { if (nextObjectInLane == null) { nextObjectInLane = HitObjects[j]; } else { secondNextObjectInLane = HitObjects[j]; break; } } } var isFirstInLane = firstInLane[currentObject.Lane - 1]; firstInLane[currentObject.Lane - 1] = false; // If this is the only object in its lane, keep it as is. if (nextObjectInLane == null && isFirstInLane) { newHitObjects.Add(currentObject); continue; } // Figure out the time gap between the end of the LN which we'll create and the next object. int?timeGap = null; if (nextObjectInLane != null) { var timingPoint = GetTimingPointAt(nextObjectInLane.StartTime); float bpm; // If the timing point starts at the next object, we want to use the previous timing point's BPM. // For example, consider a fast section of the map transitioning into a very low BPM ending starting // with the next hit object. Since the LN release and the gap are still in the fast section, they // should use the fast section's BPM. if ((int)Math.Round(timingPoint.StartTime) == nextObjectInLane.StartTime) { var prevTimingPointIndex = TimingPoints.FindLastIndex(x => x.StartTime < timingPoint.StartTime); // No timing points before the object? Just use the first timing point then, it has the correct // BPM. if (prevTimingPointIndex == -1) { prevTimingPointIndex = 0; } bpm = TimingPoints[prevTimingPointIndex].Bpm; } else { bpm = timingPoint.Bpm; } // The time gap is quarter of the milliseconds per beat. timeGap = (int?)Math.Max(Math.Round(15000 / bpm), MINIMAL_GAP_LENGTH); } // Summary of the changes: // Regular 1 -> Regular 2 => LN until 2 - time gap // Regular 1 -> LN 2 => LN until 2 // LN 1 -> Regular 2 => LN from 1 end until 2 - time gap // LN 1 -> LN 2 => LN from 1 end until 2 // // Exceptions: // - last LNs are kept (treated as regular 2) // - last regular objects are removed and treated as LN 2 if (currentObject.IsLongNote) { // LNs before regular objects are changed so they start where they ended and end a time gap before // the object. // LNs before LNs do the same but without a time gap. if (nextObjectInLane == null) { // If this is the last object in its lane, though, then it's probably a better idea // to leave it be. For example, finishing long LNs in charts. } else { currentObject.StartTime = currentObject.EndTime; // (this part can mess up the ordering) currentObject.EndTime = nextObjectInLane.StartTime - timeGap.Value; // Clear the keysounds as we're moving the start, so they won't make sense. currentObject.KeySounds = new List <KeySoundInfo>(); // If the next object is not an LN and it's the last object in the lane, or if it's an LN and // not the last object in the lane, create a regular object at the next object's start position. if ((secondNextObjectInLane == null) != nextObjectInLane.IsLongNote) { currentObject.EndTime = nextObjectInLane.StartTime; } // Filter out really short LNs or even negative length resulting from jacks or weird BPM values. if (currentObject.EndTime - currentObject.StartTime < MINIMAL_LN_LENGTH) { // These get skipped entirely. // // Actually, there can be a degenerate pattern of multiple LNs with really short gaps // in between them (less than MINIMAL_LN_LENGTH), which this logic will convert // into nothing. That should be pretty rare though. continue; } } } else { // Regular objects are replaced with LNs starting from their start and ending quarter of a beat // before the next object's start. if (nextObjectInLane == null) { // If this is the last object in lane, though, then it's not included, and instead the previous // LN spans up to this object's StartTime. continue; } currentObject.EndTime = nextObjectInLane.StartTime - timeGap.Value; // If the next object is not an LN and it's the last object in the lane, or if it's an LN and // not the last object in the lane, this LN should span until its start. if ((secondNextObjectInLane == null) == (nextObjectInLane.EndTime == 0)) { currentObject.EndTime = nextObjectInLane.StartTime; } // Filter out really short LNs or even negative length resulting from jacks or weird BPM values. if (currentObject.EndTime - currentObject.StartTime < MINIMAL_LN_LENGTH) { // These get converted back into regular objects. currentObject.EndTime = 0; } } newHitObjects.Add(currentObject); } // LN conversion can mess up the ordering, so sort it again. See the (this part can mess up the ordering) // comment above. HitObjects = newHitObjects.OrderBy(x => x.StartTime).ToList(); }
/// <summary> /// Finds the timing control point that is active at <paramref name="time"/>. /// </summary> /// <param name="time">The time to find the timing control point at.</param> /// <returns>The timing control point.</returns> public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.FirstOrDefault());
/// <summary> /// Finds all the timing points in a specified time range. /// </summary> /// <param name="startTime"></param> /// <param name="endTime"></param> /// <returns></returns> public List <TimingPoint> GetTimingPointsInTimeRange(double startTime, double endTime) { return(TimingPoints.Where(tp => Precision.DefinitelyBigger(tp.Offset, startTime) && Precision.DefinitelyBigger(endTime, tp.Offset)).ToList()); }
public void ParseLevel(string filename) { // Read the file and display it line by line. string line; bool timingPointsStart = false; bool hitEventsStart = false; string[] tmp; System.IO.StreamReader file = new System.IO.StreamReader(filename); while ((line = file.ReadLine()) != null) { System.Console.WriteLine(line); line = line.Trim(); if (line.Length == 0 || line[0] == '/') { continue; } //Debug.Log(line); if (line == "[TimingPoints]") { //Debug.Log("TimingPoints start"); hitEventsStart = false; timingPointsStart = true; continue; } if (line == "[HitEvents]") { //Debug.Log("hitobject start"); hitEventsStart = true; timingPointsStart = false; continue; } if (hitEventsStart) { //Debug.Log(line); tmp = line.Split(','); HitEvent hitEvents = new HitEvent(); hitEvents.setKey(tmp[0]); hitEvents.setOffset(tmp[1]); hitEvents.setIsNote(tmp[2]); hitEvents.setIsMine(tmp[2]); hitEvents.setColour(tmp[2]); hitEvents.setFlashBlack(tmp[2]); hitEvents.setIsHold(tmp[2]); if (tmp.Length < 5) { if (tmp.Length == 4) { hitEvents.setColorArray(tmp[3]); } } else { hitEvents.setEndOffset(tmp[3]); hitEvents.setColorArray(tmp[4]); } GameManager.Instance.hitEventsList.Add(hitEvents); } if (timingPointsStart) { tmp = line.Split(','); TimingPoints timingPoints = new TimingPoints(Convert.ToInt32(tmp[0]), Convert.ToSingle(tmp[1]), Convert.ToInt32(tmp[2]), Convert.ToInt32(tmp[3]), Convert.ToInt32(tmp[4])); GameManager.Instance.timingPointsList.Add(timingPoints); } } file.Close(); }
// based off of Quaver.API.Maps.Qua.EqualByValue() public bool Equals(Block other) { return(HitObjects.SequenceEqual(other.HitObjects, HitObjectInfo.ByValueComparer) && TimingPoints.SequenceEqual(other.TimingPoints, TimingPointInfo.ByValueComparer) && ScrollVelocities.SequenceEqual(other.ScrollVelocities, SliderVelocityInfo.ByValueComparer)); }
void loadLevel() { Dictionary <string, string> song = SongSelectParser.Instance.selectedSong; string filename = song["SongMap"]; AudioClip audioClip = Resources.Load <AudioClip>(song["SongPreview"]); songLength = audioClip.length; Debug.Log(songLength); audioSource.PlayOneShot(audioClip); string line; bool timingPointsStart = false; bool hitObjectsStart = false; string[] tmp; // Read the file and display it line by line. System.IO.StreamReader file = new System.IO.StreamReader(filename); while ((line = file.ReadLine()) != null) { System.Console.WriteLine(line); line = line.Trim(); if (line.Length == 0 || line[0] == '/') { continue; } //Debug.Log(line); if (line == "#TimingPoints") { //Debug.Log("TimingPoints start"); hitObjectsStart = false; timingPointsStart = true; continue; } if (line == "#HitObjects") { //Debug.Log("hitobject start"); hitObjectsStart = true; timingPointsStart = false; continue; } if (hitObjectsStart) { //Debug.Log(line); tmp = line.Split(','); HitObject hitObjects = new HitObject(); hitObjects.setX(tmp[0]); hitObjects.setY(tmp[1]); hitObjects.setOffset(tmp[2]); hitObjects.setIsNote(tmp[3]); hitObjects.setIsMine(tmp[3]); hitObjects.setColour(tmp[3]); hitObjects.setFlashBlack(tmp[3]); hitObjects.setIsHold(tmp[3]); hitObjectsList.Add(hitObjects); } if (timingPointsStart) { tmp = line.Split(','); TimingPoints timingPoints = new TimingPoints(Convert.ToInt32(tmp[0]), Convert.ToDouble(tmp[1]), Convert.ToInt32(tmp[2]), Convert.ToInt32(tmp[3]), Convert.ToInt32(tmp[4])); timingPointsList.Add(timingPoints); } } file.Close(); }
/// <summary> /// Sorts all <see cref="TimingPoint"/> in order of time. /// </summary> public void Sort() { TimingPoints = TimingPoints.OrderBy(o => o.Offset).ThenByDescending(o => o.Uninherited).ToList(); }
public List <TimingPoint> GetAllGreenlines() { return(TimingPoints.Where(tp => !tp.Uninherited).ToList()); }
/// <summary> /// Finds the nearest uninherited timing point which starts after a given time. /// </summary> /// <param name="time"></param> /// <returns></returns> public TimingPoint GetRedlineAfterTime(double time) { return(TimingPoints.FirstOrDefault(tp => Precision.DefinitelyBigger(tp.Offset, time) && tp.Uninherited)); }