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

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

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

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

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

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

            // Save the file to the export folder
            editor.SaveFile(Path.Combine(exportFolder, beatmap.GetFileName()));
        }
        public static List <double> TimesFromStack(string path, double x, double y)
        {
            List <double> times = new List <double>();

            EditorReaderStuff.TryGetNewestVersion(path, out var editor);

            bool xIgnore = x == -1;
            bool yIgnore = y == -1;

            foreach (HitObject ho in editor.Beatmap.HitObjects)
            {
                if ((Math.Abs(ho.Pos.X - x) < 3 || xIgnore) && (Math.Abs(ho.Pos.Y - y) < 3 || yIgnore))
                {
                    times.Add(ho.Time);
                }
            }
            return(times);
        }
        public static void ExportHitsounds(List<HitsoundEvent> hitsounds, string baseBeatmap, string exportFolder) {
            EditorReaderStuff.TryGetNewestVersion(baseBeatmap, out var editor);
            Beatmap beatmap = editor.Beatmap;

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

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

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

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

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

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

            // Save the file to the export folder
            editor.SaveFile(Path.Combine(exportFolder, beatmap.GetFileName()));
        }
Beispiel #4
0
        private void PeriodicBackupTimerOnTick(object sender, EventArgs e)
        {
            try {
                // Get the newest beatmap, save a temp version, get the hash and compare it to the previous hash, backup temp file
                var path = IOHelper.GetCurrentBeatmap();

                if (string.IsNullOrEmpty(path))
                {
                    return;
                }

                EditorReaderStuff.TryGetNewestVersion(path, out var editor);

                // Save temp version
                var tempPath = Path.Combine(MainWindow.AppDataPath, "temp.osu");

                if (!File.Exists(tempPath))
                {
                    File.Create(tempPath).Dispose();
                }

                File.WriteAllLines(tempPath, editor.Beatmap.GetLines());

                // Get MD5 from temp file
                var currentMapHash = EditorReaderStuff.GetMD5FromPath(tempPath);

                // Comparing with previously made periodic backup
                if (currentMapHash == previousPeriodicBackupHash)
                {
                    return;
                }

                // Saving backup of the map
                IOHelper.SaveMapBackup(tempPath, true, Path.GetFileName(path));

                previousPeriodicBackupHash = currentMapHash;
            } catch (Exception ex) {
                Console.WriteLine(ex.Message);
            }
        }
        /// <summary>
        /// Extract every used sample in a beatmap and return them as hitsound layers.
        /// </summary>
        /// <param name="path">The path to the beatmap.</param>
        /// <param name="volumes">Taking the volumes from the map and making different layers for different volumes.</param>
        /// <returns>The hitsound layers</returns>
        public static List <HitsoundLayer> ImportHitsounds(string path, bool volumes = false)
        {
            EditorReaderStuff.TryGetNewestVersion(path, out var editor);
            Beatmap  beatmap  = editor.Beatmap;
            Timeline timeline = beatmap.GetTimeline();

            GameMode mode   = (GameMode)beatmap.General["Mode"].Value;
            string   mapDir = editor.GetBeatmapFolder();
            Dictionary <string, string> firstSamples = AnalyzeSamples(mapDir);

            List <HitsoundLayer> hitsoundLayers = new List <HitsoundLayer>();

            foreach (TimelineObject tlo in timeline.TimelineObjects)
            {
                if (!tlo.HasHitsound)
                {
                    continue;
                }

                double volume = volumes ? tlo.FenoSampleVolume / 100 : 1;

                List <string> samples = tlo.GetPlayingFilenames(mode);

                foreach (string filename in samples)
                {
                    bool      isFilename = tlo.UsesFilename;
                    SampleSet sampleSet  = isFilename ? tlo.FenoSampleSet : GetSamplesetFromFilename(filename);
                    Hitsound  hitsound   = isFilename ? tlo.GetHitsound() : GetHitsoundFromFilename(filename);

                    string samplePath      = Path.Combine(mapDir, filename);
                    string fullPathExtLess = Path.Combine(
                        Path.GetDirectoryName(samplePath) ?? throw new InvalidOperationException(),
                        Path.GetFileNameWithoutExtension(samplePath));

                    // Get the first occurence of this sound to not get duplicated
                    if (firstSamples.Keys.Contains(fullPathExtLess))
                    {
                        samplePath = firstSamples[fullPathExtLess];
                    }
                    else
                    {
                        // Sample doesn't exist
                        if (!isFilename)
                        {
                            samplePath = Path.Combine(
                                Path.GetDirectoryName(samplePath) ?? throw new InvalidOperationException(),
                                $"{sampleSet.ToString().ToLower()}-hit{hitsound.ToString().ToLower()}-1.wav");
                        }
                    }

                    string extLessFilename = Path.GetFileNameWithoutExtension(samplePath);
                    var    importArgs      = new LayerImportArgs(ImportType.Hitsounds)
                    {
                        Path = path, SamplePath = samplePath, Volume = volume
                    };

                    // Find the hitsoundlayer with this path
                    HitsoundLayer layer = hitsoundLayers.Find(o => o.ImportArgs == importArgs);

                    if (layer != null)
                    {
                        // Find hitsound layer with this path and add this time
                        layer.Times.Add(tlo.Time);
                    }
                    else
                    {
                        // Add new hitsound layer with this path
                        HitsoundLayer newLayer = new HitsoundLayer(extLessFilename,
                                                                   sampleSet,
                                                                   hitsound,
                                                                   new SampleGeneratingArgs(samplePath)
                        {
                            Volume = volume
                        },
                                                                   importArgs);
                        newLayer.Times.Add(tlo.Time);

                        hitsoundLayers.Add(newLayer);
                    }
                }
            }

            // Sort layers by name
            hitsoundLayers = hitsoundLayers.OrderBy(o => o.Name).ToList();

            return(hitsoundLayers);
        }
Beispiel #6
0
        private string Sliderate(SlideratorVm arg, BackgroundWorker worker)
        {
            // Make a position function for Sliderator
            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.GraphMode == 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 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)
            {
                editorRead       = EditorReaderStuff.TryGetNewestVersion(arg.Path, out editor);
                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.ExportMode == 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.ExportMode == 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!");
        }