private List <Marker> GetMarkers(Beatmap beatmap, Timing timing)
        {
            List <Marker>      markers  = new List <Marker>();
            List <TimingPoint> redlines = timing.GetAllRedlines();

            foreach (HitObject ho in beatmap.HitObjects)
            {
                markers.Add(new Marker(ho));
            }
            foreach (double bookmark in beatmap.GetBookmarks())
            {
                markers.Add(new Marker(bookmark));
            }
            foreach (TimingPoint greenline in timing.TimingPoints)
            {
                markers.Add(new Marker(greenline));
            }

            // Sort the markers
            markers = markers.OrderBy(o => o.Time).ToList();

            // Calculate the beats between this marker and the last marker
            // If there is a redline in between then calculate beats from last marker to the redline and beats from redline to this marker
            // Time the same is 0
            double lastTime = redlines.First().Offset;

            foreach (Marker marker in markers)
            {
                // Get redlines between this and last marker
                List <TimingPoint> redlinesBetween = redlines.Where(o => o.Offset <marker.Time && o.Offset> lastTime).ToList();
                TimingPoint        redline         = timing.GetRedlineAtTime(lastTime);

                double beatsFromLastMarker = 0;
                foreach (TimingPoint redlineBetween in redlinesBetween)
                {
                    beatsFromLastMarker += (redlineBetween.Offset - lastTime) / redline.MpB;
                    redline              = redlineBetween;
                    lastTime             = redlineBetween.Offset;
                }
                beatsFromLastMarker += (marker.Time - lastTime) / redline.MpB;

                // Set the variable
                marker.BeatsFromLastMarker = beatsFromLastMarker;

                lastTime = marker.Time;
            }

            return(markers);
        }
        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 #3
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;
            Timeline timeline = beatmap.GetTimeline();

            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 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.Snap1, args.Snap2);
                    if (resnapped)
                    {
                        objectsResnapped += 1;
                    }
                    ho.ResnapEnd(timing, args.Snap1, args.Snap2);
                    ho.ResnapPosition(mode, circleSize);
                }
                UpdateProgressBar(worker, 18);

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

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

            if (args.ResnapBookmarks)
            {
                // Resnap the bookmarks
                List <double> bookmarks    = beatmap.GetBookmarks();
                List <double> newBookmarks = bookmarks.Select(o => timing.Resnap(o, args.Snap1, args.Snap2)).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
            List <TimingPoint> redlines = timing.GetAllRedlines();

            foreach (TimingPoint tp in redlines)
            {
                timingPointsChanges.Add(new TimingPointsChange(tp, mpb: true, meter: true, unInherited: true, omitFirstBarLine: true));
            }
            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.TimingPoints.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));
        }
