public static void ExportHitsounds(IEnumerable <HitsoundEvent> hitsounds, string baseBeatmap, string exportFolder, bool useGreenlines = true, bool useStoryboard = false)
        {
            EditorReaderStuff.TryGetNewestVersion(baseBeatmap, out var editor);
            Beatmap beatmap = editor.Beatmap;

            if (useStoryboard)
            {
                beatmap.StoryboardSoundSamples = hitsounds.Select(h =>
                                                                  new StoryboardSoundSample(h.Time, 0, h.Filename, h.Volume))
                                                 .ToList();
            }
            else
            {
                // Make new timing points
                // Add red lines
                List <TimingPoint>        timingPoints        = beatmap.BeatmapTiming.GetAllRedlines();
                List <TimingPointsChange> timingPointsChanges = timingPoints.Select(tp =>
                                                                                    new TimingPointsChange(tp, mpb: true, meter: true, inherited: true, omitFirstBarLine: true))
                                                                .ToList();

                // Add hitsound stuff
                // Replace all hitobjects with the hitsounds
                beatmap.HitObjects.Clear();
                foreach (HitsoundEvent h in hitsounds)
                {
                    if (useGreenlines)
                    {
                        TimingPoint tp = beatmap.BeatmapTiming.GetTimingPointAtTime(h.Time + 5).Copy();
                        tp.Offset      = h.Time;
                        tp.SampleIndex = h.CustomIndex;
                        h.CustomIndex  = 0; // Set it to default value because it gets handled by greenlines now
                        tp.Volume      = Math.Round(tp.Volume * h.Volume);
                        h.Volume       = 0; // Set it to default value because it gets handled by greenlines now
                        timingPointsChanges.Add(new TimingPointsChange(tp, index: true, volume: true));
                    }

                    beatmap.HitObjects.Add(new HitObject(h.Pos, h.Time, 5, h.GetHitsounds(), h.SampleSet, h.Additions,
                                                         h.CustomIndex, h.Volume * 100, h.Filename));
                }

                // Replace the old timingpoints
                beatmap.BeatmapTiming.TimingPoints.Clear();
                TimingPointsChange.ApplyChanges(beatmap.BeatmapTiming, timingPointsChanges);
            }

            // Change version to hitsounds
            beatmap.General["StackLeniency"] = new TValue("0.0");
            beatmap.General["Mode"]          = new TValue("0");
            beatmap.Metadata["Version"]      = new TValue("Hitsounds");
            beatmap.Difficulty["CircleSize"] = new TValue("4");

            // Save the file to the export folder
            editor.SaveFile(Path.Combine(exportFolder, beatmap.GetFileName()));
        }
        public static void ExportHitsounds(List<HitsoundEvent> hitsounds, string baseBeatmap, string exportFolder) {
            EditorReaderStuff.TryGetNewestVersion(baseBeatmap, out var editor);
            Beatmap beatmap = editor.Beatmap;

            // Make new timing points
            List<TimingPointsChange> timingPointsChanges = new List<TimingPointsChange>();

            // Add red lines
            List<TimingPoint> timingPoints = beatmap.BeatmapTiming.GetAllRedlines();
            foreach (TimingPoint tp in timingPoints) {
                timingPointsChanges.Add(new TimingPointsChange(tp, mpb: true, meter: true, inherited: true, omitFirstBarLine: true));
            }

            // Add hitsound stuff
            foreach (HitsoundEvent h in hitsounds) {
                TimingPoint tp = beatmap.BeatmapTiming.GetTimingPointAtTime(h.Time + 5).Copy();
                tp.Offset = h.Time;
                tp.SampleIndex = h.CustomIndex;
                tp.Volume = Math.Round(tp.Volume * h.Volume);
                timingPointsChanges.Add(new TimingPointsChange(tp, index: true, volume: true));
            }

            // Replace the old timingpoints
            beatmap.BeatmapTiming.TimingPoints.Clear();
            TimingPointsChange.ApplyChanges(beatmap.BeatmapTiming, timingPointsChanges);

            // Replace all hitobjects with the hitsounds
            beatmap.HitObjects.Clear();
            foreach (HitsoundEvent h in hitsounds) {
                beatmap.HitObjects.Add(new HitObject(h.Time, h.GetHitsounds(), h.SampleSet, h.Additions));
            }

            // Change version to hitsounds
            beatmap.General["StackLeniency"] = new TValue("0.0");
            beatmap.General["Mode"] = new TValue("0");
            beatmap.Metadata["Version"] = new TValue("Hitsounds");
            beatmap.Difficulty["CircleSize"] = new TValue("4");

            // Save the file to the export folder
            editor.SaveFile(Path.Combine(exportFolder, beatmap.GetFileName()));
        }
        private string Copy_Timing(TimingCopierVm arg, BackgroundWorker worker, DoWorkEventArgs _)
        {
            string[] paths    = arg.ExportPath.Split('|');
            int      mapsDone = 0;

            var reader = EditorReaderStuff.GetFullEditorReaderOrNot();

            foreach (string exportPath in paths)
            {
                var editorTo   = EditorReaderStuff.GetNewestVersionOrNot(exportPath, reader);
                var editorFrom = EditorReaderStuff.GetNewestVersionOrNot(arg.ImportPath, reader);

                Beatmap beatmapTo   = editorTo.Beatmap;
                Beatmap beatmapFrom = editorFrom.Beatmap;

                Timing timingTo   = beatmapTo.BeatmapTiming;
                Timing timingFrom = beatmapFrom.BeatmapTiming;

                // Get markers for hitobjects if mode 1 is used
                List <Marker> markers = new List <Marker>();
                if (arg.ResnapMode == "Number of beats between objects stays the same")
                {
                    markers = GetMarkers(beatmapTo, timingTo);
                }

                // Rid the beatmap of redlines
                // If a greenline exists at the same time as a redline then the redline ceizes to exist
                // Else convert the redline to a greenline: Inherited = false & MpB = -100
                List <TimingPoint> removeList = new List <TimingPoint>();
                foreach (TimingPoint redline in timingTo.GetAllRedlines())
                {
                    TimingPoint greenlineHere = timingTo.GetGreenlineAtTime(redline.Offset);
                    if (greenlineHere.Offset == redline.Offset)
                    {
                        removeList.Add(redline);
                    }
                    else
                    {
                        redline.Uninherited = false;
                        redline.MpB         = -100;
                    }
                }
                foreach (TimingPoint tp in removeList)
                {
                    timingTo.TimingPoints.Remove(tp);
                }

                // Make new timing points changes
                List <TimingPointsChange> timingPointsChanges = new List <TimingPointsChange>();

                // Add redlines
                List <TimingPoint> redlines = timingFrom.GetAllRedlines();
                foreach (TimingPoint tp in redlines)
                {
                    timingPointsChanges.Add(new TimingPointsChange(tp, mpb: true, meter: true, inherited: true, omitFirstBarLine: true));
                }

                // Apply timing changes
                TimingPointsChange.ApplyChanges(timingTo, timingPointsChanges);

                if (arg.ResnapMode == "Number of beats between objects stays the same")
                {
                    redlines = timingTo.GetAllRedlines();
                    List <double> newBookmarks = new List <double>();
                    double        lastTime     = redlines.FirstOrDefault().Offset;
                    foreach (Marker marker in markers)
                    {
                        // Get redlines between this and last marker
                        TimingPoint redline = timingTo.GetRedlineAtTime(lastTime, redlines.FirstOrDefault());

                        double beatsFromLastTime = marker.BeatsFromLastMarker;
                        while (true)
                        {
                            List <TimingPoint> redlinesBetween = redlines.Where(o => o.Offset <= lastTime + redline.MpB * beatsFromLastTime && o.Offset > lastTime).ToList();

                            if (redlinesBetween.Count == 0)
                            {
                                break;
                            }

                            TimingPoint first = redlinesBetween.First();
                            double      diff  = first.Offset - lastTime;
                            beatsFromLastTime -= diff / redline.MpB;

                            redline  = first;
                            lastTime = first.Offset;
                        }

                        // Last time is the time of the last redline in between
                        double newTime = lastTime + redline.MpB * beatsFromLastTime;
                        newTime     = timingTo.Resnap(newTime, arg.Snap1, arg.Snap2, firstTp: redlines.FirstOrDefault());
                        marker.Time = newTime;

                        lastTime = marker.Time;
                    }

                    // Add the bookmarks
                    foreach (Marker marker in markers)
                    {
                        // Check whether the marker is a bookmark
                        if (marker.Object is double)
                        {
                            // Don't resnap bookmarks
                            newBookmarks.Add((double)marker.Object);
                        }
                    }
                    beatmapTo.SetBookmarks(newBookmarks);
                }
                else if (arg.ResnapMode == "Just resnap")
                {
                    // Resnap hitobjects
                    foreach (HitObject ho in beatmapTo.HitObjects)
                    {
                        ho.ResnapSelf(timingTo, arg.Snap1, arg.Snap2, firstTp: redlines.FirstOrDefault());
                        ho.ResnapEnd(timingTo, arg.Snap1, arg.Snap2, firstTp: redlines.FirstOrDefault());
                    }

                    // Resnap greenlines
                    foreach (TimingPoint tp in timingTo.GetAllGreenlines())
                    {
                        tp.ResnapSelf(timingTo, arg.Snap1, arg.Snap2, firstTP: redlines.FirstOrDefault());
                    }
                }
                else
                {
                    // Don't move objects
                }

                // Save the file
                editorTo.SaveFile();

                // Update progressbar
                if (worker != null && worker.WorkerReportsProgress)
                {
                    worker.ReportProgress(++mapsDone * 100 / paths.Length);
                }
            }

            // Make an accurate message
            string message = $"Successfully copied timing to {mapsDone} {(mapsDone == 1 ? "beatmap" : "beatmaps")}!";

            return(message);
        }
