Skip to content

Commit

Permalink
Merge pull request #313 from andy840119/note-utils
Browse files Browse the repository at this point in the history
Implement note utility to handle separate and combine notes.
  • Loading branch information
andy840119 authored Dec 13, 2020
2 parents edbb0de + 0a294ae commit 463619b
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 36 deletions.
149 changes: 149 additions & 0 deletions osu.Game.Rulesets.Karaoke.Tests/Utils/NoteUtilsTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright (c) andy840119 <[email protected]>. 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]);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ private void processNotes(Beatmap beatmap, IList<string> 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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// Copyright (c) andy840119 <[email protected]>. 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;
using osu.Game.Graphics.UserInterface;
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;

Expand All @@ -32,10 +34,10 @@ public class NoteSelectionBlueprint : KaraokeSelectionBlueprint<Note>
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)
Expand Down
31 changes: 0 additions & 31 deletions osu.Game.Rulesets.Karaoke/Objects/Note.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) andy840119 <[email protected]>. 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;
Expand Down Expand Up @@ -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();
}
}
75 changes: 75 additions & 0 deletions osu.Game.Rulesets.Karaoke/Utils/NoteUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) andy840119 <[email protected]>. 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<Note, Note> 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<Note, Note>(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
};
}
}
}

0 comments on commit 463619b

Please sign in to comment.