// Gets max velocity in SV private static double GetMaxVelocity(SlideratorVm viewModel, IReadOnlyList <IGraphAnchor> anchors) { double maxValue; if (viewModel.GraphModeSetting == SlideratorVm.GraphMode.Velocity) // Integrate the graph to get the end value // Here we use SvGraphMultiplier to get an accurate conversion from SV to slider completion per beat // Completion = (100 * SliderMultiplier / PixelLength) * SV * Beats { maxValue = Math.Max(AnchorCollection.GetMaxValue(anchors), -AnchorCollection.GetMinValue(anchors)); } else { maxValue = Math.Max(AnchorCollection.GetMaxDerivative(anchors), -AnchorCollection.GetMinDerivative(anchors)) / viewModel.SvGraphMultiplier; } return(maxValue); }
private static double GetMinCompletion(SlideratorVm viewModel, IReadOnlyList <Anchor> anchors) { double minValue; if (viewModel.GraphMode == GraphMode.Velocity) // Integrate the graph to get the end value // Here we use SvGraphMultiplier to get an accurate conversion from SV to slider completion per beat // Completion = (100 * SliderMultiplier / PixelLength) * SV * Beats { minValue = AnchorCollection.GetMinIntegral(anchors) * viewModel.SvGraphMultiplier; } else { minValue = AnchorCollection.GetMinValue(anchors); } return(minValue); }
private string Sliderate(SlideratorVm arg, BackgroundWorker worker) { // Make a position function for Sliderator Classes.Tools.Sliderator.PositionFunctionDelegate positionFunction; // Test if the function is a constant velocity bool constantVelocity; // We convert the graph GetValue function to a function that works like ms -> px // d is a value representing the number of milliseconds into the slider if (arg.GraphModeSetting == SlideratorVm.GraphMode.Velocity) { // Here we use SvGraphMultiplier to get an accurate conversion from SV to slider completion per beat // Completion = (100 * SliderMultiplier / PixelLength) * SV * Beats positionFunction = d => arg.GraphState.GetIntegral(0, d * arg.BeatsPerMinute / 60000) * arg.SvGraphMultiplier * arg.PixelLength; constantVelocity = Precision.AlmostEquals(AnchorCollection.GetMaxValue(arg.GraphState.Anchors), AnchorCollection.GetMinValue(arg.GraphState.Anchors)); } else { positionFunction = d => arg.GraphState.GetValue(d * arg.BeatsPerMinute / 60000) * arg.PixelLength; constantVelocity = Precision.AlmostEquals(AnchorCollection.GetMaxDerivative(arg.GraphState.Anchors), AnchorCollection.GetMinDerivative(arg.GraphState.Anchors)); } // Dont do Sliderator if the velocity is constant AND equal to the new velocity var simplifyShape = constantVelocity && Precision.AlmostEquals( arg.PixelLength / arg.GraphBeats / arg.GlobalSv / 100, arg.NewVelocity); // Get the highest velocity occuring in the graph double velocity = arg.NewVelocity; // Velocity is in SV // Do bad stuff to the velocity to make sure its the same SV as after writing it to .osu code velocity = -100 / double.Parse((-100 / velocity).ToInvariant(), CultureInfo.InvariantCulture); // Other velocity is in px / ms var otherVelocity = velocity * arg.SvGraphMultiplier * arg.PixelLength * arg.BeatsPerMinute / 60000; // Time between timeline ticks for stream export var deltaT = 60000 / arg.BeatsPerMinute / arg.BeatSnapDivisor; // Update progressbar if (worker != null && worker.WorkerReportsProgress) { worker.ReportProgress(10); } List <Vector2> slideration = new List <Vector2>(); var sliderator = new Classes.Tools.Sliderator { PositionFunction = positionFunction, MaxT = arg.GraphBeats / arg.BeatsPerMinute * 60000, Velocity = otherVelocity, MinDendriteLength = arg.MinDendrite }; if (!simplifyShape) { // Get slider path like from the hit object preview var sliderPath = new SliderPath(arg.VisibleHitObject.SliderType, arg.VisibleHitObject.GetAllCurvePoints().ToArray(), GetMaxCompletion(arg, arg.GraphState.Anchors) * arg.PixelLength); var path = new List <Vector2>(); sliderPath.GetPathToProgress(path, 0, 1); // Update progressbar if (worker != null && worker.WorkerReportsProgress) { worker.ReportProgress(20); } // Do Sliderator sliderator.SetPath(path); slideration = arg.ExportAsStream ? sliderator.SliderateStream(deltaT) : sliderator.Sliderate(); // Check for some illegal output if (double.IsInfinity(sliderator.MaxS) || double.IsNaN(sliderator.MaxS) || slideration.Any(v => double.IsNaN(v.X) || double.IsNaN(v.Y))) { return("Encountered unexpected values from Sliderator. Please check your input."); } } // Update progressbar if (worker != null && worker.WorkerReportsProgress) { worker.ReportProgress(60); } // Exporting stuff BeatmapEditor editor; bool editorRead = false; if (arg.DoEditorRead) { editor = EditorReaderStuff.GetNewestVersionOrNot(arg.Path, out _, out var exception); if (exception == null) { editorRead = true; } arg.DoEditorRead = false; } else { editor = new BeatmapEditor(arg.Path); } var beatmap = editor.Beatmap; var timing = beatmap.BeatmapTiming; // Get hit object that might be present at the export time or make a new one var hitObjectHere = beatmap.HitObjects.FirstOrDefault(o => Math.Abs(arg.ExportTime - o.Time) < 5) ?? new HitObject(arg.ExportTime, 0, SampleSet.Auto, SampleSet.Auto); // Clone the hit object to not affect the already existing hit object instance with changes var clone = new HitObject(hitObjectHere.GetLine()) { IsCircle = arg.ExportAsStream, IsSpinner = false, IsHoldNote = false, IsSlider = !arg.ExportAsStream }; // Update progressbar if (worker != null && worker.WorkerReportsProgress) { worker.ReportProgress(70); } if (!arg.ExportAsStream) { // Give the new hit object the sliderated anchors if (simplifyShape) { // The velocity is constant, so you can simplify to the original slider shape clone.SetAllCurvePoints(arg.VisibleHitObject.GetAllCurvePoints()); clone.SliderType = arg.VisibleHitObject.SliderType; } else { clone.SetAllCurvePoints(slideration); clone.SliderType = PathType.Bezier; } clone.PixelLength = sliderator.MaxS; clone.SliderVelocity = -100 / velocity; // Add hit object if (arg.ExportModeSetting == SlideratorVm.ExportMode.Add) { beatmap.HitObjects.Add(clone); } else { beatmap.HitObjects.Remove(hitObjectHere); beatmap.HitObjects.Add(clone); } // Add SV var timingPointsChanges = new List <TimingPointsChange>(); if (arg.DelegateToBpm) { var tpAfter = timing.GetRedlineAtTime(clone.Time).Copy(); var tpOn = tpAfter.Copy(); tpAfter.Offset = clone.Time; tpOn.Offset = clone.Time - 1; // This one will be on the slider tpAfter.OmitFirstBarLine = true; tpOn.OmitFirstBarLine = true; // Express velocity in BPM tpOn.MpB /= -100 / clone.SliderVelocity; // NaN SV results in removal of slider ticks clone.SliderVelocity = arg.RemoveSliderTicks ? double.NaN : -100; // Add redlines timingPointsChanges.Add(new TimingPointsChange(tpOn, mpb: true, inherited: true, omitFirstBarLine: true, fuzzyness: 0)); timingPointsChanges.Add(new TimingPointsChange(tpAfter, mpb: true, inherited: true, omitFirstBarLine: true, fuzzyness: 0)); clone.Time -= 1; } // Add SV for every hit object so the SV doesnt change for anything else than the sliderated slider timingPointsChanges.AddRange(beatmap.HitObjects.Select(ho => { var sv = ho == clone ? ho.SliderVelocity : timing.GetSvAtTime(ho.Time); var tp = timing.GetTimingPointAtTime(ho.Time).Copy(); tp.MpB = sv; tp.Offset = ho.Time; return(new TimingPointsChange(tp, mpb: true, fuzzyness: 0)); })); TimingPointsChange.ApplyChanges(timing, timingPointsChanges); } else { // Add hit objects if (arg.ExportModeSetting == SlideratorVm.ExportMode.Override) { beatmap.HitObjects.Remove(hitObjectHere); } double t = arg.ExportTime; foreach (var pos in slideration) { clone.Pos = pos; clone.Time = t; beatmap.HitObjects.Add(clone); clone = new HitObject(clone.GetLine()) { IsCircle = true, IsSpinner = false, IsHoldNote = false, IsSlider = false, NewCombo = false }; t += deltaT; } } // Update progressbar if (worker != null && worker.WorkerReportsProgress) { worker.ReportProgress(80); } beatmap.SortHitObjects(); editor.SaveFile(); // Complete progressbar if (worker != null && worker.WorkerReportsProgress) { worker.ReportProgress(100); } // Do stuff if (arg.Quick) { RunFinished?.Invoke(this, new RunToolCompletedEventArgs(true, editorRead)); } return(arg.Quick ? string.Empty : "Done!"); }