Example #4
0
        private string Copy_Hitsounds(HitsoundCopierVm arg, BackgroundWorker worker)
        {
            var doMutedIndex = arg.MutedIndex >= 0;

            var paths        = arg.PathTo.Split('|');
            var mapsDone     = 0;
            var sampleSchema = new SampleSchema();

            var reader = EditorReaderStuff.GetFullEditorReaderOrNot();

            foreach (var pathTo in paths)
            {
                BeatmapEditor editorTo  = EditorReaderStuff.GetNewestVersionOrNot(pathTo, reader);;
                Beatmap       beatmapTo = editorTo.Beatmap;
                Beatmap       beatmapFrom;

                if (!string.IsNullOrEmpty(arg.PathFrom))
                {
                    var editorFrom = EditorReaderStuff.GetNewestVersionOrNot(arg.PathFrom, reader);
                    beatmapFrom = editorFrom.Beatmap;
                }
                else
                {
                    // Copy from an empty beatmap similar to the map to copy to
                    beatmapFrom = beatmapTo.DeepCopy();
                    beatmapFrom.HitObjects.Clear();
                    beatmapFrom.BeatmapTiming.Clear();
                }

                Timeline processedTimeline;

                if (arg.CopyMode == 0)
                {
                    // Every defined hitsound and sampleset on hitsound gets copied to their copyTo destination
                    // Timelines
                    var tlTo   = beatmapTo.GetTimeline();
                    var tlFrom = beatmapFrom.GetTimeline();

                    var volumeMuteTimes = arg.CopyVolumes && arg.AlwaysPreserve5Volume ? new List <double>() : null;

                    if (arg.CopyHitsounds)
                    {
                        ResetHitObjectHitsounds(beatmapTo);
                        CopyHitsounds(arg, tlFrom, tlTo);
                    }

                    // Save tlo times where timingpoint volume is 5%
                    // Timingpointchange all the undefined tlo from copyFrom
                    volumeMuteTimes?.AddRange(from tloTo in tlTo.TimelineObjects
                                              where tloTo.CanCopy && Math.Abs(tloTo.SampleVolume) < Precision.DOUBLE_EPSILON &&
                                              Math.Abs(tloTo.FenoSampleVolume - 5) < Precision.DOUBLE_EPSILON
                                              select tloTo.Time);

                    // Volumes and samplesets and customindices greenlines get copied with timingpointchanges and allafter enabled
                    var timingPointsChanges = beatmapFrom.BeatmapTiming.TimingPoints.Select(tp =>
                                                                                            new TimingPointsChange(tp, sampleset: arg.CopySampleSets, index: arg.CopySampleSets,
                                                                                                                   volume: arg.CopyVolumes)).ToList();

                    // Apply the timingpoint changes
                    TimingPointsChange.ApplyChanges(beatmapTo.BeatmapTiming, timingPointsChanges, true);

                    processedTimeline = tlTo;

                    // Return 5% volume to tlo that had it before
                    if (volumeMuteTimes != null)
                    {
                        var timingPointsChangesMute = new List <TimingPointsChange>();
                        processedTimeline.GiveTimingPoints(beatmapTo.BeatmapTiming);

                        // Exclude objects which use their own sample volume property instead
                        foreach (var tloTo in processedTimeline.TimelineObjects.Where(o => Math.Abs(o.SampleVolume) < Precision.DOUBLE_EPSILON))
                        {
                            if (volumeMuteTimes.Contains(tloTo.Time))
                            {
                                // Add timingpointschange to copy timingpoint hitsounds
                                var tp = tloTo.HitsoundTimingPoint.Copy();
                                tp.Offset = tloTo.Time;
                                tp.Volume = 5;
                                timingPointsChangesMute.Add(new TimingPointsChange(tp, volume: true));
                            }
                            else
                            {
                                // Add timingpointschange to preserve index and volume
                                var tp = tloTo.HitsoundTimingPoint.Copy();
                                tp.Offset = tloTo.Time;
                                tp.Volume = tloTo.FenoSampleVolume;
                                timingPointsChangesMute.Add(new TimingPointsChange(tp, volume: true));
                            }
                        }

                        // Apply the timingpoint changes
                        TimingPointsChange.ApplyChanges(beatmapTo.BeatmapTiming, timingPointsChangesMute);
                    }
                }
                else
                {
                    // Smarty mode
                    // Copy the defined hitsounds literally (not feno, that will be reserved for cleaner). Only the tlo that have been defined by copyFrom get overwritten.
                    var tlTo   = beatmapTo.GetTimeline();
                    var tlFrom = beatmapFrom.GetTimeline();

                    var timingPointsChanges = new List <TimingPointsChange>();
                    var mode         = (GameMode)beatmapTo.General["Mode"].IntValue;
                    var mapDir       = editorTo.GetParentFolder();
                    var firstSamples = HitsoundImporter.AnalyzeSamples(mapDir);

                    if (arg.CopyHitsounds)
                    {
                        CopyHitsounds(arg, beatmapTo, tlFrom, tlTo, timingPointsChanges, mode, mapDir, firstSamples, ref sampleSchema);
                    }

                    if (arg.CopyBodyHitsounds)
                    {
                        // Remove timingpoints in beatmapTo that are in a sliderbody/spinnerbody for both beatmapTo and BeatmapFrom
                        foreach (var tp in from ho in beatmapTo.HitObjects
                                 from tp in ho.BodyHitsounds
                                 where beatmapFrom.HitObjects.Any(o => o.Time <tp.Offset && o.EndTime> tp.Offset)
                                 where !tp.Uninherited
                                 select tp)
                        {
                            beatmapTo.BeatmapTiming.Remove(tp);
                        }

                        // Get timingpointschanges for every timingpoint from beatmapFrom that is in a sliderbody/spinnerbody for both beatmapTo and BeatmapFrom
                        timingPointsChanges.AddRange(from ho in beatmapFrom.HitObjects
                                                     from tp in ho.BodyHitsounds
                                                     where beatmapTo.HitObjects.Any(o => o.Time <tp.Offset && o.EndTime> tp.Offset)
                                                     select new TimingPointsChange(tp.Copy(), sampleset: arg.CopySampleSets, index: arg.CopySampleSets,
                                                                                   volume: arg.CopyVolumes));
                    }

                    // Apply the timingpoint changes
                    TimingPointsChange.ApplyChanges(beatmapTo.BeatmapTiming, timingPointsChanges);

                    processedTimeline = tlTo;
                }

                if (arg.CopyStoryboardedSamples)
                {
                    if (arg.CopyMode == 0)
                    {
                        beatmapTo.StoryboardSoundSamples.Clear();
                    }

                    beatmapTo.GiveObjectsGreenlines();
                    processedTimeline.GiveTimingPoints(beatmapTo.BeatmapTiming);

                    var mapDir       = editorTo.GetParentFolder();
                    var firstSamples = HitsoundImporter.AnalyzeSamples(mapDir, true);

                    var samplesTo = new HashSet <StoryboardSoundSample>(beatmapTo.StoryboardSoundSamples);
                    var mode      = (GameMode)beatmapTo.General["Mode"].IntValue;

                    foreach (var sampleFrom in beatmapFrom.StoryboardSoundSamples)
                    {
                        if (arg.IgnoreHitsoundSatisfiedSamples)
                        {
                            var tloHere = processedTimeline.TimelineObjects.FindAll(o =>
                                                                                    Math.Abs(o.Time - sampleFrom.StartTime) <= arg.TemporalLeniency);
                            var samplesHere = new HashSet <string>();
                            foreach (var tlo in tloHere)
                            {
                                foreach (var filename in tlo.GetPlayingFilenames(mode))
                                {
                                    var samplePath      = Path.Combine(mapDir, filename);
                                    var fullPathExtLess = Path.Combine(Path.GetDirectoryName(samplePath),
                                                                       Path.GetFileNameWithoutExtension(samplePath));

                                    if (firstSamples.Keys.Contains(fullPathExtLess))
                                    {
                                        samplePath = firstSamples[fullPathExtLess];
                                    }

                                    samplesHere.Add(samplePath);
                                }
                            }

                            var sbSamplePath      = Path.Combine(mapDir, sampleFrom.FilePath);
                            var sbFullPathExtLess = Path.Combine(Path.GetDirectoryName(sbSamplePath),
                                                                 Path.GetFileNameWithoutExtension(sbSamplePath));

                            if (firstSamples.Keys.Contains(sbFullPathExtLess))
                            {
                                sbSamplePath = firstSamples[sbFullPathExtLess];
                            }

                            if (samplesHere.Contains(sbSamplePath))
                            {
                                continue;
                            }
                        }

                        // Add the StoryboardSoundSamples from beatmapFrom to beatmapTo if it doesn't already have the sample
                        if (!samplesTo.Contains(sampleFrom))
                        {
                            beatmapTo.StoryboardSoundSamples.Add(sampleFrom);
                        }
                    }

                    // Sort the storyboarded samples
                    beatmapTo.StoryboardSoundSamples.Sort();
                }

                if (arg.MuteSliderends)
                {
                    var timingPointsChanges = new List <TimingPointsChange>();
                    beatmapTo.GiveObjectsGreenlines();
                    processedTimeline.GiveTimingPoints(beatmapTo.BeatmapTiming);

                    foreach (var tloTo in processedTimeline.TimelineObjects)
                    {
                        if (FilterMuteTlo(tloTo, beatmapTo, arg))
                        {
                            // Set volume to 5%, remove all hitsounds, apply customindex and sampleset
                            tloTo.SampleSet   = arg.MutedSampleSet;
                            tloTo.AdditionSet = 0;
                            tloTo.Normal      = false;
                            tloTo.Whistle     = false;
                            tloTo.Finish      = false;
                            tloTo.Clap        = false;

                            tloTo.HitsoundsToOrigin();

                            // Add timingpointschange to copy timingpoint hitsounds
                            var tp = tloTo.HitsoundTimingPoint.Copy();
                            tp.Offset      = tloTo.Time;
                            tp.SampleSet   = arg.MutedSampleSet;
                            tp.SampleIndex = arg.MutedIndex;
                            tp.Volume      = 5;
                            timingPointsChanges.Add(new TimingPointsChange(tp, sampleset: true, index: doMutedIndex,
                                                                           volume: true));
                        }
                        else
                        {
                            // Add timingpointschange to preserve index and volume and sampleset
                            var tp = tloTo.HitsoundTimingPoint.Copy();
                            tp.Offset = tloTo.Time;
                            timingPointsChanges.Add(new TimingPointsChange(tp, sampleset: true, index: doMutedIndex,
                                                                           volume: true));
                        }
                    }

                    // Apply the timingpoint changes
                    TimingPointsChange.ApplyChanges(beatmapTo.BeatmapTiming, timingPointsChanges);
                }

                // Save the file
                editorTo.SaveFile();

                // Export the sample schema if there are samples
                if (sampleSchema.Count > 0)
                {
                    string exportFolder = MainWindow.ExportPath;

                    DirectoryInfo di = new DirectoryInfo(exportFolder);
                    foreach (FileInfo file in di.GetFiles())
                    {
                        file.Delete();
                    }

                    HitsoundExporter.ExportSampleSchema(sampleSchema, exportFolder);

                    System.Diagnostics.Process.Start(exportFolder);
                }

                // Update progressbar
                if (worker != null && worker.WorkerReportsProgress)
                {
                    worker.ReportProgress(++mapsDone * 100 / paths.Length);
                }
            }

            return("Done!");
        }
Example #5
0
        public static void ExportHitsounds(List <HitsoundEvent> hitsounds, string baseBeatmap, string exportFolder, string exportMapName, GameMode exportGameMode, bool useGreenlines, bool useStoryboard)
        {
            var     editor  = EditorReaderStuff.GetNewestVersionOrNot(baseBeatmap);
            Beatmap beatmap = editor.Beatmap;

            if (useStoryboard)
            {
                beatmap.StoryboardSoundSamples.Clear();
                foreach (var h in hitsounds.Where(h => !string.IsNullOrEmpty(h.Filename)))
                {
                    beatmap.StoryboardSoundSamples.Add(new StoryboardSoundSample(h.Time, 0, h.Filename, h.Volume * 100));
                }
            }
            else
            {
                // Make new timing points
                // Add red lines
                List <TimingPoint>        timingPoints        = beatmap.BeatmapTiming.GetAllRedlines();
                List <TimingPointsChange> timingPointsChanges = timingPoints.Select(tp =>
                                                                                    new TimingPointsChange(tp, mpb: true, meter: true, inherited: true, omitFirstBarLine: true))
                                                                .ToList();

                // Add hitsound stuff
                // Replace all hitobjects with the hitsounds
                beatmap.HitObjects.Clear();
                foreach (HitsoundEvent h in hitsounds)
                {
                    if (useGreenlines)
                    {
                        TimingPoint tp = beatmap.BeatmapTiming.GetTimingPointAtTime(h.Time + 5).Copy();
                        tp.Offset      = h.Time;
                        tp.SampleIndex = h.CustomIndex;
                        h.CustomIndex  = 0; // Set it to default value because it gets handled by greenlines now
                        tp.Volume      = Math.Round(tp.Volume * h.Volume);
                        h.Volume       = 0; // Set it to default value because it gets handled by greenlines now
                        timingPointsChanges.Add(new TimingPointsChange(tp, index: true, volume: true));
                    }

                    beatmap.HitObjects.Add(new HitObject(h.Pos, h.Time, 5, h.GetHitsounds(), h.SampleSet, h.Additions,
                                                         h.CustomIndex, h.Volume * 100, h.Filename));
                }

                // Replace the old timingpoints
                beatmap.BeatmapTiming.TimingPoints.Clear();
                TimingPointsChange.ApplyChanges(beatmap.BeatmapTiming, timingPointsChanges);
            }

            // Change version to hitsounds
            beatmap.General["StackLeniency"] = new TValue("0.0");
            beatmap.General["Mode"]          = new TValue(((int)exportGameMode).ToInvariant());
            beatmap.Metadata["Version"]      = new TValue(exportMapName);

            if (exportGameMode == GameMode.Mania)
            {
                // Count the number of distinct X positions
                int numXPositions = new HashSet <double>(hitsounds.Select(h => h.Pos.X)).Count;
                int numKeys       = MathHelper.Clamp(numXPositions, 1, 18);

                beatmap.Difficulty["CircleSize"] = new TValue(numKeys.ToInvariant());
            }
            else
            {
                beatmap.Difficulty["CircleSize"] = new TValue("4");
            }

            // Save the file to the export folder
            editor.SaveFile(Path.Combine(exportFolder, beatmap.GetFileName()));
        }
