/// <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)); }
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!"); }