Exemple #1
0
        public static void changeBpmOfTimingPoint(Beatmap beatmap, HtmlDisplayer htmlDisplayer,
                                                  double offset, double newValue, bool shiftRestOfBeatmap,
                                                  bool saveBackups, string customPath)
        {
            if (saveBackups)
            {
                beatmap.save(customPath + "//" + beatmap.FileName);
            }

            decimal newValueDecimal = Convert.ToDecimal(newValue);

            // We need to extract the objects between the selected offset and the next timing point.
            List <TimingPoint> originalPoints = beatmap.TimingPoints;

            // Get exact and next timing points. Exact timing point cannot be null. If it is null, throw
            // a message and bail out.
            TimingPoint sourcePoint = SearchUtils.GetExactTimingPoint(originalPoints, offset);
            TimingPoint nextPoint   = SearchUtils.GetClosestNextTimingPoint(originalPoints, sourcePoint);

            if (sourcePoint == null)
            {
                if (!htmlDisplayer.containsSections())
                {
                    htmlDisplayer.addSection("Starting uninherited timing point not found in one or more difficulties.");
                }
                else
                {
                    htmlDisplayer.addLineBreak();
                }
                htmlDisplayer.addSubsection("Beatmap difficulty: " + beatmap.DifficultyName);
                htmlDisplayer.addWarning(StringUtils.GetOffsetWithLink(offset) + " does not exist.");
                return;
            }

            // Get the objects we require.
            SearchUtils.GetObjectsInBetween(beatmap, offset, nextPoint != null ? nextPoint.Offset : double.MaxValue,
                                            out IList <Bookmark> bookmarks, out IList <TimingPoint> timingPoints, out IList <HitObject> hitObjects);

            // Since the snaps are already calculated, it should be easy to calculate the next offsets
            // after changing the BPM.

            // Remove the next timing point offset from all lists.
            if (nextPoint != null)
            {
                ((SubList <Bookmark>)bookmarks).TrimEnd(x => x.Offset == nextPoint.Offset);
                ((SubList <TimingPoint>)timingPoints).TrimEnd(x => x.Offset == nextPoint.Offset);
                ((SubList <HitObject>)hitObjects).TrimEnd(x => x.Offset == nextPoint.Offset);
            }

            // Next point's snap is important to determine the offset difference for rest of the objects,
            // if shifting is enabled.
            if (shiftRestOfBeatmap && nextPoint != null)
            {
                // This means there is a next point and it should be included in timingPoints list.
                // Check the snap differences between them and shift them all first.
                decimal snapDifference   = Convert.ToDecimal(nextPoint.GetSnap()) - Convert.ToDecimal(sourcePoint.GetSnap());
                int     offsetDifference = SnapUtils.calculateEndOffsetFromBpmValue(offset, snapDifference, newValueDecimal);

                // Shift all the objects starting from the last object offset.
                SearchUtils.GetObjectsInBetween(beatmap, offset, nextPoint.Offset,
                                                out IList <Bookmark> bookmarks2, out IList <TimingPoint> timingPoints2, out IList <HitObject> hitObjects2);
                SnapUtils.shiftAllElementsByOffset(offsetDifference, hitObjects2, timingPoints2, bookmarks2);
            }

            // After this, now start calculating the end offsets from the relative snaps of the elements.
            sourcePoint.PointValue = newValue;

            // Use the newValueDecimal to adjust everything.
            SnapUtils.shiftAllElementsByNewPointValue(beatmap, sourcePoint, offset, newValueDecimal, hitObjects, timingPoints, bookmarks);

            // And the process should be complete.
        }