Example #6
0
        private string Complete_Sliders(SliderCompletionatorVm arg, BackgroundWorker worker, DoWorkEventArgs _)
        {
            int slidersCompleted = 0;

            var reader = EditorReaderStuff.GetFullEditorReaderOrNot(out var editorReaderException1);

            if (arg.ImportModeSetting == SliderCompletionatorVm.ImportMode.Selected && editorReaderException1 != null)
            {
                throw new Exception("Could not fetch selected hit objects.", editorReaderException1);
            }

            foreach (string path in arg.Paths)
            {
                var editor = EditorReaderStuff.GetNewestVersionOrNot(path, reader, out var selected, out var editorReaderException2);

                if (arg.ImportModeSetting == SliderCompletionatorVm.ImportMode.Selected && editorReaderException2 != null)
                {
                    throw new Exception("Could not fetch selected hit objects.", editorReaderException2);
                }

                Beatmap          beatmap       = editor.Beatmap;
                Timing           timing        = beatmap.BeatmapTiming;
                List <HitObject> markedObjects = arg.ImportModeSetting == SliderCompletionatorVm.ImportMode.Selected ? selected :
                                                 arg.ImportModeSetting == SliderCompletionatorVm.ImportMode.Bookmarked ? beatmap.GetBookmarkedObjects() :
                                                 arg.ImportModeSetting == SliderCompletionatorVm.ImportMode.Time ? beatmap.QueryTimeCode(arg.TimeCode).ToList() :
                                                 beatmap.HitObjects;

                for (int i = 0; i < markedObjects.Count; i++)
                {
                    HitObject ho = markedObjects[i];
                    if (ho.IsSlider)
                    {
                        double oldSpatialLength = ho.PixelLength;
                        double newSpatialLength = arg.SpatialLength != -1 ? ho.GetSliderPath(fullLength: true).Distance *arg.SpatialLength : oldSpatialLength;

                        double oldTemporalLength = timing.CalculateSliderTemporalLength(ho.Time, ho.PixelLength);
                        double newTemporalLength = arg.TemporalLength != -1 ? timing.GetMpBAtTime(ho.Time) * arg.TemporalLength : oldTemporalLength;

                        double oldSv = timing.GetSvAtTime(ho.Time);
                        double newSv = oldSv / ((newSpatialLength / oldSpatialLength) / (newTemporalLength / oldTemporalLength));

                        if (double.IsNaN(newSv))
                        {
                            throw new Exception("Encountered NaN slider velocity. Make sure none of the inputs are zero.");
                        }

                        ho.SliderVelocity = newSv;
                        ho.PixelLength    = newSpatialLength;

                        // Scale anchors to completion
                        if (arg.MoveAnchors)
                        {
                            ho.SetAllCurvePoints(SliderPathUtil.MoveAnchorsToLength(
                                                     ho.GetAllCurvePoints(), ho.SliderType, ho.PixelLength, out var pathType));
                            ho.SliderType = pathType;
                        }

                        slidersCompleted++;
                    }
                    if (worker != null && worker.WorkerReportsProgress)
                    {
                        worker.ReportProgress(i / markedObjects.Count);
                    }
                }

                // Reconstruct SliderVelocity
                List <TimingPointsChange> timingPointsChanges = new List <TimingPointsChange>();
                // Add Hitobject stuff
                foreach (HitObject ho in beatmap.HitObjects)
                {
                    if (ho.IsSlider) // SliderVelocity changes
                    {
                        TimingPoint tp = ho.TimingPoint.Copy();
                        tp.Offset = ho.Time;
                        tp.MpB    = ho.SliderVelocity;
                        timingPointsChanges.Add(new TimingPointsChange(tp, mpb: true));
                    }
                }

                // Add the new SliderVelocity changes
                TimingPointsChange.ApplyChanges(timing, timingPointsChanges);

                // Save the file
                editor.SaveFile();
            }

            // Complete progressbar
            if (worker != null && worker.WorkerReportsProgress)
            {
                worker.ReportProgress(100);
            }

            // Do stuff
            if (arg.Quick)
            {
                RunFinished?.Invoke(this, new RunToolCompletedEventArgs(true, reader != null));
            }

            // Make an accurate message
            string message = "";

            if (Math.Abs(slidersCompleted) == 1)
            {
                message += "Successfully completed " + slidersCompleted + " slider!";
            }
            else
            {
                message += "Successfully completed " + slidersCompleted + " sliders!";
            }
            return(arg.Quick ? "" : message);
        }
Example #7
0
        /// <summary>
        /// Places each hit object of the pattern beatmap into the other beatmap and applies timingpoint changes to copy timingpoint stuff aswell.
        /// The given pattern beatmap could be modified by this method if protectBeatmapPattern is false.
        /// </summary>
        /// <param name="patternBeatmap">The pattern beatmap to be placed into the beatmap.</param>
        /// <param name="beatmap">To beatmap to place the pattern in.</param>
        /// <param name="offset">An offset to move the pattern beatmap in time with.</param>
        /// <param name="protectBeatmapPattern">If true, copies the pattern beatmap to prevent the pattern beatmap from being modified by this method.</param>
        public void PlaceOsuPattern(Beatmap patternBeatmap, Beatmap beatmap, double offset = 0, bool protectBeatmapPattern = true)
        {
            if (protectBeatmapPattern)
            {
                // Copy so the original pattern doesnt get changed
                patternBeatmap = patternBeatmap.DeepCopy();
            }

            // Do the offset
            if (Math.Abs(offset) > Precision.DOUBLE_EPSILON)
            {
                patternBeatmap.OffsetTime(offset);
            }

            // We adjust the pattern first so it alligns with the beatmap.
            // The right timing is applied and optional pre-processing is applied.
            // Sliderends and object timingpoints get recalculated.
            PreparePattern(patternBeatmap, beatmap, out var parts, out var timingPointsChanges);

            // Keep just the timing point changes which are inside the parts.
            // These timing point changes have everything that is necessary for inside the parts of the pattern. (even timing)
            timingPointsChanges = timingPointsChanges.Where(tpc => parts.Any(part =>
                                                                             part.StartTime <= tpc.MyTP.Offset && part.EndTime >= tpc.MyTP.Offset)).ToList();

            // Remove stuff
            if (PatternOverwriteMode != PatternOverwriteMode.NoOverwrite)
            {
                foreach (var part in parts)
                {
                    RemovePartOfBeatmap(beatmap, part.StartTime - Padding, part.EndTime + Padding);
                }
            }

            // Add timingpoint changes for each hitobject to make sure they still have the wanted SV and hitsounds (especially near the edges of parts)
            // It is possible for the timingpoint of a hitobject at the start of a part to be outside of the part, so this fixes issues related to that
            timingPointsChanges.AddRange(
                beatmap.HitObjects.Where(ho => ho.TimingPoint != null)
                .Select(GetSvChange));

            if (IncludeHitsounds)
            {
                timingPointsChanges.AddRange(
                    beatmap.HitObjects.Where(ho => ho.HitsoundTimingPoint != null)
                    .Select(GetHitsoundChange));
            }

            // Apply the changes
            TimingPointsChange.ApplyChanges(beatmap.BeatmapTiming, timingPointsChanges);

            // Add the hitobjects of the pattern
            beatmap.HitObjects.AddRange(patternBeatmap.HitObjects);

            // Sort hitobjects
            beatmap.SortHitObjects();

            if (FixColourHax)
            {
                beatmap.FixComboSkip();
            }

            beatmap.GiveObjectsGreenlines();
            beatmap.CalculateSliderEndTimes();
        }