Example #4
0
        private string Run_Program2(List <string> arguments, BackgroundWorker worker, DoWorkEventArgs e)
        {
            // Retrieve all arguments
            string path                      = arguments[0];
            bool   volumeSliders             = arguments[1] == "True";
            bool   samplesetSliders          = arguments[2] == "True";
            bool   volumeSpinners            = arguments[3] == "True";
            bool   resnapObjects             = arguments[4] == "True";
            bool   resnapBookmarks           = arguments[5] == "True";
            int    snap1                     = int.Parse(arguments[6].Split('/')[1]);
            int    snap2                     = int.Parse(arguments[7].Split('/')[1]);
            bool   removeSliderendMuting     = arguments[8] == "True";
            bool   removeUnclickabeHitsounds = arguments[9] == "True";

            Editor   editor   = new Editor(path);
            Timing   timing   = editor.Beatmap.BeatmapTiming;
            Timeline timeline = editor.Beatmap.GetTimeline();

            int mode             = editor.Beatmap.General["Mode"].Value;
            int num_timingPoints = editor.Beatmap.BeatmapTiming.TimingPoints.Count;
            int objectsResnapped = 0;

            // Count total stages
            int maxStages = 11;

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

            for (int i = 0; i < timing.TimingPoints.Count; i++)
            {
                TimingPoint tp = timing.TimingPoints[i];
                if (tp.Kiai != lastKiai)
                {
                    kiaiToggles.Add(tp.Copy());
                    lastKiai = tp.Kiai;
                }
                if (tp.Inherited)
                {
                    lastSV = -100;
                }
                else
                {
                    if (tp.MpB != lastSV)
                    {
                        svChanges.Add(tp.Copy());
                        lastSV = tp.MpB;
                    }
                }
                UpdateProgressbar(worker, (double)i / timing.TimingPoints.Count, 0, maxStages);
            }

            // Resnap shit
            if (resnapObjects)
            {
                // Resnap all objects
                for (int i = 0; i < editor.Beatmap.HitObjects.Count; i++)
                {
                    HitObject ho        = editor.Beatmap.HitObjects[i];
                    bool      resnapped = ho.ResnapSelf(timing, snap1, snap2);
                    if (resnapped)
                    {
                        objectsResnapped += 1;
                    }
                    ho.ResnapEnd(timing, snap1, snap2);
                    UpdateProgressbar(worker, (double)i / editor.Beatmap.HitObjects.Count, 1, maxStages);
                }

                // Resnap Kiai toggles and SV changes
                for (int i = 0; i < kiaiToggles.Count; i++)
                {
                    TimingPoint tp = kiaiToggles[i];
                    tp.ResnapSelf(timing, snap1, snap2);
                    UpdateProgressbar(worker, (double)i / kiaiToggles.Count, 2, maxStages);
                }
                for (int i = 0; i < svChanges.Count; i++)
                {
                    TimingPoint tp = svChanges[i];
                    tp.ResnapSelf(timing, snap1, snap2);
                    UpdateProgressbar(worker, (double)i / svChanges.Count, 3, maxStages);
                }
            }

            if (resnapBookmarks)
            {
                // Resnap the bookmarks
                List <double> newBookmarks = new List <double>();
                List <double> bookmarks    = editor.Beatmap.GetBookmarks();
                for (int i = 0; i < bookmarks.Count; i++)
                {
                    double bookmark = bookmarks[i];
                    newBookmarks.Add(Math.Floor(timing.Resnap(bookmark, snap1, snap2)));
                    UpdateProgressbar(worker, (double)i / bookmarks.Count, 4, maxStages);
                }
                editor.Beatmap.SetBookmarks(newBookmarks);
            }

            // Maybe mute unclickable timelineobjects
            if (removeUnclickabeHitsounds)
            {
                foreach (TimelineObject tlo in timeline.TimeLineObjects)
                {
                    if (!(tlo.IsCircle || tlo.IsSliderHead || tlo.IsHoldnoteHead)) // Not clickable
                    {
                        tlo.FenoSampleVolume = 5;                                  // 5% volume mute
                    }
                }
            }

            // Make new timingpoints
            List <Change> changes = new List <Change>();
            // Add redlines
            List <TimingPoint> redlines = timing.GetAllRedlines();

            for (int i = 0; i < redlines.Count; i++)
            {
                TimingPoint tp = redlines[i];
                changes.Add(new Change(tp, mpb: true, meter: true, inherited: true));
                UpdateProgressbar(worker, (double)i / redlines.Count, 5, maxStages);
            }
            // Add SV changes for taiko and mania
            if (mode == 1 || mode == 3)
            {
                for (int i = 0; i < svChanges.Count; i++)
                {
                    TimingPoint tp = svChanges[i];
                    changes.Add(new Change(tp, mpb: true));
                    UpdateProgressbar(worker, (double)i / svChanges.Count, 6, maxStages);
                }
            }
            // Add Kiai toggles
            for (int i = 0; i < kiaiToggles.Count; i++)
            {
                TimingPoint tp = kiaiToggles[i];
                changes.Add(new Change(tp, kiai: true));
                UpdateProgressbar(worker, (double)i / kiaiToggles.Count, 7, maxStages);
            }
            // Add Hitobject stuff
            for (int i = 0; i < editor.Beatmap.HitObjects.Count; i++)
            {
                HitObject ho = editor.Beatmap.HitObjects[i];
                if (ho.IsSlider) // SV changes
                {
                    TimingPoint tp = ho.TP.Copy();
                    tp.Offset = ho.Time;
                    tp.MpB    = ho.SV;
                    changes.Add(new Change(tp, mpb: true));
                }
                // Body hitsounds
                bool vol = (ho.IsSlider && volumeSliders) || (ho.IsSpinner && volumeSpinners);
                bool sam = (ho.IsSlider && samplesetSliders && ho.SampleSet == 0);
                bool ind = (ho.IsSlider && samplesetSliders);
                bool samplesetActuallyChanged = false;
                foreach (TimingPoint tp in ho.BodyHitsounds)
                {
                    if (tp.Volume == 5 && removeSliderendMuting)
                    {
                        vol = false;
                    }                                                              // Removing sliderbody silencing
                    changes.Add(new Change(tp, volume: vol, index: ind, sampleset: sam));
                    if (tp.SampleSet != ho.HitsoundTP.SampleSet)
                    {
                        samplesetActuallyChanged = 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.HitsoundTP.SampleSet;
                    ho.SliderExtras = true;
                }
                if (ho.IsSlider && samplesetActuallyChanged) // Make it start out with the right sampleset
                {
                    TimingPoint tp = ho.HitsoundTP.Copy();
                    tp.Offset = ho.Time;
                    changes.Add(new Change(tp, sampleset: true));
                }
                UpdateProgressbar(worker, (double)i / editor.Beatmap.HitObjects.Count, 8, maxStages);
            }
            // Add timeline hitsounds
            for (int i = 0; i < timeline.TimeLineObjects.Count; i++)
            {
                TimelineObject tlo = timeline.TimeLineObjects[i];
                // Change the samplesets in the hitobjects
                if (tlo.Origin.IsCircle)
                {
                    tlo.Origin.SampleSet   = tlo.FenoSampleSet;
                    tlo.Origin.AdditionSet = tlo.FenoAdditionSet;
                    if (mode == 3)
                    {
                        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;
                    tlo.Origin.SliderExtras = true;
                    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 (mode == 0 && tlo.HasHitsound) // Add greenlines for custom indexes and volumes
                {
                    TimingPoint tp = tlo.Origin.TP.Copy();
                    tp.Offset      = tlo.Time;
                    tp.SampleIndex = tlo.FenoCustomIndex;
                    tp.Volume      = tlo.FenoSampleVolume;
                    bool ind = !(tlo.Filename != "" && (tlo.IsCircle || tlo.IsHoldnoteHead || tlo.IsSpinnerEnd));   // Index doesnt have to change if custom is overridden by Filename
                    bool vol = !(tp.Volume == 5 && removeSliderendMuting && (tlo.IsSliderEnd || tlo.IsSpinnerEnd)); // Remove volume change if sliderend muting or spinnerend muting
                    changes.Add(new Change(tp, volume: vol, index: ind));
                }
                UpdateProgressbar(worker, (double)i / timeline.TimeLineObjects.Count, 9, maxStages);
            }


            // Add the new timingpoints
            changes = changes.OrderBy(o => o.TP.Offset).ToList();
            List <TimingPoint> newTimingPoints = new List <TimingPoint>();

            for (int i = 0; i < changes.Count; i++)
            {
                Change c = changes[i];
                c.AddChange(newTimingPoints, timing);
                UpdateProgressbar(worker, (double)i / changes.Count, 10, maxStages);
            }

            // Replace the old timingpoints
            timing.TimingPoints = newTimingPoints;

            // Save the file
            editor.SaveFile();

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

            // Make an accurate message (Softwareporn)
            int    removed = num_timingPoints - newTimingPoints.Count;
            string message = "";

            if (removed < 0)
            {
                message += "Succesfully added " + Math.Abs(removed);
            }
            else
            {
                message += "Succesfully removed " + removed;
            }
            if (Math.Abs(removed) == 1)
            {
                message += " greenline and resnapped " + objectsResnapped;
            }
            else
            {
                message += " greenlines and resnapped " + objectsResnapped;
            }
            if (Math.Abs(objectsResnapped) == 1)
            {
                message += " object!";
            }
            else
            {
                message += " objects!";
            }
            return(message);
        }