Exemple #1
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));
        }
Exemple #2
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!");
        }