Example #8
0
        /// <summary>
        /// Does a procedure similar to <see cref="MapCleaner"/> which adjusts the pattern so it fits in the beatmap.
        /// It does so according to the options selected in this.
        /// </summary>
        /// <param name="patternBeatmap"></param>
        /// <param name="beatmap"></param>
        /// <param name="parts"></param>
        /// <param name="timingPointsChanges"></param>
        private void PreparePattern(Beatmap patternBeatmap, Beatmap beatmap, out List <Part> parts, out List <TimingPointsChange> timingPointsChanges)
        {
            double patternStartTime = patternBeatmap.GetHitObjectStartTime();

            Timing originalTiming = beatmap.BeatmapTiming;
            Timing patternTiming  = patternBeatmap.BeatmapTiming;

            GameMode targetMode = (GameMode)beatmap.General["Mode"].IntValue;

            double originalCircleSize = beatmap.Difficulty["CircleSize"].DoubleValue;
            double patternCircleSize  = patternBeatmap.Difficulty["CircleSize"].DoubleValue;

            double originalTickRate = beatmap.Difficulty["SliderTickRate"].DoubleValue;
            double patternTickRate  = patternBeatmap.Difficulty["SliderTickRate"].DoubleValue;

            // Don't include SV changes if it is based on nothing
            bool includePatternSliderVelocity = patternTiming.Count > 0;

            // Avoid including hitsounds if there are no timingpoints to get hitsounds from
            bool includeTimingPointHitsounds = IncludeHitsounds && patternTiming.Count > 0;

            // Don't scale to new timing if the pattern has no timing to speak of
            bool scaleToNewTiming = ScaleToNewTiming && patternTiming.Redlines.Count > 0;

            // Avoid overwriting timing if the pattern has no redlines
            TimingOverwriteMode timingOverwriteMode = patternTiming.Redlines.Count > 0
                ? TimingOverwriteMode
                : TimingOverwriteMode.OriginalTimingOnly;

            // Get the scale for custom scale x CS scale
            double csScale = Beatmap.GetHitObjectRadius(originalCircleSize) /
                             Beatmap.GetHitObjectRadius(patternCircleSize);
            double spatialScale = ScaleToNewCircleSize && !double.IsNaN(csScale) ? CustomScale * csScale : CustomScale;

            // Get a BPM multiplier to fix the tick rate
            // This multiplier is not meant to change SV so this is subtracted from the greenline SV later
            double bpmMultiplier = FixTickRate ? patternTickRate / originalTickRate : 1;

            // Dont give new combo to all hit objects which were actually new combo in the pattern,
            // because it leads to unexpected NC's at the start of patterns.

            // Collect Kiai toggles
            List <TimingPoint> kiaiToggles = new List <TimingPoint>();
            bool lastKiai = false;

            // If not including the kiai of the pattern, add the kiai of the original map.
            // This has to be done because this part of the original map might get deleted.
            foreach (TimingPoint tp in IncludeKiai ? patternTiming.TimingPoints : originalTiming.TimingPoints)
            {
                if (tp.Kiai != lastKiai || kiaiToggles.Count == 0)
                {
                    kiaiToggles.Add(tp.Copy());
                    lastKiai = tp.Kiai;
                }
            }

            // Collect SliderVelocity changes for mania/taiko
            List <TimingPoint> svChanges = new List <TimingPoint>();
            double             lastSV    = -100;

            // If not including the SV of the pattern, add the SV of the original map.
            // This has to be done because this part of the original map might get deleted.
            foreach (TimingPoint tp in includePatternSliderVelocity ? patternTiming.TimingPoints : originalTiming.TimingPoints)
            {
                if (tp.Uninherited)
                {
                    lastSV = -100;
                }
                else
                {
                    if (Math.Abs(tp.MpB - lastSV) > Precision.DOUBLE_EPSILON)
                    {
                        svChanges.Add(tp.Copy());
                        lastSV = tp.MpB;
                    }
                }
            }

            // If not including the SV of the pattern, set the SV of sliders to that of the original beatmap,
            // so the pattern will take over the SV of the original beatmap.
            if (!includePatternSliderVelocity)
            {
                foreach (var ho in patternBeatmap.HitObjects.Where(ho => ho.IsSlider))
                {
                    ho.SliderVelocity = originalTiming.GetSvAtTime(ho.Time);
                }
            }

            // Get the timeline before moving all objects so it has the correct hitsounds
            // Make sure that moving the objects in the pattern moves the timeline objects aswell
            // This method is NOT safe to use in beat time
            Timeline patternTimeline         = patternBeatmap.GetTimeline();
            Timing   transformOriginalTiming = originalTiming;
            Timing   transformPatternTiming  = patternTiming;

            if (scaleToNewTiming)
            {
                // Transform everything to beat time relative to pattern start time
                foreach (var ho in patternBeatmap.HitObjects)
                {
                    double oldEndTime = ho.GetEndTime(false);

                    ho.Time    = patternTiming.GetBeatLength(patternStartTime, ho.Time);
                    ho.EndTime = patternTiming.GetBeatLength(patternStartTime, oldEndTime);

                    // The body hitsounds are not copies of timingpoints in patternTiming so they should be copied before changing offset
                    for (int i = 0; i < ho.BodyHitsounds.Count; i++)
                    {
                        TimingPoint tp = ho.BodyHitsounds[i].Copy();
                        tp.Offset           = patternTiming.GetBeatLength(patternStartTime, tp.Offset);
                        ho.BodyHitsounds[i] = tp;
                    }
                }

                foreach (var tp in kiaiToggles.Concat(svChanges))
                {
                    tp.Offset = patternTiming.GetBeatLength(patternStartTime, tp.Offset);
                }

                // Transform the pattern redlines to beat time
                // This will not change the order of redlines (unless negative BPM exists)
                transformPatternTiming = patternTiming.Copy();
                foreach (var tp in transformPatternTiming.Redlines)
                {
                    tp.Offset = patternTiming.GetBeatLength(patternStartTime, tp.Offset);
                }

                // Transform the original timingpoints to beat time
                // This will not change the order of timingpoints (unless negative BPM exists)
                transformOriginalTiming = originalTiming.Copy();
                foreach (var tp in transformOriginalTiming.TimingPoints)
                {
                    tp.Offset = originalTiming.GetBeatLength(patternStartTime, tp.Offset);
                }
            }

            // Fix SV for the new global SV
            var globalSvFactor = transformOriginalTiming.SliderMultiplier / transformPatternTiming.SliderMultiplier;

            if (FixGlobalSv)
            {
                foreach (HitObject ho in patternBeatmap.HitObjects.Where(o => o.IsSlider))
                {
                    ho.SliderVelocity *= globalSvFactor;
                }
                foreach (TimingPoint tp in svChanges)
                {
                    tp.MpB *= globalSvFactor;
                }
            }
            else
            {
                foreach (HitObject ho in patternBeatmap.HitObjects.Where(o => o.IsSlider))
                {
                    ho.TemporalLength /= globalSvFactor;
                }
            }

            // Partition the pattern based on the timing in the pattern
            if (PatternOverwriteMode == PatternOverwriteMode.PartitionedOverwrite)
            {
                parts = PartitionBeatmap(patternBeatmap, scaleToNewTiming);
            }
            else
            {
                parts = new List <Part> {
                    new Part(patternBeatmap.HitObjects[0].Time,
                             patternBeatmap.HitObjects[patternBeatmap.HitObjects.Count - 1].Time,
                             patternBeatmap.HitObjects)
                };
            }

            // Construct a new timing which is a mix of the beatmap and the pattern.
            // If scaleToNewTiming then use beat relative values to determine the duration of timing sections in the pattern.
            // scaleToNewTiming must scale all the partitions, timingpoints, hitobjects, and events (if applicable).
            Timing newTiming = new Timing(transformOriginalTiming.SliderMultiplier);

            var lastEndTime = double.NegativeInfinity;

            foreach (var part in parts)
            {
                var startTime = part.StartTime;
                var endTime   = part.EndTime; // Subtract one to omit BPM changes right on the end of the part.

                // Add the redlines in between patterns
                newTiming.AddRange(transformOriginalTiming.GetRedlinesInRange(lastEndTime, startTime, false));

                var startOriginalRedline = transformOriginalTiming.GetRedlineAtTime(startTime);

                // Minus 1 the offset so its possible to have a custom BPM redline right on the start time if you have
                // the default BPM redline before it.
                var patternDefaultMpb = transformPatternTiming.GetMpBAtTime(startTime - 2 * Precision.DOUBLE_EPSILON);

                TimingPoint[] inPartRedlines;
                TimingPoint   startPartRedline;
                switch (timingOverwriteMode)
                {
                case TimingOverwriteMode.PatternTimingOnly:
                    // Subtract one from the end time to omit BPM changes right on the end of the part.
                    inPartRedlines = transformPatternTiming.GetRedlinesInRange(startTime,
                                                                               Math.Max(startTime, endTime - 2 * Precision.DOUBLE_EPSILON)).ToArray();
                    startPartRedline = transformPatternTiming.GetRedlineAtTime(startTime);
                    break;

                case TimingOverwriteMode.InPatternAbsoluteTiming:
                    var tempInPartRedlines = transformPatternTiming.GetRedlinesInRange(startTime, endTime - 2 * Precision.DOUBLE_EPSILON);

                    // Replace all parts in the pattern which have the default BPM to timing from the target beatmap.
                    inPartRedlines = tempInPartRedlines.Select(tp => {
                        if (Precision.AlmostEquals(tp.MpB, patternDefaultMpb))
                        {
                            var tp2    = transformOriginalTiming.GetRedlineAtTime(tp.Offset).Copy();
                            tp2.Offset = tp2.Offset;
                            return(tp2);
                        }

                        return(tp);
                    }).ToArray();

                    startPartRedline = startOriginalRedline;
                    break;

                case TimingOverwriteMode.InPatternRelativeTiming:
                    // Multiply mix the pattern timing and the original timing together.
                    // The pattern timing divided by the default BPM will be used as a scalar for the original timing.
                    var tempInPartRedlines2    = transformPatternTiming.GetRedlinesInRange(startTime, endTime - 2 * Precision.DOUBLE_EPSILON);
                    var tempInOriginalRedlines = transformOriginalTiming.GetRedlinesInRange(startTime, endTime - 2 * Precision.DOUBLE_EPSILON);

                    // Replace all parts in the pattern which have the default BPM to timing from the target beatmap.
                    inPartRedlines = tempInPartRedlines2.Select(tp => {
                        var tp2  = tp.Copy();
                        tp2.MpB *= transformOriginalTiming.GetMpBAtTime(tp.Offset) / patternDefaultMpb;
                        return(tp2);
                    }).Concat(tempInOriginalRedlines.Select(tp => {
                        var tp2  = tp.Copy();
                        tp2.MpB *= transformPatternTiming.GetMpBAtTime(tp.Offset) / patternDefaultMpb;
                        return(tp2);
                    })).ToArray();

                    startPartRedline      = transformPatternTiming.GetRedlineAtTime(startTime).Copy();
                    startPartRedline.MpB *= transformOriginalTiming.GetMpBAtTime(startTime) / patternDefaultMpb;
                    break;

                default:      // Original timing only
                    // Subtract one from the end time to omit BPM changes right on the end of the part.
                    inPartRedlines = transformOriginalTiming.GetRedlinesInRange(startTime,
                                                                                Math.Max(startTime, endTime - 2 * Precision.DOUBLE_EPSILON)).ToArray();
                    startPartRedline = transformOriginalTiming.GetRedlineAtTime(startTime);
                    break;
                }

                // Add the redlines for inside the part
                newTiming.AddRange(inPartRedlines);

                // If the pattern starts with different BPM than the map add an extra redline at the start of the pattern
                // to make sure it the pattern starts out at the right BPM as we only copy the timingpoints during the pattern itself
                // and the redline may be way before that.
                // This will probably only do something on the PatternTimingOnly mode as the other modes make sure
                // the BPM at the start of the pattern will be the same as the original beatmap anyways.
                if (Math.Abs(startPartRedline.MpB * bpmMultiplier - startOriginalRedline.MpB) > Precision.DOUBLE_EPSILON)
                {
                    // We dont have to add the redline again if its already during the pattern.
                    if (Math.Abs(startPartRedline.Offset - startTime) > Precision.DOUBLE_EPSILON)
                    {
                        var copy = startPartRedline.Copy();
                        copy.Offset = startTime;
                        newTiming.Add(copy);
                    }
                }

                // Fix SV for the new BPM, so the SV effect of the new BPM is cancelled
                if (FixBpmSv)
                {
                    if (scaleToNewTiming)
                    {
                        foreach (HitObject ho in patternBeatmap.HitObjects.Where(o => o.IsSlider))
                        {
                            var bpmSvFactor = SnapToNewTiming ?
                                              transformPatternTiming.GetMpBAtTime(ho.Time) /
                                              newTiming.GetMpBAtTime(newTiming.ResnapBeatTime(ho.Time, BeatDivisors)) :
                                              transformPatternTiming.GetMpBAtTime(ho.Time) /
                                              newTiming.GetMpBAtTime(ho.Time);
                            ho.SliderVelocity *= bpmSvFactor;
                        }
                    }
                    else
                    {
                        foreach (HitObject ho in patternBeatmap.HitObjects.Where(o => o.IsSlider))
                        {
                            var bpmSvFactor = SnapToNewTiming ?
                                              transformPatternTiming.GetMpBAtTime(ho.Time) / newTiming.GetMpBAtTime(newTiming.Resnap(ho.Time, BeatDivisors)) :
                                              transformPatternTiming.GetMpBAtTime(ho.Time) / newTiming.GetMpBAtTime(ho.Time);
                            ho.SliderVelocity *= bpmSvFactor;
                        }
                    }
                }

                // Recalculate temporal length and re-assign redline for the sliderend resnapping later
                foreach (var ho in part.HitObjects)
                {
                    ho.UnInheritedTimingPoint = newTiming.GetRedlineAtTime(ho.Time);
                    if (ho.IsSlider)
                    {
                        // If scaleToNewTiming then the end time is already at the correct beat time
                        // The SV has to be adjusted so the sliderend is really on the end time
                        if (scaleToNewTiming)
                        {
                            var wantedMsDuration = (newTiming.GetMilliseconds(ho.GetEndTime(false), patternStartTime) -
                                                    newTiming.GetMilliseconds(ho.Time, patternStartTime)) / ho.Repeat;
                            var trueMsDuration = newTiming.CalculateSliderTemporalLength(SnapToNewTiming ? newTiming.ResnapBeatTime(ho.Time, BeatDivisors) : ho.Time, ho.PixelLength, ho.SliderVelocity);
                            ho.SliderVelocity /= trueMsDuration / wantedMsDuration;
                        }
                        else
                        {
                            ho.TemporalLength = newTiming.CalculateSliderTemporalLength(SnapToNewTiming ? newTiming.Resnap(ho.Time, BeatDivisors) : ho.Time, ho.PixelLength, ho.SliderVelocity);
                        }
                    }
                }

                // Update the end time because the lengths of sliders changed
                endTime      = part.HitObjects.Max(o => o.GetEndTime(!scaleToNewTiming));
                part.EndTime = endTime;

                // Add a redline at the end of the pattern to make sure the BPM goes back to normal after the pattern.
                var endOriginalRedline = transformOriginalTiming.GetRedlineAtTime(endTime);
                var endPartRedline     = inPartRedlines.LastOrDefault() ?? startPartRedline;
                if (Math.Abs(endPartRedline.MpB * bpmMultiplier - endOriginalRedline.MpB) > Precision.DOUBLE_EPSILON)
                {
                    // We dont have to add the redline again if its already during the parts in between parts.
                    if (Math.Abs(endOriginalRedline.Offset - endTime) > Precision.DOUBLE_EPSILON)
                    {
                        var copy = endOriginalRedline.Copy();
                        copy.Offset = endTime;
                        newTiming.Add(copy);
                    }
                }

                lastEndTime = endTime;
            }

            // Transform the beat time back to millisecond time
            Timing transformNewTiming = newTiming;

            if (scaleToNewTiming)
            {
                // Transform back the timing
                transformNewTiming = newTiming.Copy();
                foreach (var tp in transformNewTiming.TimingPoints)
                {
                    tp.Offset = Math.Floor(newTiming.GetMilliseconds(tp.Offset, patternStartTime) + Precision.DOUBLE_EPSILON);
                }

                // Transform back the parts
                foreach (Part part in parts)
                {
                    part.StartTime = Math.Floor(newTiming.GetMilliseconds(part.StartTime, patternStartTime));
                    part.EndTime   = Math.Floor(newTiming.GetMilliseconds(part.EndTime, patternStartTime));
                }

                // Transform everything to millisecond time relative to pattern start time
                foreach (var ho in patternBeatmap.HitObjects)
                {
                    // Calculate the millisecond end time before changing the start time because the end time getter uses the beat time start time
                    var msEndTime = newTiming.GetMilliseconds(ho.GetEndTime(false), patternStartTime);

                    ho.Time = newTiming.GetMilliseconds(ho.Time, patternStartTime);

                    // End time has to be set after the time because the end time setter uses the millisecond start time
                    ho.EndTime = msEndTime;

                    foreach (var tp in ho.BodyHitsounds)
                    {
                        tp.Offset = newTiming.GetMilliseconds(tp.Offset, patternStartTime);
                    }

                    // It is necessary to resnap early so it can recalculate the duration using the right offset
                    if (SnapToNewTiming)
                    {
                        ho.ResnapSelf(transformNewTiming, BeatDivisors);
                    }

                    if (ho.IsSlider)
                    {
                        ho.CalculateSliderTemporalLength(transformNewTiming, true);
                    }

                    ho.UnInheritedTimingPoint = transformNewTiming.GetRedlineAtTime(ho.Time);
                    ho.UpdateTimelineObjectTimes();
                }

                foreach (var tp in kiaiToggles.Concat(svChanges))
                {
                    tp.Offset = Math.Floor(newTiming.GetMilliseconds(tp.Offset, patternStartTime));
                }
            }

            // Apply custom scale and rotate
            if (Math.Abs(spatialScale - 1) > Precision.DOUBLE_EPSILON ||
                Math.Abs(CustomRotate) > Precision.DOUBLE_EPSILON)
            {
                // Create a transformation matrix for the custom scale and rotate
                // The rotation is inverted because the default osu! rotation goes clockwise
                Matrix2 transform = Matrix2.Mult(Matrix2.CreateScale(spatialScale), Matrix2.CreateRotation(-CustomRotate));
                Vector2 centre    = new Vector2(256, 192);
                foreach (var ho in patternBeatmap.HitObjects)
                {
                    ho.Move(-centre);
                    ho.Transform(transform);
                    ho.Move(centre);

                    // Scale pixel length and SV for sliders aswell
                    if (ho.IsSlider)
                    {
                        ho.PixelLength    *= spatialScale;
                        ho.SliderVelocity /= spatialScale;
                    }
                }

                // osu! clips coordinates to the bounds (0,512), so there is some space downwards to still place the pattern
                // Calculate the new bounds of the pattern and try to place it in the playfield
                var     minX   = patternBeatmap.HitObjects.Min(o => o.Pos.X);
                var     minY   = patternBeatmap.HitObjects.Min(o => o.Pos.Y);
                Vector2 offset = new Vector2(Math.Max(-minX, 0), Math.Max(-minY, 0));
                if (offset.LengthSquared > 0)
                {
                    foreach (var ho in patternBeatmap.HitObjects)
                    {
                        ho.Move(offset);
                    }
                }
            }

            // Manualify stacks
            if (FixStackLeniency)
            {
                // If scale to new timing was used update the circle size of the pattern,
                // so it calculates stacks at the new size of the pattern.
                if (ScaleToNewCircleSize)
                {
                    patternBeatmap.Difficulty["CircleSize"].DoubleValue = originalCircleSize;
                }

                patternBeatmap.CalculateEndPositions();
                patternBeatmap.UpdateStacking(rounded: true);

                // Manualify by setting the base position to the stacked position
                foreach (var ho in patternBeatmap.HitObjects)
                {
                    var offset = ho.StackedPos - ho.Pos;
                    ho.Move(offset);
                }
            }

            // Resnap everything to the new timing.
            if (SnapToNewTiming)
            {
                // Resnap all objects
                foreach (HitObject ho in patternBeatmap.HitObjects)
                {
                    ho.ResnapSelf(transformNewTiming, BeatDivisors);
                    ho.ResnapEnd(transformNewTiming, BeatDivisors);
                    ho.ResnapPosition(targetMode, patternCircleSize);  // Resnap to column X positions for mania only
                }
                // Resnap Kiai toggles
                foreach (TimingPoint tp in kiaiToggles)
                {
                    tp.ResnapSelf(transformNewTiming, BeatDivisors);
                }

                // Resnap SliderVelocity changes
                foreach (TimingPoint tp in svChanges)
                {
                    tp.ResnapSelf(transformNewTiming, BeatDivisors);
                }
            }

            // Multiply BPM and divide SV
            foreach (var part in parts)
            {
                foreach (var tp in transformNewTiming.GetRedlinesInRange(part.StartTime - 2 * Precision.DOUBLE_EPSILON, part.EndTime, false))
                {
                    tp.MpB /= bpmMultiplier;  // MpB is the inverse of the BPM
                }

                foreach (var ho in part.HitObjects)
                {
                    ho.SliderVelocity *= bpmMultiplier;  // SliderVelocity is the inverse of the multiplier
                }
            }

            // Make new timingpoints changes for the hitsounds and other stuff

            // Add redlines
            timingPointsChanges = transformNewTiming.Redlines.Select(tp =>
                                                                     new TimingPointsChange(tp, mpb: true, meter: true, unInherited: true, omitFirstBarLine: true, fuzzyness: Precision.DOUBLE_EPSILON)).ToList();

            // Add SliderVelocity changes for taiko and mania
            if (includePatternSliderVelocity && (targetMode == GameMode.Taiko || targetMode == GameMode.Mania))
            {
                timingPointsChanges.AddRange(svChanges.Select(tp => new TimingPointsChange(tp, mpb: true, fuzzyness: 0.4)));
            }

            // Add Kiai toggles
            timingPointsChanges.AddRange(kiaiToggles.Select(tp => new TimingPointsChange(tp, kiai: true)));

            // Add Hitobject stuff
            foreach (HitObject ho in patternBeatmap.HitObjects)
            {
                if (ho.IsSlider) // SliderVelocity changes
                {
                    TimingPoint tp = ho.TimingPoint.Copy();
                    tp.Offset = ho.Time;
                    tp.MpB    = ho.SliderVelocity;
                    timingPointsChanges.Add(new TimingPointsChange(tp, mpb: true, fuzzyness: 0.4));
                }

                if (!IncludeHitsounds)
                {
                    // Remove hitsounds and skip adding body hitsounds
                    ho.ResetHitsounds();
                    continue;
                }

                if (includeTimingPointHitsounds)
                {
                    // Body hitsounds
                    bool vol = ho.IsSlider || ho.IsSpinner;
                    bool sam = ho.IsSlider && ho.SampleSet == 0;
                    bool ind = ho.IsSlider;
                    timingPointsChanges.AddRange(ho.BodyHitsounds.Select(tp =>
                                                                         new TimingPointsChange(tp, volume: vol, index: ind, sampleset: sam)));
                }
            }

            // Add timeline hitsounds
            if (includeTimingPointHitsounds)
            {
                foreach (TimelineObject tlo in patternTimeline.TimelineObjects)
                {
                    if (tlo.HasHitsound)
                    {
                        // Add greenlines for hitsounds
                        TimingPoint tp = tlo.HitsoundTimingPoint.Copy();
                        tp.Offset = tlo.Time;
                        timingPointsChanges.Add(new TimingPointsChange(tp, sampleset: true, volume: true, index: true));
                    }
                }
            }

            // Replace the old timingpoints
            patternTiming.Clear();
            TimingPointsChange.ApplyChanges(patternTiming, timingPointsChanges);

            patternBeatmap.GiveObjectsGreenlines();
            patternBeatmap.CalculateSliderEndTimes();
        }