Exemple #2
0
        public static bool AddSvChanges(Form form, Beatmap beatmap, double firstOffset, double lastOffset,
                                        double firstSv, double lastSv, double targetBpm,
                                        double gridSnap, int svOffset, int svIncreaseMode, int count,
                                        double svIncreaseMultiplier, bool putPointsByNotes)
        {
            List <TimingPoint> actualPoints = beatmap.TimingPoints;

            if (count > 0 && lastOffset <= firstOffset)
            {
                if (putPointsByNotes)
                {
                    // We need to determine the end offset here from notes
                    // and note count.
                    SnapUtils.getEndOffsetFromObjectsByCount(beatmap, firstOffset, count, out lastOffset);
                }
                else
                {
                    if (gridSnap == 0)
                    {
                        showErrorMessageInMainThread(form, "End offset was not defined and grid snap and count values are" + Environment.NewLine + "also not defined, hence the end offset could not be calculated. Aborting.");
                        return(false);
                    }
                    lastOffset = SnapUtils.calculateEndOffset(beatmap, firstOffset, gridSnap, count);
                }
            }
            else if (lastOffset <= firstOffset)
            {
                showErrorMessageInMainThread(form, "End offset could not be calculated, necessary values are missing.");
                return(false);
            }

            SearchUtils.GetObjectsInBetween(beatmap, firstOffset, lastOffset,
                                            out IList <TimingPoint> points, out IList <HitObject> objects);

            // If "putPointsByNotes" is selected and no objects are found, throw an error
            // and return false.
            if (putPointsByNotes && objects.Count == 0)
            {
                showErrorMessageInMainThread(form, "No hit objects found in the specified area. Aborting.");
                return(false);
            }

            // We need at least 1 timing point to take reference from
            // the start of the list. Account that and add a timing
            // point if the first index is not a timing point already.
            if (!SearchUtils.IsFirstPointTimingPoint(points))
            {
                points.Insert(0, SearchUtils.GetClosestTimingPoint(actualPoints, firstOffset));
            }

            // After this, if we still don't have a timing point, we cannot proceed.
            // A map has to contain at least one timing point before or on the declared offset.
            if (!SearchUtils.ContainsTimingPoint(points))
            {
                showErrorMessageInMainThread(form, "No timing points found to take reference from. Aborting.");
                return(false);
            }

            // The POW value. This is determined by
            // "svIncreaseMode" and "svIncreaseMultiplier"
            // where "svIncreaseMode" is 0 for linear, 1 for exponential
            // and 2 for logarithmic. If mode is 0, POW is always 1.
            // If mode is 1, the POW is the original value.
            // If mode is 2, the POW is divided as 1/POW.
            double pow;

            switch (svIncreaseMode)
            {
            case 0:
                pow = 1;
                break;

            case 1:
                pow = svIncreaseMultiplier;
                break;

            case 2:
                pow = 1 / svIncreaseMultiplier;
                break;

            default:
                throw new ArgumentException("The sv increase mode has to be 0, 1 or 2, found " + svIncreaseMode + ".");
            }

            // This is a really corner case since we already prevent it from
            // happening in the SV Changer form itself, but it still is checked.
            if (pow == 0)
            {
                showErrorMessageInMainThread(form, "The sv increase mode and sv increase multiplier has" + Environment.NewLine + "resulted in the power value being 0. Aborting.");
                return(false);
            }

            // If "put points by notes" is selected,
            // we need to calculate the target offset by
            // the first note.
            if (putPointsByNotes)
            {
                firstOffset = objects[0].Offset;
            }

            // Get the start BPM value.
            double startBpmValue = SearchUtils.GetBpmValueInOffset(actualPoints, firstOffset);

            // If "targetBpm" is entered, we need to apply a ratio to the last SV.
            // Apply the logic here and change the last SV value depending on it.
            if (targetBpm != 0)
            {
                double ratio = targetBpm / SearchUtils.GetBpmInOffset(actualPoints, firstOffset);
                lastSv *= ratio;
            }

            // Now that we have set already in place, let's calculate the SVs and
            // add or edit them.

            // The temp value to use on additional SVs.
            double targetSv;

            // The temp value for the SV for osu! representation.
            double targetSvValue;

            // The current percentage that will result
            // in the final SV of the point. This should be
            // always equal to percentage change if the change is linear.
            double targetPowerValue;

            // Define a target offset object and
            // calculate the target offset depending
            // on the passed parameters, a.k.a "putPointsByNoteSnaps"
            // or "gridSnap" and "count".
            double targetOffset = firstOffset;

            // Used in determining which hit object
            // we should use if "put points by notes"
            // is selected, as the target offset.
            int hitObjectIndex = 0;

            while (targetOffset <= lastOffset)
            {
                // Compute the actual target offset, where the offset is determined
                // but needs to be shifted depending on user input.
                double actualTargetOffset = targetOffset + svOffset;

                // This is the BPM value, not the BPM itself, a.k.a it is the osu! representation
                // of a BPM value. For instance, this is 1000 if BPM is 120 in the map, where
                // a beat is in a total second.
                double currentBpmValue = SearchUtils.GetBpmValueInOffset(points, targetOffset);

                // The ratio that we need to multiply while calculating the SV.
                double ratio = startBpmValue / currentBpmValue;

                // Get the closest and exact inherited points (if exists)
                // Closest point cannot be null (it will return the first timing point
                // in worst case), but exact point can be null.
                TimingPoint closestPoint = SearchUtils.GetClosestPoint(actualPoints, targetOffset, true);
                TimingPoint exactPoint   = SearchUtils.GetExactInheritedPoint(actualPoints, targetOffset);

                // The copy point that we need to hold the attributes of.
                TimingPoint copy;

                // Determines if the object is already existing in the list.
                bool exists = false;

                // If exact point is not null, we need to edit that.
                if (exactPoint != null)
                {
                    copy   = exactPoint;
                    exists = true;
                }
                else
                {
                    copy = new TimingPoint(closestPoint)
                    {
                        // Make sure the copy one is inherited.
                        IsInherited = true
                    };
                }

                // Get the current offset power value. This determines
                // how much the target SV will be as in "startSv + this value".
                targetPowerValue = MathUtils.calculateMultiplierFromPower(svIncreaseMultiplier,
                                                                          firstOffset, lastOffset, targetOffset);

                // Calculate the target SV. Now, the value should be divided by -100 / target SV
                // to achieve the osu! representation of this value.
                // Ratio is the BPM ratio between the start and current offset. Should be 1
                // if the BPMs are the same.
                targetSv      = MathUtils.calculateValueFromPercentage(firstSv, lastSv, targetPowerValue) * ratio;
                targetSvValue = -100d / targetSv;

                // At this point, we need to either add the point, or
                // replace the existing one. If the existing one toggles kiai,
                // and the actualTargetOffset is different from this point's offset,
                // we need to add a point and change the kiai one too instead.
                // Otherwise, we just move the point.
                if (exists)
                {
                    if (SearchUtils.TogglesKiai(actualPoints, copy))
                    {
                        // If there is an offset change (a.k.a actualTargetOffset not
                        // equal to targetOffset) we need to add a point and change
                        // this one as well.
                        if (actualTargetOffset != targetOffset)
                        {
                            TimingPoint copy2 = new TimingPoint(copy)
                            {
                                Offset     = actualTargetOffset,
                                IsKiaiOpen = !copy.IsKiaiOpen,
                                PointValue = targetSvValue
                            };
                            copy.PointValue = targetSvValue;

                            // Now, there is a trick here. If the actual inherited point
                            // exists with the edited offset, we need to edit that, not
                            // add a duplicate one and force an exception.
                            // Just set "exact" values of "copy" into this.
                            TimingPoint exact = SearchUtils.GetExactInheritedPoint(actualPoints, actualTargetOffset);
                            if (exact != null)
                            {
                                exact.setTo(copy);
                            }
                            else
                            {
                                // We haven't found an exact timing point so this is fine.
                                actualPoints.Insert(SearchUtils.GetAdditionIndex(actualPoints, copy2), copy2);
                            }
                        }
                        else
                        {
                            copy.PointValue = targetSvValue;
                            copy.Offset     = actualTargetOffset;
                            SearchUtils.MarkChangeMade(actualPoints);
                        }
                    }
                    else
                    {
                        // If there is an offset change (a.k.a actualTargetOffset not
                        // equal to targetOffset) we need to add a point and change
                        // this one as well.
                        if (actualTargetOffset != targetOffset)
                        {
                            // This time, do not change the kiai.
                            TimingPoint copy2 = new TimingPoint(copy)
                            {
                                Offset     = actualTargetOffset,
                                PointValue = targetSvValue
                            };
                            copy.PointValue = targetSvValue;

                            // Now, there is a trick here. If the actual inherited point
                            // exists with the edited offset, we need to edit that, not
                            // add a duplicate one and force an exception.
                            // Just set "exact" values of "copy" into this.
                            TimingPoint exact = SearchUtils.GetExactInheritedPoint(actualPoints, actualTargetOffset);
                            if (exact != null)
                            {
                                exact.setTo(copy);
                            }
                            else
                            {
                                // We haven't found an exact timing point so this is fine.
                                actualPoints.Insert(SearchUtils.GetAdditionIndex(actualPoints, copy2), copy2);
                            }
                        }
                        else
                        {
                            copy.PointValue = targetSvValue;
                            copy.Offset     = actualTargetOffset;
                            SearchUtils.MarkChangeMade(actualPoints);
                        }

                        copy.PointValue = targetSvValue;
                        copy.Offset     = actualTargetOffset;
                        SearchUtils.MarkChangeMade(actualPoints);
                    }
                }
                else
                {
                    copy.PointValue = targetSvValue;
                    copy.Offset     = actualTargetOffset;

                    // Now, there is a trick here. If the actual inherited point
                    // exists with the edited offset, we need to edit that, not
                    // add a duplicate one and force an exception.
                    // Just set "exact" values of "copy" into this.
                    TimingPoint exact = SearchUtils.GetExactInheritedPoint(actualPoints, actualTargetOffset);
                    if (exact != null)
                    {
                        exact.setTo(copy);
                    }
                    else
                    {
                        // We haven't found an exact timing point so this is fine.
                        actualPoints.Insert(SearchUtils.GetAdditionIndex(actualPoints, copy), copy);
                    }
                }

                // At the bottom, calculate the next offset
                // and continue.
                if (putPointsByNotes)
                {
                    if (hitObjectIndex == objects.Count - 1)
                    {
                        break;
                    }
                    targetOffset = objects[++hitObjectIndex].Offset;
                }
                else
                {
                    targetOffset += gridSnap / currentBpmValue;
                }
            }

            // At the end, force sort the points
            // and return true.
            actualPoints.Sort();
            SearchUtils.MarkSorted(actualPoints);
            return(true);
        }