private void ImportFromBeatmap(string importPath) { try { var editor = new BeatmapEditor(importPath); var beatmap = editor.Beatmap; Artist = beatmap.Metadata["ArtistUnicode"].StringValue; RomanisedArtist = beatmap.Metadata["Artist"].StringValue; Title = beatmap.Metadata["TitleUnicode"].StringValue; RomanisedTitle = beatmap.Metadata["Title"].StringValue; BeatmapCreator = beatmap.Metadata["Creator"].StringValue; Source = beatmap.Metadata["Source"].StringValue; Tags = beatmap.Metadata["Tags"].StringValue; PreviewTime = beatmap.General["PreviewTime"].Value; ComboColours = new ObservableCollection <ComboColour>(beatmap.ComboColours); SpecialColours.Clear(); foreach (var specialColour in beatmap.SpecialColours) { SpecialColours.Add(new SpecialColour(specialColour.Value.Color, specialColour.Key)); } } catch (Exception ex) { MessageBox.Show($"{ex.Message}{Environment.NewLine}{ex.StackTrace}", "Error"); } }
public OsuPattern FromFile(string filePath, string name) { // Read some stuff from the pattern var patternBeatmap = new BeatmapEditor(filePath).Beatmap; return(FromBeatmap(patternBeatmap, name)); }
public SpectrogramForm(BeatmapEditor editor) { InitializeComponent(); Editor = editor; Size = new Size(Screen.PrimaryScreen.Bounds.Width, Size.Height); Editor.BeatmapModified += UpdateSpectrogram; Editor.BeatmapSwitched += UpdateSpectrogram; UpdateSpectrogram(this, EventArgs.Empty); Instance = this; }
public void UnchangingEmptyMapCodeTest() { var path = "Resources\\EmptyTestMap.osu"; var lines = File.ReadAllLines(path).ToList(); var editor = new BeatmapEditor(path); var lines2 = editor.Beatmap.GetLines(); for (int i = 0; i < lines.Count; i++) { Assert.AreEqual(lines[i], lines2[i]); } }
/// <summary> /// Returns an editor for the beatmap of the specified path. If said beatmap is currently open in the editor it will update the Beatmap object with the latest values. /// </summary> /// <param name="path">Path to the beatmap</param> /// <param name="newestEditor">The editor with the newest version</param> /// <param name="selected">List of selected hit objects</param> /// <param name="fullReader">Reader object that has already fetched all</param> /// <returns>Boolean indicating whether it actually got the newest version</returns> public static bool TryGetNewestVersion(string path, out BeatmapEditor newestEditor, out List <HitObject> selected, EditorReader fullReader = null) { BeatmapEditor editor = new BeatmapEditor(path); newestEditor = editor; selected = new List <HitObject>(); // Check if Editor Reader is enabled if (!SettingsManager.Settings.UseEditorReader) { return(false); } // Get a reader object that has everything fetched var reader = fullReader; if (reader == null) { if (!TryGetFullEditorReader(out reader)) { return(false); } } // Get the path from the beatmap in memory // This can only crash if the provided fullReader didn't fetch all values try { string songs = SettingsManager.GetSongsPath(); string folder = reader.ContainingFolder; string filename = reader.Filename; string memoryPath = Path.Combine(songs, folder, filename); // Check whether the beatmap in the editor is the same as the beatmap you want if (memoryPath != path) { return(true); } // Update the beatmap with memory values selected = UpdateBeatmap(editor.Beatmap, reader); } catch (Exception ex) { MessageBox.Show($"Exception ({ex.Message}) while editor reading."); return(false); } return(true); }
public static BeatmapEditor TryGetNewestVersion(EditorReader reader, out List <HitObject> selected) { // Get the path from the beatmap in memory string songs = SettingsManager.GetSongsPath(); string folder = reader.ContainingFolder; string filename = reader.Filename; string memoryPath = Path.Combine(songs, folder, filename); var editor = new BeatmapEditor(memoryPath); // Update the beatmap with memory values selected = SettingsManager.Settings.UseEditorReader ? UpdateBeatmap(editor.Beatmap, reader) : new List <HitObject>(); return(editor); }
/// <summary> /// Copies a backup to replace a beatmap at the destination path. /// </summary> /// <param name="backupPath">Path to the backup map.</param> /// <param name="destination">Path to the destination map.</param> /// <param name="allowDifferentFilename">If false, this method throws an exception when the backup and the destination have mismatching beatmap metadata.</param> public static void LoadMapBackup(string backupPath, string destination, bool allowDifferentFilename = false) { var backupEditor = new BeatmapEditor(backupPath); var destinationEditor = new BeatmapEditor(destination); var backupFilename = backupEditor.Beatmap.GetFileName(); var destinationFilename = destinationEditor.Beatmap.GetFileName(); if (!allowDifferentFilename && !string.Equals(backupFilename, destinationFilename)) { throw new BeatmapIncompatibleException($"The backup and the destination beatmap have mismatching metadata.\n{backupFilename}\n{destinationFilename}"); } File.Copy(backupPath, destination, true); }
public void ImportComboColoursFromBeatmap(string importPath) { try { var editor = new BeatmapEditor(importPath); var beatmap = editor.Beatmap; ComboColours.Clear(); for (int i = 0; i < beatmap.ComboColours.Count; i++) { ComboColours.Add(new SpecialColour(beatmap.ComboColours[i].Color, $"Combo{i + 1}")); } } catch (Exception ex) { ex.Show(); } }
public void ImportComboColoursFromBeatmap(string importPath) { try { var editor = new BeatmapEditor(importPath); var beatmap = editor.Beatmap; ComboColours.Clear(); for (int i = 0; i < beatmap.ComboColours.Count; i++) { ComboColours.Add(new SpecialColour(beatmap.ComboColours[i].Color, $"Combo{i + 1}")); } } catch (Exception ex) { MessageBox.Show($"{ex.Message}{Environment.NewLine}{ex.StackTrace}", "Error"); } }
/// <summary> /// Returns an editor for the beatmap which is currently open in the editor. Returns null if there is no beatmap open in the editor. /// </summary> /// <param name="fullReader">Reader object that has already fetched all</param> /// <param name="selected">List of selected hit objects</param> /// <returns>An editor for the beatmap</returns> /// <exception cref="ArgumentNullException"></exception> public static BeatmapEditor GetBeatmapEditor(EditorReader fullReader, out List <HitObject> selected) { if (fullReader == null) { throw new ArgumentNullException(nameof(fullReader)); } // Get the path from the beatmap in memory string memoryPath = GetCurrentBeatmap(fullReader); // Update the beatmap with memory values var editor = new BeatmapEditor(memoryPath); selected = UpdateBeatmap(editor.Beatmap, fullReader); return(editor); }
/// <summary> /// Tries to get the newest version if editorRead is true otherwise just makes a normal <see cref="BeatmapEditor"/> /// </summary> /// <param name="path"></param> /// <param name="fullReader"></param> /// <param name="readEditor"></param> /// <param name="selected"></param> /// <param name="editorRead">Indicates true if the editor memory was actually read</param> /// <returns></returns> public static BeatmapEditor GetBeatmapEditor(string path, EditorReader fullReader, bool readEditor, out List <HitObject> selected, out bool editorRead) { BeatmapEditor editor; if (readEditor) { editorRead = TryGetNewestVersion(path, out editor, out selected, fullReader); } else { editor = new BeatmapEditor(path); selected = new List <HitObject>(); editorRead = false; } return(editor); }
/// <summary> /// Returns an editor for the beatmap of the specified path. If said beatmap is currently open in the editor it will update the Beatmap object with the latest values. /// </summary> /// <param name="path">Path to the beatmap</param> /// <param name="selected">List of selected hit objects</param> /// <param name="fullReader">Reader object that has already fetched all</param> /// <returns>The editor with the newest version</returns> /// <exception cref="ArgumentNullException"></exception> public static BeatmapEditor GetNewestVersion(string path, EditorReader fullReader, out List <HitObject> selected) { if (fullReader == null) { throw new ArgumentNullException(nameof(fullReader)); } BeatmapEditor editor = new BeatmapEditor(path); selected = new List <HitObject>(); // Get the path from the beatmap in memory string memoryPath = GetCurrentBeatmap(fullReader); // Check whether the beatmap in the editor is the same as the beatmap you want if (memoryPath == path) { // Update the beatmap with memory values selected = UpdateBeatmap(editor.Beatmap, fullReader); } return(editor); }
public static List <HitObject> GetSelectedObjects(BeatmapEditor editor, EditorReader reader) { try { string songs = SettingsManager.GetSongsPath(); string folder = reader.ContainingFolder; string filename = reader.Filename; string memoryPath = Path.Combine(songs, folder, filename); // Check whether the beatmap in the editor is the same as the beatmap you want if (memoryPath != editor.Path) { return(new List <HitObject>()); } reader.FetchSelected(); var convertedSelected = reader.selectedObjects.Select(o => (HitObject)o).ToList(); var selectedHitObjects = new List <HitObject>(convertedSelected.Count()); var comparer = new HitObjectComparer(); // Get all the hit objects that are selected according to the editor reader foreach (var ho in editor.Beatmap.HitObjects) { if (convertedSelected.Contains(ho, comparer)) { selectedHitObjects.Add(ho); } } return(selectedHitObjects); } catch (Exception ex) { MessageBox.Show($"Exception ({ex.Message}) while editor reading."); return(new List <HitObject>()); } }
/// <summary> /// Returns an editor for the beatmap which is currently open in the editor. Returns null if there is no beatmap open in the editor. /// </summary> /// <param name="selected">List of selected hit objects</param> /// <param name="fullReader">Reader object that has already fetched all</param> /// <returns>An editor for the beatmap</returns> public static BeatmapEditor GetBeatmapEditor(out List <HitObject> selected, EditorReader fullReader = null) { selected = new List <HitObject>(); BeatmapEditor editor = null; // Get a reader object that has everything fetched var reader = fullReader; if (reader == null) { if (!TryGetFullEditorReader(out reader)) { return(null); } } // Get the path from the beatmap in memory // This can only crash if the provided fullReader didn't fetch all values try { string songs = SettingsManager.GetSongsPath(); string folder = reader.ContainingFolder; string filename = reader.Filename; string memoryPath = Path.Combine(songs, folder, filename); // Update the beatmap with memory values editor = new BeatmapEditor(memoryPath); selected = UpdateBeatmap(editor.Beatmap, reader); } catch (Exception ex) { MessageBox.Show($"Exception ({ex.Message}) while editor reading."); } return(editor); }
public OsuPattern FromFileFilter(string filePath, out Beatmap patternBeatmap, string name, string filter = null, double filterStartTime = -1, double filterEndTime = -1) { // Read some stuff from the pattern patternBeatmap = new BeatmapEditor(filePath).Beatmap; RemoveStoryboard(patternBeatmap); // Optionally filter stuff var hitObjects = !string.IsNullOrEmpty(filter) ? patternBeatmap.QueryTimeCode(filter).ToList() : patternBeatmap.HitObjects; if (filterStartTime != -1) { hitObjects.RemoveAll(o => o.EndTime < filterStartTime); } if (filterEndTime != -1) { hitObjects.RemoveAll(o => o.Time > filterEndTime); } RemoveEverythingThatIsNotTheseHitObjects(patternBeatmap, hitObjects); return(FromBeatmap(patternBeatmap, name)); }
public void ImportColourHaxFromBeatmap(string importPath) { try { var editor = new BeatmapEditor(importPath); var beatmap = editor.Beatmap; // Add default colours if there are no colours if (beatmap.ComboColours.Count == 0) { beatmap.ComboColours.AddRange(ComboColour.GetDefaultComboColours()); } ComboColours.Clear(); for (int i = 0; i < beatmap.ComboColours.Count; i++) { ComboColours.Add(new SpecialColour(beatmap.ComboColours[i].Color, $"Combo{i + 1}")); } // Remove all colour points since those are getting replaced ColourPoints.Clear(); // Get all the hit objects which can colorhax. AKA new combos and not spinners var colorHaxObjects = beatmap.HitObjects.Where(o => o.ActualNewCombo && !o.IsSpinner).ToArray(); // Get the array with all the lengths of sequences that are going to be checked var sequenceLengthChecks = Enumerable.Range(1, ComboColours.Count * 2 + 2).ToArray(); int sequenceStartIndex = 0; int[] lastNormalSequence = null; bool lastBurst = false; while (sequenceStartIndex < colorHaxObjects.Length) { var firstComboHitObject = colorHaxObjects[sequenceStartIndex]; var bestSequence = GetBestSequenceAtIndex( sequenceStartIndex, 3, colorHaxObjects, beatmap, sequenceLengthChecks, lastBurst, lastNormalSequence )?.Item1; if (bestSequence == null) { lastBurst = false; sequenceStartIndex += 1; continue; } var bestContribution = GetSequenceContribution(colorHaxObjects, sequenceStartIndex, bestSequence); // Get the colours for every colour index. Using modulo to make sure the index is always in range. var colourSequence = bestSequence.Select(o => ComboColours[MathHelper.Mod(o, ComboColours.Count)]); // Add a new colour point var mode = bestContribution == 1 && GetComboLength(beatmap.HitObjects, firstComboHitObject) <= MaxBurstLength ? ColourPointMode.Burst : ColourPointMode.Normal; // To optimize on colour points, we dont add a new colour point if the previous point was a burst and // the sequence before the burst is equivalent to this sequence if (!(lastBurst && lastNormalSequence != null && IsSubSequence(bestSequence, lastNormalSequence) && (bestSequence.Length == lastNormalSequence.Length || bestContribution <= bestSequence.Length))) { ColourPoints.Add(GenerateNewColourPoint(firstComboHitObject.Time, colourSequence, mode)); } lastBurst = mode == ColourPointMode.Burst; sequenceStartIndex += bestContribution; lastNormalSequence = mode == ColourPointMode.Burst ? lastNormalSequence : bestSequence; } } catch (Exception ex) { MessageBox.Show($"{ex.Message}{Environment.NewLine}{ex.StackTrace}", "Error"); } }
/// <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)); }
public static int RemoveUnusedSamples(string mapDir) { // Collect all the used samples HashSet <string> allFilenames = new HashSet <string>(); bool anySpinners = false; List <string> beatmaps = Directory.GetFiles(mapDir, "*.osu", SearchOption.TopDirectoryOnly).ToList(); foreach (string path in beatmaps) { BeatmapEditor editor = new BeatmapEditor(path); Beatmap beatmap = editor.Beatmap; GameMode mode = (GameMode)beatmap.General["Mode"].IntValue; double sliderTickRate = beatmap.Difficulty["SliderTickRate"].DoubleValue; if (!anySpinners) { anySpinners = mode == 0 && beatmap.HitObjects.Any(o => o.IsSpinner); } allFilenames.Add(beatmap.General["AudioFilename"].Value.Trim()); foreach (HitObject ho in beatmap.HitObjects) { allFilenames.UnionWith(ho.GetPlayingBodyFilenames(sliderTickRate, false)); } foreach (TimelineObject tlo in beatmap.GetTimeline().TimelineObjects) { allFilenames.UnionWith(tlo.GetPlayingFilenames(mode, false)); } foreach (StoryboardSoundSample sbss in beatmap.StoryboardSoundSamples) { allFilenames.Add(sbss.FilePath); } } List <string> storyboards = Directory.GetFiles(mapDir, "*.osb", SearchOption.TopDirectoryOnly).ToList(); foreach (string path in storyboards) { StoryboardEditor editor = new StoryboardEditor(path); StoryBoard storyboard = editor.StoryBoard; foreach (StoryboardSoundSample sbss in storyboard.StoryboardSoundSamples) { allFilenames.Add(sbss.FilePath); } } // Only if there are spinners in standard you may have spinnerspin and spinnerbonus if (anySpinners) { allFilenames.UnionWith(new[] { "spinnerspin", "spinnerbonus" }); } // We don't do extensions in osu! HashSet <string> usedFilenames = new HashSet <string>(allFilenames.Select(Path.GetFileNameWithoutExtension)); // Get the sound files var extList = new[] { ".wav", ".ogg", ".mp3" }; DirectoryInfo di = new DirectoryInfo(mapDir); List <FileInfo> sampleFiles = di.GetFiles("*.*", SearchOption.TopDirectoryOnly) .Where(n => extList.Contains(n.Extension, StringComparer.OrdinalIgnoreCase)).ToList(); int removed = 0; foreach (FileInfo fi in sampleFiles) { string extless = Path.GetFileNameWithoutExtension(fi.Name); if (!(usedFilenames.Contains(extless) || BeatmapSkinnableSamples.Any(o => Regex.IsMatch(extless, o)))) { fi.Delete(); //Console.WriteLine($"Deleting sample {fi.Name}"); removed++; } } return(removed); }
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!"); }
/// <summary> /// Returns an editor for the beatmap of the specified path. If said beatmap is currently open in the editor it will update the Beatmap object with the latest values. /// </summary> /// <param name="path">Path to the beatmap</param> /// <param name="editor">The editor with the newest version</param> /// <param name="fullReader">Reader object that has already fetched all</param> /// <returns>Boolean indicating whether it actually got the newest version</returns> public static bool TryGetNewestVersion(string path, out BeatmapEditor editor, EditorReader fullReader = null) { return(TryGetNewestVersion(path, out editor, out _, fullReader)); }
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!"); }