Example #9
0
        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!");
        }
        private string TransformProperties(PropertyTransformerVM vm, BackgroundWorker worker, DoWorkEventArgs _)
        {
            bool   doFilterMatch = vm.MatchFilter != -1 && vm.EnableFilters;
            bool   doFilterRange = (vm.MinTimeFilter != -1 || vm.MaxTimeFilter != -1) && vm.EnableFilters;
            double min           = vm.MinTimeFilter == -1 ? double.NegativeInfinity : vm.MinTimeFilter;
            double max           = vm.MaxTimeFilter == -1 ? double.PositiveInfinity : vm.MaxTimeFilter;

            bool editorRead = EditorReaderStuff.TryGetFullEditorReader(out var reader);

            foreach (string path in vm.MapPaths)
            {
                var     editor  = EditorReaderStuff.GetBeatmapEditor(path, reader, editorRead);
                Beatmap beatmap = editor.Beatmap;

                // Count all the total amount of things to loop through
                int loops      = 0;
                int totalLoops = beatmap.BeatmapTiming.TimingPoints.Count;
                if (vm.HitObjectTimeMultiplier != 1 || vm.HitObjectTimeOffset != 0)
                {
                    totalLoops += beatmap.HitObjects.Count;
                }
                if (vm.BookmarkTimeMultiplier != 1 || vm.BookmarkTimeOffset != 0)
                {
                    totalLoops += beatmap.GetBookmarks().Count;
                }
                if (vm.SBSampleTimeMultiplier != 1 || vm.SBSampleTimeOffset != 0)
                {
                    totalLoops += beatmap.StoryboardSoundSamples.Count;
                }

                List <TimingPointsChange> timingPointsChanges = new List <TimingPointsChange>();
                foreach (TimingPoint tp in beatmap.BeatmapTiming.TimingPoints)
                {
                    // Offset
                    if (vm.TimingpointOffsetMultiplier != 1 || vm.TimingpointOffsetOffset != 0)
                    {
                        if (Filter(tp.Offset, tp.Offset, doFilterMatch, doFilterRange, vm.MatchFilter, min, max))
                        {
                            tp.Offset = Math.Round(tp.Offset * vm.TimingpointOffsetMultiplier + vm.TimingpointOffsetOffset);
                        }
                    }

                    // BPM
                    if (vm.TimingpointBPMMultiplier != 1 || vm.TimingpointBPMOffset != 0)
                    {
                        if (tp.Uninherited)
                        {
                            if (Filter(tp.GetBpm(), tp.Offset, doFilterMatch, doFilterRange, vm.MatchFilter, min, max))
                            {
                                double newBPM = tp.GetBpm() * vm.TimingpointBPMMultiplier + vm.TimingpointBPMOffset;
                                newBPM = vm.ClipProperties ? MathHelper.Clamp(newBPM, 15, 10000) : newBPM;  // Clip the value if specified
                                tp.MpB = 60000 / newBPM;
                            }
                        }
                    }

                    // Slider Velocity
                    if (vm.TimingpointSVMultiplier != 1 || vm.TimingpointSVOffset != 0)
                    {
                        if (Filter(beatmap.BeatmapTiming.GetSvMultiplierAtTime(tp.Offset), tp.Offset, doFilterMatch, doFilterRange, vm.MatchFilter, min, max))
                        {
                            TimingPoint tpchanger = tp.Copy();
                            double      newSV     = beatmap.BeatmapTiming.GetSvMultiplierAtTime(tp.Offset) * vm.TimingpointSVMultiplier + vm.TimingpointSVOffset;
                            newSV         = vm.ClipProperties ? MathHelper.Clamp(newSV, 0.1, 10) : newSV; // Clip the value if specified
                            tpchanger.MpB = -100 / newSV;
                            timingPointsChanges.Add(new TimingPointsChange(tpchanger, mpb: true));
                        }
                    }

                    // Index
                    if (vm.TimingpointIndexMultiplier != 1 || vm.TimingpointIndexOffset != 0)
                    {
                        if (Filter(tp.SampleIndex, tp.Offset, doFilterMatch, doFilterRange, vm.MatchFilter, min, max))
                        {
                            int newIndex = (int)Math.Round(tp.SampleIndex * vm.TimingpointIndexMultiplier + vm.TimingpointIndexOffset);
                            tp.SampleIndex = vm.ClipProperties ? MathHelper.Clamp(newIndex, 0, 100) : newIndex;
                        }
                    }

                    // Volume
                    if (vm.TimingpointVolumeMultiplier != 1 || vm.TimingpointVolumeOffset != 0)
                    {
                        if (Filter(tp.Volume, tp.Offset, doFilterMatch, doFilterRange, vm.MatchFilter, min, max))
                        {
                            int newVolume = (int)Math.Round(tp.Volume * vm.TimingpointVolumeMultiplier + vm.TimingpointVolumeOffset);
                            tp.Volume = vm.ClipProperties ? MathHelper.Clamp(newVolume, 5, 100) : newVolume;
                        }
                    }

                    // Update progress bar
                    loops++;
                    UpdateProgressBar(worker, loops * 100 / totalLoops);
                }

                // Hitobject Time
                if (vm.HitObjectTimeMultiplier != 1 || vm.HitObjectTimeOffset != 0)
                {
                    foreach (HitObject ho in beatmap.HitObjects)
                    {
                        if (Filter(ho.Time, ho.Time, doFilterMatch, doFilterRange, vm.MatchFilter, min, max))
                        {
                            ho.Time = Math.Round(ho.Time * vm.HitObjectTimeMultiplier + vm.HitObjectTimeOffset);
                        }

                        // Update progress bar
                        loops++;
                        UpdateProgressBar(worker, loops * 100 / totalLoops);
                    }
                }

                // Bookmark Time
                if (vm.BookmarkTimeMultiplier != 1 || vm.BookmarkTimeOffset != 0)
                {
                    List <double> newBookmarks = new List <double>();
                    List <double> bookmarks    = beatmap.GetBookmarks();
                    foreach (double bookmark in bookmarks)
                    {
                        if (Filter(bookmark, bookmark, doFilterMatch, doFilterRange, vm.MatchFilter, min, max))
                        {
                            newBookmarks.Add(Math.Round(bookmark * vm.BookmarkTimeMultiplier + vm.BookmarkTimeOffset));
                        }
                        else
                        {
                            newBookmarks.Add(bookmark);
                        }

                        // Update progress bar
                        loops++;
                        UpdateProgressBar(worker, loops * 100 / totalLoops);
                    }
                    beatmap.SetBookmarks(newBookmarks);
                }

                // Storyboarded sample Time
                if (vm.SBSampleTimeMultiplier != 1 || vm.SBSampleTimeOffset != 0)
                {
                    foreach (StoryboardSoundSample ss in beatmap.StoryboardSoundSamples)
                    {
                        if (Filter(ss.Time, ss.Time, doFilterMatch, doFilterRange, vm.MatchFilter, min, max))
                        {
                            ss.Time = Math.Round(ss.Time * vm.SBSampleTimeMultiplier + vm.SBSampleTimeOffset);
                        }

                        // Update progress bar
                        loops++;
                        UpdateProgressBar(worker, loops * 100 / totalLoops);
                    }
                }

                TimingPointsChange.ApplyChanges(beatmap.BeatmapTiming, timingPointsChanges);

                // Save the file
                editor.SaveFile();
            }

            return("Done!");
        }
