diff --git a/osu.Game.Rulesets.Karaoke.Tests/Utils/NoteUtilsTest.cs b/osu.Game.Rulesets.Karaoke.Tests/Utils/NoteUtilsTest.cs new file mode 100644 index 000000000..cdd228694 --- /dev/null +++ b/osu.Game.Rulesets.Karaoke.Tests/Utils/NoteUtilsTest.cs @@ -0,0 +1,149 @@ +// Copyright (c) andy840119 . Licensed under the GPL Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using NUnit.Framework; +using osu.Game.Rulesets.Karaoke.Objects; +using osu.Game.Rulesets.Karaoke.Utils; + +namespace osu.Game.Rulesets.Karaoke.Tests.Utils +{ + public class NoteUtilsTest + { + [TestCase(new double[] { 1000, 3000 }, 0, 1, new double[] { 1000, 3000 })] + [TestCase(new double[] { 1000, 3000 }, 0, 0.5, new double[] { 1000, 1500 })] + [TestCase(new double[] { 1000, 3000 }, 0.5, 0.5, new double[] { 2500, 1500 })] + [TestCase(new double[] { 1000, 3000 }, 0.3, 0.4, new double[] { 1900, 1200 })] + [TestCase(new double[] { 1000, 3000 }, 0.3, 1, null)] // start + duration should not exceed 1 + public void TestSliceNoteTime(double[] time, double startPercentage, double durationPercentage, double[] actualTime) + { + var note = new Note + { + StartTime = time[0], + Duration = time[1], + }; + + try + { + var sliceNote = NoteUtils.SliceNote(note, startPercentage, durationPercentage); + Assert.AreEqual(sliceNote.StartTime, actualTime[0]); + Assert.AreEqual(sliceNote.Duration, actualTime[1]); + } + catch + { + Assert.IsNull(actualTime); + } + } + + [TestCase(new double[] { 1000, 5000 }, 0.2, new double[] { 1000, 1000 }, new double[] { 2000, 4000 })] + [TestCase(new double[] { 1000, 5000 }, 0.5, new double[] { 1000, 2500 }, new double[] { 3500, 2500 })] + [TestCase(new double[] { 1000, 0 }, 0.2, new double[] { 1000, 0 }, new double[] { 1000, 0 })] // it's ok to split if duration is 0. + [TestCase(new double[] { 1000, 0 }, 0.7, new double[] { 1000, 0 }, new double[] { 1000, 0 })] + [TestCase(new double[] { 1000, 5000 }, -1, null, null)] // should be in the range. + [TestCase(new double[] { 1000, 5000 }, 3, null, null)] + [TestCase(new double[] { 1000, 5000 }, 0, null, null)] // should not be 0 or 1. + [TestCase(new double[] { 1000, 5000 }, 1, null, null)] + public void TestSeparateNoteTime(double[] time, double percentage, double[] firstTime, double[] secondTime) + { + var note = new Note + { + StartTime = time[0], + Duration = time[1], + }; + + try + { + var (firstNote, secondNote) = NoteUtils.SplitNote(note, percentage); + Assert.AreEqual(firstNote.StartTime, firstTime[0]); + Assert.AreEqual(firstNote.Duration, firstTime[1]); + + Assert.AreEqual(secondNote.StartTime, secondTime[0]); + Assert.AreEqual(secondNote.Duration, secondTime[1]); + } + catch + { + Assert.IsNull(firstTime); + Assert.IsNull(secondTime); + } + } + + [Test] + public void TestSeparateNoteOtherProperty() + { + const double percentage = 0.3; + var lyric = new Lyric(); + + var note = new Note + { + StartTime = 1000, + Duration = 2000, + StartIndex = 1, + EndIndex = 2, + Text = "ka", + Singers = new[] { 0 }, + Display = false, + Tone = new Tone(-1, true), + ParentLyric = lyric + }; + + // create other property and make sure other class is applied value. + var (firstNote, secondNote) = NoteUtils.SplitNote(note, percentage); + + Assert.AreEqual(firstNote.StartTime, 1000); + Assert.AreEqual(secondNote.StartTime, 1600); + + Assert.AreEqual(firstNote.Duration, 600); + Assert.AreEqual(secondNote.Duration, 1400); + + testRemainProperty(firstNote, note); + testRemainProperty(firstNote, note); + + static void testRemainProperty(Note expect, Note actual) + { + Assert.AreEqual(expect.StartIndex, actual.StartIndex); + Assert.AreEqual(expect.EndIndex, actual.EndIndex); + Assert.AreEqual(expect.Text, actual.Text); + + Assert.AreEqual(expect.Singers, actual.Singers); + Assert.AreNotSame(expect.Singers, actual.Singers); + + Assert.AreEqual(expect.Display, actual.Display); + Assert.AreEqual(expect.Tone, actual.Tone); + Assert.AreEqual(expect.ParentLyric, actual.ParentLyric); + } + } + + [TestCase(new double[] { 1000, 1000 }, new double[] { 2000, 4000 }, new double[] { 1000, 5000 })] + [TestCase(new double[] { 1000, 2500 }, new double[] { 3500, 2500 }, new double[] { 1000, 5000 })] + [TestCase(new double[] { 1000, 0 }, new double[] { 1000, 0 }, new double[] { 1000, 0 })] // it's ok to combine if duration is 0. + public void TestCombineNoteTime(double[] firstTime, double[] secondTime, double[] actualTime) + { + const int start_index = 3; + const int end_index = 5; + + var lyric = new Lyric(); + + var firstNote = new Note + { + StartIndex = start_index, + EndIndex = end_index, + ParentLyric = lyric, + StartTime = firstTime[0], + Duration = firstTime[1], + }; + + var secondNote = new Note + { + StartIndex = start_index, + EndIndex = end_index, + ParentLyric = lyric, + StartTime = secondTime[0], + Duration = secondTime[1], + }; + + var combineNote = NoteUtils.CombineNote(firstNote, secondNote); + Assert.AreEqual(combineNote.StartTime, actualTime[0]); + Assert.AreEqual(combineNote.Duration, actualTime[1]); + } + } +} diff --git a/osu.Game.Rulesets.Karaoke/Beatmaps/Formats/KaraokeLegacyBeatmapDecoder.cs b/osu.Game.Rulesets.Karaoke/Beatmaps/Formats/KaraokeLegacyBeatmapDecoder.cs index 5f291123a..3589f910c 100644 --- a/osu.Game.Rulesets.Karaoke/Beatmaps/Formats/KaraokeLegacyBeatmapDecoder.cs +++ b/osu.Game.Rulesets.Karaoke/Beatmaps/Formats/KaraokeLegacyBeatmapDecoder.cs @@ -168,7 +168,7 @@ private void processNotes(Beatmap beatmap, IList lines) } // Split note and apply them - var splitDefaultNote = defaultNote.CopyByPercentage(startPercentage, percentage); + var splitDefaultNote = NoteUtils.SliceNote(defaultNote, startPercentage, percentage); startPercentage += percentage; if (!string.IsNullOrEmpty(ruby)) splitDefaultNote.Text = ruby; diff --git a/osu.Game.Rulesets.Karaoke/Edit/Blueprints/Notes/NoteSelectionBlueprint.cs b/osu.Game.Rulesets.Karaoke/Edit/Blueprints/Notes/NoteSelectionBlueprint.cs index df617991b..76265199d 100644 --- a/osu.Game.Rulesets.Karaoke/Edit/Blueprints/Notes/NoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Karaoke/Edit/Blueprints/Notes/NoteSelectionBlueprint.cs @@ -1,6 +1,7 @@ // Copyright (c) andy840119 . Licensed under the GPL Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; @@ -8,6 +9,7 @@ using osu.Game.Rulesets.Karaoke.Edit.Blueprints.Notes.Components; using osu.Game.Rulesets.Karaoke.Objects; using osu.Game.Rulesets.Karaoke.Objects.Drawables; +using osu.Game.Rulesets.Karaoke.Utils; using osu.Game.Screens.Edit; using osuTK; @@ -32,10 +34,10 @@ public class NoteSelectionBlueprint : KaraokeSelectionBlueprint private void splitNote() { // TODO : percentage should be enter by dialog - var splittedNote = HitObject.CopyByPercentage(0.5); - EditorBeatmap?.Add(splittedNote); - // Change object's duration - HitObject.Duration = HitObject.Duration - splittedNote.Duration; + var (firstNote, secondNote) = NoteUtils.SplitNote(HitObject, 0.5); + EditorBeatmap?.Add(firstNote); + EditorBeatmap?.Add(secondNote); + EditorBeatmap?.Remove(HitObject); } public void ChangeDisplay(bool display) diff --git a/osu.Game.Rulesets.Karaoke/Objects/Note.cs b/osu.Game.Rulesets.Karaoke/Objects/Note.cs index 6a336a6cb..1d7bd3fc5 100644 --- a/osu.Game.Rulesets.Karaoke/Objects/Note.cs +++ b/osu.Game.Rulesets.Karaoke/Objects/Note.cs @@ -1,7 +1,6 @@ // Copyright (c) andy840119 . Licensed under the GPL Licence. // See the LICENCE file in the repository root for full licence text. -using System; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Game.Rulesets.Judgements; @@ -88,36 +87,6 @@ public virtual Tone Tone public Lyric ParentLyric { get; set; } - public Note CopyByPercentage(double startPercentage = 0, double durationPercentage = 0.5) - { - if (startPercentage < 0 || startPercentage + durationPercentage > 1) - throw new ArgumentOutOfRangeException($"{nameof(Note)} cannot assign split range of start from {startPercentage} and duration {durationPercentage}"); - - var startTime = StartTime + Duration * startPercentage; - var duration = Duration * durationPercentage; - - return CopyByTime(startTime, duration); - } - - public Note CopyByTime(double startTime, double duration) - { - if (startTime < StartTime || startTime + duration > EndTime) - throw new ArgumentOutOfRangeException($"{nameof(Note)} cannot assign split range of start from {startTime} and duration {duration}"); - - return new Note - { - StartTime = startTime, - Duration = duration, - StartIndex = StartIndex, - EndIndex = EndIndex, - Text = Text, - Singers = Singers, - Display = Display, - Tone = Tone, - ParentLyric = ParentLyric - }; - } - public override Judgement CreateJudgement() => new KaraokeNoteJudgement(); } } diff --git a/osu.Game.Rulesets.Karaoke/Utils/NoteUtils.cs b/osu.Game.Rulesets.Karaoke/Utils/NoteUtils.cs new file mode 100644 index 000000000..f9a7c93e4 --- /dev/null +++ b/osu.Game.Rulesets.Karaoke/Utils/NoteUtils.cs @@ -0,0 +1,75 @@ +// Copyright (c) andy840119 . Licensed under the GPL Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Karaoke.Objects; +using System; + +namespace osu.Game.Rulesets.Karaoke.Utils +{ + public static class NoteUtils + { + public static Note SliceNote(Note note, double startPercentage, double durationPercentage) + { + if (startPercentage < 0 || startPercentage + durationPercentage > 1) + throw new ArgumentOutOfRangeException($"{nameof(Note)} cannot assign split range of start from {startPercentage} and duration {durationPercentage}"); + + var startTime = note.StartTime + note.Duration * startPercentage; + var duration = note.Duration * durationPercentage; + + return copyByTime(note, startTime, duration); + } + + public static Tuple SplitNote(Note note, double percentage = 0.5) + { + if (percentage < 0 || percentage > 1) + throw new ArgumentOutOfRangeException(nameof(Note)); + + if (percentage == 0 || percentage == 1) + throw new InvalidOperationException($"{nameof(percentage)} cannot be {0} or {1}."); + + var firstNoteStartTime = note.StartTime; + var firstNoteDuration = note.Duration * percentage; + + var secondNoteStartTime = firstNoteStartTime + firstNoteDuration; + var secondNoteDuration = note.Duration * (1 - percentage); + + var firstNote = copyByTime(note, firstNoteStartTime, firstNoteDuration); + var secondNote = copyByTime(note, secondNoteStartTime, secondNoteDuration); + + return new Tuple(firstNote, secondNote); + } + + public static Note CombineNote(Note firstLyric, Note secondLyric) + { + if (firstLyric.ParentLyric != secondLyric.ParentLyric) + throw new InvalidOperationException($"{nameof(firstLyric.ParentLyric)} and {nameof(secondLyric.ParentLyric)} should be same."); + + if (firstLyric.StartIndex != secondLyric.StartIndex) + throw new InvalidOperationException($"{nameof(firstLyric.StartIndex)} and {nameof(secondLyric.StartIndex)} should be same."); + + if (firstLyric.EndIndex != secondLyric.EndIndex) + throw new InvalidOperationException($"{nameof(firstLyric.EndIndex)} and {nameof(secondLyric.EndIndex)} should be same."); + + var startTime = Math.Min(firstLyric.StartTime, secondLyric.StartTime); + var endTime = Math.Max(firstLyric.EndTime, secondLyric.EndTime); + + return copyByTime(firstLyric, startTime, endTime - startTime); + } + + private static Note copyByTime(Note originNote, double startTime, double duration) + { + return new Note + { + StartTime = startTime, + Duration = duration, + StartIndex = originNote.StartIndex, + EndIndex = originNote.EndIndex, + Text = originNote.Text, + Singers = originNote.Singers?.Clone() as int[], + Display = originNote.Display, + Tone = originNote.Tone, + ParentLyric = originNote.ParentLyric + }; + } + } +}