Example #11
0
        private string Complete_Sliders(SliderCompletionatorVm arg, BackgroundWorker worker, DoWorkEventArgs _)
        {
            int slidersCompleted = 0;

            var reader = EditorReaderStuff.GetFullEditorReaderOrNot(out var editorReaderException1);

            if (arg.ImportModeSetting == SliderCompletionatorVm.ImportMode.Selected && editorReaderException1 != null)
            {
                throw new Exception("Could not fetch selected hit objects.", editorReaderException1);
            }

            foreach (string path in arg.Paths)
            {
                var editor = EditorReaderStuff.GetNewestVersionOrNot(path, reader, out var selected, out var editorReaderException2);

                if (arg.ImportModeSetting == SliderCompletionatorVm.ImportMode.Selected && editorReaderException2 != null)
                {
                    throw new Exception("Could not fetch selected hit objects.", editorReaderException2);
                }

                Beatmap beatmap = editor.Beatmap;
                Timing  timing  = beatmap.BeatmapTiming;

                List <HitObject> markedObjects = arg.ImportModeSetting switch {
                    SliderCompletionatorVm.ImportMode.Selected => selected,
                    SliderCompletionatorVm.ImportMode.Bookmarked => beatmap.GetBookmarkedObjects(),
                    SliderCompletionatorVm.ImportMode.Time => beatmap.QueryTimeCode(arg.TimeCode).ToList(),
                    SliderCompletionatorVm.ImportMode.Everything => beatmap.HitObjects,
                    _ => throw new ArgumentException("Unexpected import mode.")
                };

                for (int i = 0; i < markedObjects.Count; i++)
                {
                    HitObject ho = markedObjects[i];
                    if (ho.IsSlider)
                    {
                        double mpb = timing.GetMpBAtTime(ho.Time);

                        double oldDuration = timing.CalculateSliderTemporalLength(ho.Time, ho.PixelLength);
                        double oldLength   = ho.PixelLength;
                        double oldSv       = timing.GetSvAtTime(ho.Time);

                        double newDuration = arg.UseEndTime ? arg.EndTime == -1 ? oldDuration : arg.EndTime - ho.Time :
                                             arg.Duration == -1 ? oldDuration : timing.WalkBeatsInMillisecondTime(arg.Duration, ho.Time) - ho.Time;
                        double newLength = arg.Length == -1 ? oldLength : ho.GetSliderPath(fullLength: true).Distance *arg.Length;
                        double newSv     = arg.SliderVelocity == -1 ? oldSv : -100 / arg.SliderVelocity;

                        switch (arg.FreeVariableSetting)
                        {
                        case SliderCompletionatorVm.FreeVariable.Velocity:
                            newSv = -10000 * timing.SliderMultiplier * newDuration / (newLength * mpb);
                            break;

                        case SliderCompletionatorVm.FreeVariable.Duration:
                            // This actually doesn't get used anymore because the .osu doesn't store the duration
                            newDuration = newLength * newSv * mpb / (-10000 * timing.SliderMultiplier);
                            break;

                        case SliderCompletionatorVm.FreeVariable.Length:
                            newLength = -10000 * timing.SliderMultiplier * newDuration / (newSv * mpb);
                            break;

                        default:
                            throw new ArgumentException("Unexpected free variable setting.");
                        }

                        if (double.IsNaN(newSv))
                        {
                            throw new Exception("Encountered NaN slider velocity. Make sure none of the inputs are zero.");
                        }

                        if (newDuration < 0)
                        {
                            throw new Exception("Encountered slider with negative duration. Make sure the end time is greater than the end time of all selected sliders.");
                        }

                        ho.SliderVelocity = newSv;
                        ho.PixelLength    = newLength;

                        // Scale anchors to completion
                        if (arg.MoveAnchors)
                        {
                            ho.SetAllCurvePoints(SliderPathUtil.MoveAnchorsToLength(
                                                     ho.GetAllCurvePoints(), ho.SliderType, ho.PixelLength, out var pathType));
                            ho.SliderType = pathType;
                        }

                        slidersCompleted++;
                    }
                    if (worker != null && worker.WorkerReportsProgress)
                    {
                        worker.ReportProgress(i / markedObjects.Count);
                    }
                }

                // Reconstruct SliderVelocity
                List <TimingPointsChange> timingPointsChanges = new List <TimingPointsChange>();
                // Add Hitobject stuff
                foreach (HitObject ho in beatmap.HitObjects)
                {
                    // SliderVelocity changes
                    if (ho.IsSlider)
                    {
                        if (markedObjects.Contains(ho) && arg.DelegateToBpm)
                        {
                            var tpAfter = timing.GetRedlineAtTime(ho.Time).Copy();
                            var tpOn    = tpAfter.Copy();

                            tpAfter.Offset = ho.Time;
                            tpOn.Offset    = ho.Time - 1; // This one will be on the slider

                            tpAfter.OmitFirstBarLine = true;
                            tpOn.OmitFirstBarLine    = true;

                            // Express velocity in BPM
                            tpOn.MpB *= ho.SliderVelocity / -100;
                            // NaN SV results in removal of slider ticks
                            ho.SliderVelocity = arg.RemoveSliderTicks ? double.NaN : -100;

                            // Add redlines
                            timingPointsChanges.Add(new TimingPointsChange(tpOn, mpb: true, unInherited: true, omitFirstBarLine: true, fuzzyness: Precision.DOUBLE_EPSILON));
                            timingPointsChanges.Add(new TimingPointsChange(tpAfter, mpb: true, unInherited: true, omitFirstBarLine: true, fuzzyness: Precision.DOUBLE_EPSILON));

                            ho.Time -= 1;
                        }

                        TimingPoint tp = ho.TimingPoint.Copy();
                        tp.Offset = ho.Time;
                        tp.MpB    = ho.SliderVelocity;
                        timingPointsChanges.Add(new TimingPointsChange(tp, mpb: true, fuzzyness: Precision.DOUBLE_EPSILON));
                    }
                }

                // Add the new SliderVelocity changes
                TimingPointsChange.ApplyChanges(timing, timingPointsChanges);

                // Save the file
                editor.SaveFile();
            }

            // Complete progressbar
            if (worker != null && worker.WorkerReportsProgress)
            {
                worker.ReportProgress(100);
            }

            // Do stuff
            RunFinished?.Invoke(this, new RunToolCompletedEventArgs(true, reader != null, arg.Quick));

            // Make an accurate message
            string message = "";

            if (Math.Abs(slidersCompleted) == 1)
            {
                message += "Successfully completed " + slidersCompleted + " slider!";
            }
            else
            {
                message += "Successfully completed " + slidersCompleted + " sliders!";
            }
            return(arg.Quick ? "" : message);
        }
        private string Complete_Sliders(Arguments arg, BackgroundWorker worker, DoWorkEventArgs _)
        {
            int slidersCompleted = 0;

            bool editorRead = EditorReaderStuff.TryGetFullEditorReader(out var reader);

            foreach (string path in arg.Paths)
            {
                var editor = EditorReaderStuff.GetBeatmapEditor(path, reader, editorRead, out var selected, out var editorActuallyRead);

                if (arg.SelectionMode == 0 && !editorActuallyRead)
                {
                    return(EditorReaderStuff.SelectedObjectsReadFailText);
                }

                Beatmap          beatmap       = editor.Beatmap;
                Timing           timing        = beatmap.BeatmapTiming;
                List <HitObject> markedObjects = arg.SelectionMode == 0 ? selected :
                                                 arg.SelectionMode == 1 ? beatmap.GetBookmarkedObjects() :
                                                 beatmap.HitObjects;

                for (int i = 0; i < markedObjects.Count; i++)
                {
                    HitObject ho = markedObjects[i];
                    if (ho.IsSlider)
                    {
                        double oldSpatialLength = ho.PixelLength;
                        double newSpatialLength = arg.SpatialLength != -1 ? ho.GetSliderPath(fullLength: true).Distance *arg.SpatialLength : oldSpatialLength;

                        double oldTemporalLength = timing.CalculateSliderTemporalLength(ho.Time, ho.PixelLength);
                        double newTemporalLength = arg.TemporalLength != -1 ? timing.GetMpBAtTime(ho.Time) * arg.TemporalLength : oldTemporalLength;

                        double oldSv = timing.GetSvAtTime(ho.Time);
                        double newSv = oldSv / ((newSpatialLength / oldSpatialLength) / (newTemporalLength / oldTemporalLength));

                        ho.SliderVelocity = newSv;
                        ho.PixelLength    = newSpatialLength;

                        // Scale anchors to completion
                        if (arg.MoveAnchors)
                        {
                            ho.SetAllCurvePoints(SliderPathUtil.MoveAnchorsToLength(
                                                     ho.GetAllCurvePoints(), ho.SliderType, ho.PixelLength, out var pathType));
                            ho.SliderType = pathType;
                        }

                        slidersCompleted++;
                    }
                    if (worker != null && worker.WorkerReportsProgress)
                    {
                        worker.ReportProgress(i / markedObjects.Count);
                    }
                }

                // Reconstruct SliderVelocity
                List <TimingPointsChange> timingPointsChanges = new List <TimingPointsChange>();
                // Add Hitobject stuff
                foreach (HitObject ho in beatmap.HitObjects)
                {
                    if (ho.IsSlider) // SliderVelocity changes
                    {
                        TimingPoint tp = ho.TimingPoint.Copy();
                        tp.Offset = ho.Time;
                        tp.MpB    = ho.SliderVelocity;
                        timingPointsChanges.Add(new TimingPointsChange(tp, mpb: true));
                    }
                }

                // Add the new SliderVelocity changes
                TimingPointsChange.ApplyChanges(timing, timingPointsChanges);

                // Save the file
                editor.SaveFile();
            }

            // Complete progressbar
            if (worker != null && worker.WorkerReportsProgress)
            {
                worker.ReportProgress(100);
            }

            // Do stuff
            if (arg.Quick)
            {
                RunFinished?.Invoke(this, new RunToolCompletedEventArgs(true, editorRead));
            }

            // Make an accurate message
            string message = "";

            if (Math.Abs(slidersCompleted) == 1)
            {
                message += "Successfully completed " + slidersCompleted + " slider!";
            }
            else
            {
                message += "Successfully completed " + slidersCompleted + " sliders!";
            }
            return(arg.Quick ? "" : message);
        }
Example #13
0
        /// <summary>
        /// Places each hit object of the pattern beatmap into the other beatmap and applies timingpoint changes to copy timingpoint stuff aswell.
        /// The given pattern beatmap could be modified by this method if protectBeatmapPattern is false.
        /// </summary>
        /// <param name="patternBeatmap">The pattern beatmap to be placed into the beatmap.</param>
        /// <param name="beatmap">To beatmap to place the pattern in.</param>
        /// <param name="offset">An offset to move the pattern beatmap in time with.</param>
        /// <param name="protectBeatmapPattern">If true, copies the pattern beatmap to prevent the pattern beatmap from being modified by this method.</param>
        public void PlaceOsuPattern(Beatmap patternBeatmap, Beatmap beatmap, double offset = 0, bool protectBeatmapPattern = true)
        {
            if (protectBeatmapPattern)
            {
                // Copy so the original pattern doesnt get changed
                patternBeatmap = patternBeatmap.DeepCopy();
            }

            if (offset != 0)
            {
                patternBeatmap.OffsetTime(offset);
            }

            // Do some kind of processing to fix timing etc
            // Set the global SV and BPM in the pattern beatmap so the object end times can be calculated for the partitioning
            patternBeatmap.BeatmapTiming.SliderMultiplier = beatmap.BeatmapTiming.SliderMultiplier;
            patternBeatmap.BeatmapTiming.TimingPoints.RemoveAll(tp => tp.Uninherited);
            patternBeatmap.BeatmapTiming.TimingPoints.AddRange(beatmap.BeatmapTiming.GetAllRedlines());
            patternBeatmap.BeatmapTiming.Sort();
            patternBeatmap.CalculateSliderEndTimes();

            // Partition the pattern beatmap
            List <Tuple <double, double> > parts;

            if (PatternOverwriteMode == PatternOverwriteMode.PartitionedOverwrite)
            {
                parts = PartitionBeatmap(patternBeatmap);
            }
            else
            {
                parts = new List <Tuple <double, double> > {
                    new Tuple <double, double>(patternBeatmap.GetHitObjectStartTime(), patternBeatmap.GetHitObjectEndTime())
                };
            }

            // Remove stuff
            if (PatternOverwriteMode != PatternOverwriteMode.NoOverwrite)
            {
                foreach (var part in parts)
                {
                    RemovePartOfBeatmap(beatmap, part.Item1 - Padding, part.Item2 + Padding);
                }
            }

            // Add the hitobjects of the pattern
            beatmap.HitObjects.AddRange(patternBeatmap.HitObjects);

            // Add timingpoint changes for each timingpoint in a part in the pattern
            var timingPointsChanges = new List <TimingPointsChange>();

            foreach (var part in parts)
            {
                timingPointsChanges.AddRange(
                    patternBeatmap.BeatmapTiming.TimingPoints.Where(tp => tp.Offset >= part.Item1 - Padding &&
                                                                    tp.Offset <= part.Item2 + Padding)
                    .Select(tp => GetTimingPointsChange(tp, true, true)));
            }

            // Add timingpoint changes for each hitobject to make sure they still have the wanted SV and hitsounds (especially near the edges of parts)
            // It is possible for the timingpoint of a hitobject at the start of a part to be outside of the part, so this fixes issues related to that
            timingPointsChanges.AddRange(
                beatmap.HitObjects.Where(ho => ho.TimingPoint != null)
                .Select(ho => GetTimingPointsChange(ho, true, true)));

            // Apply the changes
            TimingPointsChange.ApplyChanges(beatmap.BeatmapTiming, timingPointsChanges);

            // Sort hitobjects later so the timingpoints changes from the new hitobjects have priority
            beatmap.SortHitObjects();
        }
        private string TransformProperties(PropertyTransformerVm vm, BackgroundWorker worker, DoWorkEventArgs _)
        {
            var reader = EditorReaderStuff.GetFullEditorReaderOrNot();

            foreach (string path in vm.ExportPaths)
            {
                Editor editor;
                if (Path.GetExtension(path) == ".osb")
                {
                    editor = new StoryboardEditor(path);
                }
                else
                {
                    editor = EditorReaderStuff.GetNewestVersionOrNot(path, reader);
                }

                if (editor is BeatmapEditor beatmapEditor)
                {
                    Beatmap beatmap = beatmapEditor.Beatmap;

                    List <TimingPointsChange> timingPointsChanges = new List <TimingPointsChange>();
                    foreach (TimingPoint tp in beatmap.BeatmapTiming.TimingPoints)
                    {
                        // Offset
                        if (vm.TimingpointOffsetMultiplier != 1 || vm.TimingpointOffsetOffset != 0)
                        {
                            if (Filter(tp.Offset, tp.Offset, vm))
                            {
                                tp.Offset = Math.Round(tp.Offset * vm.TimingpointOffsetMultiplier +
                                                       vm.TimingpointOffsetOffset);
                            }
                        }

                        // BPM
                        if (vm.TimingpointBPMMultiplier != 1 || vm.TimingpointBPMOffset != 0)
                        {
                            if (tp.Uninherited)
                            {
                                if (Filter(tp.GetBpm(), tp.Offset, vm))
                                {
                                    double newBPM = tp.GetBpm() * vm.TimingpointBPMMultiplier + vm.TimingpointBPMOffset;
                                    newBPM = vm.ClipProperties
                                        ? MathHelper.Clamp(newBPM, 15, 10000)
                                        : newBPM; // Clip the value if specified
                                    tp.MpB = 60000 / newBPM;
                                }
                            }
                        }

                        // Slider Velocity
                        if (vm.TimingpointSVMultiplier != 1 || vm.TimingpointSVOffset != 0)
                        {
                            if (Filter(beatmap.BeatmapTiming.GetSvMultiplierAtTime(tp.Offset), tp.Offset, vm))
                            {
                                TimingPoint tpchanger = tp.Copy();
                                double      newSV     =
                                    beatmap.BeatmapTiming.GetSvMultiplierAtTime(tp.Offset) *
                                    vm.TimingpointSVMultiplier + vm.TimingpointSVOffset;
                                newSV = vm.ClipProperties
                                    ? MathHelper.Clamp(newSV, 0.1, 10)
                                    : newSV; // Clip the value if specified
                                tpchanger.MpB = -100 / newSV;
                                timingPointsChanges.Add(new TimingPointsChange(tpchanger, mpb: true));
                            }
                        }

                        // Index
                        if (vm.TimingpointIndexMultiplier != 1 || vm.TimingpointIndexOffset != 0)
                        {
                            if (Filter(tp.SampleIndex, tp.Offset, vm))
                            {
                                int newIndex =
                                    (int)Math.Round(tp.SampleIndex * vm.TimingpointIndexMultiplier +
                                                    vm.TimingpointIndexOffset);
                                tp.SampleIndex = vm.ClipProperties ? MathHelper.Clamp(newIndex, 0, int.MaxValue) : newIndex;
                            }
                        }

                        // Volume
                        if (vm.TimingpointVolumeMultiplier != 1 || vm.TimingpointVolumeOffset != 0)
                        {
                            if (Filter(tp.Volume, tp.Offset, vm))
                            {
                                int newVolume =
                                    (int)Math.Round(tp.Volume * vm.TimingpointVolumeMultiplier +
                                                    vm.TimingpointVolumeOffset);
                                tp.Volume = vm.ClipProperties ? MathHelper.Clamp(newVolume, 5, 100) : newVolume;
                            }
                        }
                    }

                    UpdateProgressBar(worker, 20);

                    // Hitobject time
                    if (vm.HitObjectTimeMultiplier != 1 || vm.HitObjectTimeOffset != 0)
                    {
                        foreach (HitObject ho in beatmap.HitObjects)
                        {
                            // Get the end time early because the start time gets modified
                            double oldEndTime = ho.GetEndTime(false);

                            if (Filter(ho.Time, ho.Time, vm))
                            {
                                ho.Time = Math.Round(ho.Time * vm.HitObjectTimeMultiplier + vm.HitObjectTimeOffset);
                            }

                            // Transform end time of hold notes and spinner
                            if ((ho.IsHoldNote || ho.IsSpinner) &&
                                Filter(oldEndTime, oldEndTime, vm))
                            {
                                ho.EndTime = Math.Round(oldEndTime * vm.HitObjectTimeMultiplier + vm.HitObjectTimeOffset);
                            }
                        }
                    }

                    UpdateProgressBar(worker, 30);

                    // Bookmark time
                    if (vm.BookmarkTimeMultiplier != 1 || vm.BookmarkTimeOffset != 0)
                    {
                        List <double> newBookmarks = new List <double>();
                        List <double> bookmarks    = beatmap.GetBookmarks();
                        foreach (double bookmark in bookmarks)
                        {
                            if (Filter(bookmark, bookmark, vm))
                            {
                                newBookmarks.Add(
                                    Math.Round(bookmark * vm.BookmarkTimeMultiplier + vm.BookmarkTimeOffset));
                            }
                            else
                            {
                                newBookmarks.Add(bookmark);
                            }
                        }

                        beatmap.SetBookmarks(newBookmarks);
                    }

                    UpdateProgressBar(worker, 40);

                    // Storyboarded event time
                    if (vm.SBEventTimeMultiplier != 1 || vm.SBEventTimeOffset != 0)
                    {
                        foreach (Event ev in beatmap.StoryboardLayerBackground.Concat(beatmap.StoryboardLayerFail)
                                 .Concat(beatmap.StoryboardLayerPass).Concat(beatmap.StoryboardLayerForeground)
                                 .Concat(beatmap.StoryboardLayerOverlay))
                        {
                            TransformEventTime(ev, vm.SBEventTimeMultiplier, vm.SBEventTimeOffset, vm);
                        }
                    }

                    UpdateProgressBar(worker, 50);

                    // Storyboarded sample time
                    if (vm.SBSampleTimeMultiplier != 1 || vm.SBSampleTimeOffset != 0)
                    {
                        foreach (StoryboardSoundSample ss in beatmap.StoryboardSoundSamples)
                        {
                            if (Filter(ss.StartTime, ss.StartTime, vm))
                            {
                                ss.StartTime =
                                    (int)Math.Round(ss.StartTime * vm.SBSampleTimeMultiplier + vm.SBSampleTimeOffset);
                            }
                        }
                    }

                    UpdateProgressBar(worker, 60);

                    // Break time
                    if (vm.BreakTimeMultiplier != 1 || vm.BreakTimeOffset != 0)
                    {
                        foreach (Break br in beatmap.BreakPeriods)
                        {
                            if (Filter(br.StartTime, br.StartTime, vm))
                            {
                                br.StartTime =
                                    (int)Math.Round(br.StartTime * vm.BreakTimeMultiplier + vm.BreakTimeOffset);
                            }

                            if (Filter(br.EndTime, br.EndTime, vm))
                            {
                                br.EndTime = (int)Math.Round(br.EndTime * vm.BreakTimeMultiplier + vm.BreakTimeOffset);
                            }
                        }
                    }

                    UpdateProgressBar(worker, 70);

                    // Video start time
                    if (vm.VideoTimeMultiplier != 1 || vm.VideoTimeOffset != 0)
                    {
                        foreach (Event ev in beatmap.BackgroundAndVideoEvents)
                        {
                            if (ev is Video video)
                            {
                                if (Filter(video.StartTime, video.StartTime, vm))
                                {
                                    video.StartTime =
                                        (int)Math.Round(video.StartTime * vm.VideoTimeMultiplier + vm.VideoTimeOffset);
                                }
                            }
                        }
                    }

                    UpdateProgressBar(worker, 80);

                    // Preview point time
                    if (vm.PreviewTimeMultiplier != 1 || vm.PreviewTimeOffset != 0)
                    {
                        if (beatmap.General.ContainsKey("PreviewTime") &&
                            beatmap.General["PreviewTime"].IntValue != -1)
                        {
                            var previewTime = beatmap.General["PreviewTime"].DoubleValue;
                            if (Filter(previewTime, previewTime, vm))
                            {
                                var newPreviewTime =
                                    Math.Round(previewTime * vm.PreviewTimeMultiplier + vm.PreviewTimeOffset);
                                beatmap.General["PreviewTime"].SetDouble(newPreviewTime);
                            }
                        }
                    }

                    UpdateProgressBar(worker, 90);

                    TimingPointsChange.ApplyChanges(beatmap.BeatmapTiming, timingPointsChanges);

                    // Save the file
                    beatmapEditor.SaveFile();

                    UpdateProgressBar(worker, 100);
                }
                else if (editor is StoryboardEditor storyboardEditor)
                {
                    StoryBoard storyboard = storyboardEditor.StoryBoard;

                    // Storyboarded event time
                    if (vm.SBEventTimeMultiplier != 1 || vm.SBEventTimeOffset != 0)
                    {
                        foreach (Event ev in storyboard.StoryboardLayerBackground.Concat(storyboard.StoryboardLayerFail)
                                 .Concat(storyboard.StoryboardLayerPass).Concat(storyboard.StoryboardLayerForeground)
                                 .Concat(storyboard.StoryboardLayerOverlay))
                        {
                            TransformEventTime(ev, vm.SBEventTimeMultiplier, vm.SBEventTimeOffset, vm);
                        }
                    }

                    UpdateProgressBar(worker, 50);

                    // Storyboarded sample time
                    if (vm.SBSampleTimeMultiplier != 1 || vm.SBSampleTimeOffset != 0)
                    {
                        foreach (StoryboardSoundSample ss in storyboard.StoryboardSoundSamples)
                        {
                            if (Filter(ss.StartTime, ss.StartTime, vm))
                            {
                                ss.StartTime =
                                    (int)Math.Round(ss.StartTime * vm.SBSampleTimeMultiplier + vm.SBSampleTimeOffset);
                            }
                        }
                    }

                    UpdateProgressBar(worker, 70);

                    // Video start time
                    if (vm.VideoTimeMultiplier != 1 || vm.VideoTimeOffset != 0)
                    {
                        foreach (Event ev in storyboard.BackgroundAndVideoEvents)
                        {
                            if (ev is Video video)
                            {
                                if (Filter(video.StartTime, video.StartTime, vm))
                                {
                                    video.StartTime =
                                        (int)Math.Round(video.StartTime * vm.VideoTimeMultiplier + vm.VideoTimeOffset);
                                }
                            }
                        }
                    }

                    UpdateProgressBar(worker, 90);

                    // Save the file
                    storyboardEditor.SaveFile();

                    UpdateProgressBar(worker, 100);
                }
            }

            return("Done!");
        }
Example #15
0
        /// <summary>
        /// Cleans a map.
        /// </summary>
        /// <param name="editor">The editor of the beatmap that is going to be cleaned.</param>
        /// <param name="args">The arguments for how to clean the beatmap.</param>
        /// <param name="worker">The BackgroundWorker for updating progress.</param>
        /// <returns>Number of resnapped objects.</returns>
        public static MapCleanerResult CleanMap(BeatmapEditor editor, MapCleanerArgs args, BackgroundWorker worker = null)
        {
            UpdateProgressBar(worker, 0);

            Beatmap beatmap = editor.Beatmap;
            Timing  timing  = beatmap.BeatmapTiming;

            GameMode mode       = (GameMode)beatmap.General["Mode"].IntValue;
            double   circleSize = beatmap.Difficulty["CircleSize"].DoubleValue;
            string   mapDir     = editor.GetParentFolder();
            Dictionary <string, string> firstSamples = HitsoundImporter.AnalyzeSamples(mapDir);

            int objectsResnapped = 0;
            int samplesRemoved   = 0;

            // Collect timeline objects before resnapping, so the timingpoints
            // are still valid and the tlo's get the correct hitsounds and offsets.
            // Resnapping of the hit objects will move the tlo's aswell
            Timeline timeline = beatmap.GetTimeline();

            // Collect Kiai toggles and SliderVelocity changes for mania/taiko
            List <TimingPoint> kiaiToggles = new List <TimingPoint>();
            List <TimingPoint> svChanges   = new List <TimingPoint>();
            bool   lastKiai = false;
            double lastSV   = -100;

            foreach (TimingPoint tp in timing.TimingPoints)
            {
                if (tp.Kiai != lastKiai)
                {
                    kiaiToggles.Add(tp.Copy());
                    lastKiai = tp.Kiai;
                }
                if (tp.Uninherited)
                {
                    lastSV = -100;
                }
                else
                {
                    if (tp.MpB != lastSV)
                    {
                        svChanges.Add(tp.Copy());
                        lastSV = tp.MpB;
                    }
                }
            }
            UpdateProgressBar(worker, 9);

            // Resnap shit
            if (args.ResnapObjects)
            {
                // Resnap all objects
                foreach (HitObject ho in beatmap.HitObjects)
                {
                    bool resnapped = ho.ResnapSelf(timing, args.BeatDivisors);
                    if (resnapped)
                    {
                        objectsResnapped += 1;
                    }
                    ho.ResnapEnd(timing, args.BeatDivisors);
                    ho.ResnapPosition(mode, circleSize);
                }
                UpdateProgressBar(worker, 18);

                // Resnap Kiai toggles
                foreach (TimingPoint tp in kiaiToggles)
                {
                    tp.ResnapSelf(timing, args.BeatDivisors);
                }
                UpdateProgressBar(worker, 27);

                // Resnap SliderVelocity changes
                foreach (TimingPoint tp in svChanges)
                {
                    tp.ResnapSelf(timing, args.BeatDivisors);
                }
                UpdateProgressBar(worker, 36);
            }

            if (args.ResnapBookmarks)
            {
                // Resnap the bookmarks
                List <double> bookmarks    = beatmap.GetBookmarks();
                List <double> newBookmarks = bookmarks.Select(o => timing.Resnap(o, args.BeatDivisors)).ToList();

                // Remove duplicate bookmarks
                newBookmarks = newBookmarks.Distinct().ToList();
                beatmap.SetBookmarks(newBookmarks);

                UpdateProgressBar(worker, 45);
            }

            // Make new timingpoints
            List <TimingPointsChange> timingPointsChanges = new List <TimingPointsChange>();

            // Add redlines
            var redlines = timing.Redlines;

            foreach (TimingPoint tp in redlines)
            {
                timingPointsChanges.Add(new TimingPointsChange(tp, mpb: true, meter: true, unInherited: true, omitFirstBarLine: true, fuzzyness: Precision.DOUBLE_EPSILON));
            }
            UpdateProgressBar(worker, 55);

            // Add SliderVelocity changes for taiko and mania
            if (mode == GameMode.Taiko || mode == GameMode.Mania)
            {
                foreach (TimingPoint tp in svChanges)
                {
                    timingPointsChanges.Add(new TimingPointsChange(tp, mpb: true));
                }
            }
            UpdateProgressBar(worker, 60);

            // Add Kiai toggles
            foreach (TimingPoint tp in kiaiToggles)
            {
                timingPointsChanges.Add(new TimingPointsChange(tp, kiai: true));
            }
            UpdateProgressBar(worker, 65);

            // Add Hitobject stuff
            foreach (HitObject ho in beatmap.HitObjects)
            {
                if (ho.IsSlider) // SliderVelocity changes
                {
                    TimingPoint tp = ho.TimingPoint.Copy();
                    tp.Offset = ho.Time;
                    tp.MpB    = ho.SliderVelocity;
                    timingPointsChanges.Add(new TimingPointsChange(tp, mpb: true));
                }
                // Body hitsounds
                bool vol = (ho.IsSlider && args.VolumeSliders) || (ho.IsSpinner && args.VolumeSpinners);
                bool sam = (ho.IsSlider && args.SampleSetSliders && ho.SampleSet == 0);
                bool ind = (ho.IsSlider && args.SampleSetSliders);
                bool samplesetActuallyChanged = false;
                foreach (TimingPoint tp in ho.BodyHitsounds)
                {
                    if (tp.Volume == 5 && args.RemoveMuting)
                    {
                        vol = false;  // Removing sliderbody silencing
                        ind = false;  // Removing silent custom index
                    }
                    timingPointsChanges.Add(new TimingPointsChange(tp, volume: vol, index: ind, sampleset: sam));
                    if (tp.SampleSet != ho.HitsoundTimingPoint.SampleSet)
                    {
                        samplesetActuallyChanged = args.SampleSetSliders && ho.SampleSet == 0;
                    }                                                                 // True for sampleset change in sliderbody
                }
                if (ho.IsSlider && (!samplesetActuallyChanged) && ho.SampleSet == 0)  // Case can put sampleset on sliderbody
                {
                    ho.SampleSet = ho.HitsoundTimingPoint.SampleSet;
                }
                if (ho.IsSlider && samplesetActuallyChanged) // Make it start out with the right sampleset
                {
                    TimingPoint tp = ho.HitsoundTimingPoint.Copy();
                    tp.Offset = ho.Time;
                    timingPointsChanges.Add(new TimingPointsChange(tp, sampleset: true));
                }
            }
            UpdateProgressBar(worker, 75);

            // Add timeline hitsounds
            foreach (TimelineObject tlo in timeline.TimelineObjects)
            {
                // Change the samplesets in the hitobjects
                if (tlo.Origin.IsCircle)
                {
                    tlo.Origin.SampleSet   = tlo.FenoSampleSet;
                    tlo.Origin.AdditionSet = tlo.FenoAdditionSet;
                    if (mode == GameMode.Mania)
                    {
                        tlo.Origin.CustomIndex  = tlo.FenoCustomIndex;
                        tlo.Origin.SampleVolume = tlo.FenoSampleVolume;
                    }
                }
                else if (tlo.Origin.IsSlider)
                {
                    tlo.Origin.EdgeHitsounds[tlo.Repeat]    = tlo.GetHitsounds();
                    tlo.Origin.EdgeSampleSets[tlo.Repeat]   = tlo.FenoSampleSet;
                    tlo.Origin.EdgeAdditionSets[tlo.Repeat] = tlo.FenoAdditionSet;
                    if (tlo.Origin.EdgeAdditionSets[tlo.Repeat] == tlo.Origin.EdgeSampleSets[tlo.Repeat])  // Simplify additions to auto
                    {
                        tlo.Origin.EdgeAdditionSets[tlo.Repeat] = 0;
                    }
                }
                else if (tlo.Origin.IsSpinner)
                {
                    if (tlo.Repeat == 1)
                    {
                        tlo.Origin.SampleSet   = tlo.FenoSampleSet;
                        tlo.Origin.AdditionSet = tlo.FenoAdditionSet;
                    }
                }
                else if (tlo.Origin.IsHoldNote)
                {
                    if (tlo.Repeat == 0)
                    {
                        tlo.Origin.SampleSet    = tlo.FenoSampleSet;
                        tlo.Origin.AdditionSet  = tlo.FenoAdditionSet;
                        tlo.Origin.CustomIndex  = tlo.FenoCustomIndex;
                        tlo.Origin.SampleVolume = tlo.FenoSampleVolume;
                    }
                }
                if (tlo.Origin.AdditionSet == tlo.Origin.SampleSet)  // Simplify additions to auto
                {
                    tlo.Origin.AdditionSet = 0;
                }
                if (tlo.HasHitsound) // Add greenlines for custom indexes and volumes
                {
                    TimingPoint tp = tlo.HitsoundTimingPoint.Copy();

                    bool doUnmute = tlo.FenoSampleVolume == 5 && args.RemoveMuting;
                    bool doMute   = args.RemoveUnclickableHitsounds && !args.RemoveMuting &&
                                    !(tlo.IsCircle || tlo.IsSliderHead || tlo.IsHoldnoteHead);

                    bool ind = !tlo.UsesFilename && !doUnmute; // Index doesnt have to change if custom is overridden by Filename
                    bool vol = !doUnmute;                      // Remove volume change muted

                    // Index doesn't have to change if the sample it plays currently is the same as the sample it would play with the previous index
                    if (ind)
                    {
                        List <string> nativeSamples = tlo.GetFirstPlayingFilenames(mode, mapDir, firstSamples);

                        int    oldIndex = tlo.FenoCustomIndex;
                        int    newIndex = tlo.FenoCustomIndex;
                        double latest   = double.NegativeInfinity;
                        foreach (TimingPointsChange tpc in timingPointsChanges)
                        {
                            if (tpc.Index && tpc.MyTP.Offset <= tlo.Time && tpc.MyTP.Offset >= latest)
                            {
                                newIndex = tpc.MyTP.SampleIndex;
                                latest   = tpc.MyTP.Offset;
                            }
                        }

                        tp.SampleIndex = newIndex;
                        tlo.GiveHitsoundTimingPoint(tp);
                        List <string> newSamples = tlo.GetFirstPlayingFilenames(mode, mapDir, firstSamples);
                        if (nativeSamples.SequenceEqual(newSamples))
                        {
                            // Index changes dont change sound
                            tp.SampleIndex = newIndex;
                        }
                        else
                        {
                            tp.SampleIndex = oldIndex;
                        }

                        tlo.GiveHitsoundTimingPoint(tp);
                    }

                    tp.Offset      = tlo.Time;
                    tp.SampleIndex = tlo.FenoCustomIndex;
                    tp.Volume      = doMute ? 5 : tlo.FenoSampleVolume;

                    timingPointsChanges.Add(new TimingPointsChange(tp, volume: vol, index: ind));
                }
            }
            UpdateProgressBar(worker, 85);

            // Replace the old timingpoints
            timing.Clear();
            TimingPointsChange.ApplyChanges(timing, timingPointsChanges);
            beatmap.GiveObjectsGreenlines();

            UpdateProgressBar(worker, 90);

            // Remove unused samples
            if (args.RemoveUnusedSamples)
            {
                RemoveUnusedSamples(mapDir);
            }

            // Complete progressbar
            UpdateProgressBar(worker, 100);

            return(new MapCleanerResult(objectsResnapped, samplesRemoved));
        }