Skip to content

Commit

Permalink
Merge pull request #1806 from andy840119/implement-page-auto-generator
Browse files Browse the repository at this point in the history
Implement page auto generator
  • Loading branch information
andy840119 authored Dec 22, 2022
2 parents 5bf358f + 8f63613 commit b4b3893
Show file tree
Hide file tree
Showing 19 changed files with 456 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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.Beatmaps;
using osu.Game.Rulesets.Karaoke.Edit.Generator.Beatmaps;

namespace osu.Game.Rulesets.Karaoke.Tests.Editor.Generator.Beatmaps
{
public abstract class BaseBeatmapDetectorTest<TDetector, TObject, TConfig>
where TDetector : class, IBeatmapPropertyDetector<TObject> where TConfig : new()
{
protected static TConfig GeneratorConfig(params string?[] properties)
{
var config = new TConfig();

foreach (string? propertyName in properties)
{
if (propertyName == null)
continue;

var theMethod = config.GetType().GetProperty(propertyName);
if (theMethod == null)
throw new MissingMethodException("Config is not exist.");

theMethod.SetValue(config, true);
}

return config;
}

protected static TDetector GenerateDetector(TConfig config)
{
if (Activator.CreateInstance(typeof(TDetector), config) is not TDetector detector)
throw new ArgumentNullException(nameof(detector));

return detector;
}

protected static void CheckCanDetect(KaraokeBeatmap beatmap, bool canDetect, TConfig config)
{
var detector = GenerateDetector(config);

CheckCanDetect(beatmap, canDetect, detector);
}

protected static void CheckCanDetect(KaraokeBeatmap beatmap, bool canDetect, TDetector detector)
{
bool actual = detector.CanDetect(beatmap);
Assert.AreEqual(canDetect, actual);
}

protected void CheckDetectResult(KaraokeBeatmap beatmap, TObject expected, TConfig config)
{
var detector = GenerateDetector(config);

CheckDetectResult(beatmap, expected, detector);
}

protected void CheckDetectResult(KaraokeBeatmap beatmap, TObject expected, TDetector detector)
{
// create time tag and actually time tag.
var actual = detector.Detect(beatmap);
AssertEqual(expected, actual);
}

protected abstract void AssertEqual(TObject expected, TObject actual);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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.Beatmaps;
using osu.Game.Rulesets.Karaoke.Edit.Generator;
using osu.Game.Rulesets.Karaoke.Edit.Generator.Beatmaps;

namespace osu.Game.Rulesets.Karaoke.Tests.Editor.Generator.Beatmaps
{
public abstract class BaseBeatmapGeneratorTest<TGenerator, TObject, TConfig>
where TGenerator : class, IBeatmapPropertyGenerator<TObject> where TConfig : IHasConfig<TConfig>, new()
{
protected static TConfig GeneratorConfig(Action<TConfig>? action = null)
{
var config = new TConfig().CreateDefaultConfig();
action?.Invoke(config);
return config;
}

protected static TGenerator GenerateGenerator(TConfig config)
{
if (Activator.CreateInstance(typeof(TGenerator), config) is not TGenerator generator)
throw new ArgumentNullException(nameof(generator));

return generator;
}

protected static void CheckCanGenerate(KaraokeBeatmap beatmap, bool canGenerate, TConfig config)
{
var generator = GenerateGenerator(config);

CheckCanGenerate(beatmap, canGenerate, generator);
}

protected static void CheckCanGenerate(KaraokeBeatmap beatmap, bool canGenerate, TGenerator generator)
{
bool actual = generator.CanGenerate(beatmap);
Assert.AreEqual(canGenerate, actual);
}

protected void CheckGenerateResult(KaraokeBeatmap beatmap, TObject expected, TConfig config)
{
var generator = GenerateGenerator(config);

CheckGenerateResult(beatmap, expected, generator);
}

protected void CheckGenerateResult(KaraokeBeatmap beatmap, TObject expected, TGenerator generator)
{
// create time tag and actually time tag.
var actual = generator.Generate(beatmap);
AssertEqual(expected, actual);
}

protected abstract void AssertEqual(TObject expected, TObject actual);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// 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 System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Game.Rulesets.Karaoke.Beatmaps;
using osu.Game.Rulesets.Karaoke.Beatmaps.Metadatas;
using osu.Game.Rulesets.Karaoke.Edit.Checks;
using osu.Game.Rulesets.Karaoke.Edit.Generator.Beatmaps.Pages;
using osu.Game.Rulesets.Karaoke.Objects;
using osu.Game.Rulesets.Karaoke.Tests.Helper;

namespace osu.Game.Rulesets.Karaoke.Tests.Editor.Generator.Beatmaps.Pages;

[TestFixture]
public class PageGeneratorTest : BaseBeatmapGeneratorTest<PageGenerator, Page[], PageGeneratorConfig>
{
private const double min_interval = CheckBeatmapPageInfo.MIN_INTERVAL;
private const double max_interval = CheckBeatmapPageInfo.MAX_INTERVAL;

[TestCase(new[] { "[1000,3000]:karaoke" }, true)]
[TestCase(new[] { "[1000,3000]:karaoke", "[4000,6000]:karaoke" }, true)]
[TestCase(new[] { "[1000,3000]:karaoke", "[1000,3000]:karaoke" }, true)] // should still runnable even if lyric is overlapping.
[TestCase(new string[] { }, false)]
public void TestCanGenerate(string[] lyrics, bool canGenerate)
{
var config = GeneratorConfig();
var beatmap = new KaraokeBeatmap
{
HitObjects = TestCaseTagHelper.ParseLyrics(lyrics).OfType<KaraokeHitObject>().ToList(),
};

CheckCanGenerate(beatmap, canGenerate, config);
}

[Test]
public void TestGenerateWithZeroLyric()
{
var config = GeneratorConfig();
var beatmap = new KaraokeBeatmap
{
HitObjects = new List<KaraokeHitObject>()
};

var expectedPages = Array.Empty<Page>();

CheckGenerateResult(beatmap, expectedPages, config);
}

[TestCase("[1000,3000]:karaoke", new double[] { 1000, 3000 })]
[TestCase("[1000,23000]:karaoke", new[] { 1000, 1000 + max_interval, 1000 + max_interval * 2, 23000 })]
public void TestGenerateWithSingleLyric(string lyric, double[] expectedTimes)
{
var config = GeneratorConfig();
var beatmap = new KaraokeBeatmap
{
HitObjects = new List<KaraokeHitObject>
{
TestCaseTagHelper.ParseLyric(lyric)
}
};

var expectedPages = expectedTimes.Select(x => new Page
{
Time = x
}).ToArray();

CheckGenerateResult(beatmap, expectedPages, config);
}

[TestCase("[1000,4000]:karaoke", "[4000,7000]:karaoke", new double[] { 1000, 4000, 7000 })]
[TestCase("[1000,4000]:karaoke", "[5000,8000]:karaoke", new double[] { 1000, 4500, 8000 })]
[TestCase("[1000,3000]:karaoke", "[1000,3000]:karaoke", new double[] { 1000, 3000 })] //should deal with overlapping lyric.
[TestCase("[1000,23000]:karaoke", "[1000,23000]:karaoke", new[] { 1000, 1000 + max_interval, 1000 + max_interval * 2, 23000 })] //should deal with overlapping lyric with long time.
[TestCase("[1000,23000]:karaoke", "[3000,4000]:karaoke", new[] { 1000, 1000 + max_interval, 1000 + max_interval * 2, 23000 })] // should ignore second lyric.
public void TestGenerateWithTwoLyrics(string firstLyric, string secondLyric, double[] expectedTimes)
{
var config = GeneratorConfig();
var beatmap = new KaraokeBeatmap
{
HitObjects = new List<KaraokeHitObject>
{
TestCaseTagHelper.ParseLyric(firstLyric),
TestCaseTagHelper.ParseLyric(secondLyric)
}
};

var expectedPages = expectedTimes.Select(x => new Page
{
Time = x
}).ToArray();

CheckGenerateResult(beatmap, expectedPages, config);
}

[Ignore("Waiting for implementation.")]
public void TestGenerateWithSingleLyricWithPage()
{
}

[Ignore("Waiting for implementation.")]
public void TestGenerateWithTwoLyricsWithPage()
{
}

protected override void AssertEqual(Page[] expected, Page[] actual)
{
string expectedTimes = string.Join(",", expected.Select(x => x.Time));
string actualTimes = string.Join(",", actual.Select(x => x.Time));
Assert.AreEqual(expectedTimes, actualTimes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

using System;
using NUnit.Framework;
using osu.Game.Rulesets.Karaoke.Edit.Generator.Lyrics.Types;
using osu.Game.Rulesets.Karaoke.Edit.Generator.Lyrics;
using osu.Game.Rulesets.Karaoke.Objects;

namespace osu.Game.Rulesets.Karaoke.Tests.Editor.Generator.Lyrics
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

using System;
using NUnit.Framework;
using osu.Game.Rulesets.Karaoke.Edit.Generator.Lyrics.Types;
using osu.Game.Rulesets.Karaoke.Edit.Generator.Lyrics;
using osu.Game.Rulesets.Karaoke.Objects;

namespace osu.Game.Rulesets.Karaoke.Tests.Editor.Generator.Lyrics
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
using osu.Framework.Allocation;
using osu.Framework.Localisation;
using osu.Game.Rulesets.Karaoke.Configuration;
using osu.Game.Rulesets.Karaoke.Edit.Generator.Lyrics;
using osu.Game.Rulesets.Karaoke.Edit.Generator.Lyrics.Language;
using osu.Game.Rulesets.Karaoke.Edit.Generator.Lyrics.Notes;
using osu.Game.Rulesets.Karaoke.Edit.Generator.Lyrics.ReferenceLyric;
using osu.Game.Rulesets.Karaoke.Edit.Generator.Lyrics.RomajiTags;
using osu.Game.Rulesets.Karaoke.Edit.Generator.Lyrics.RubyTags;
using osu.Game.Rulesets.Karaoke.Edit.Generator.Lyrics.TimeTags;
using osu.Game.Rulesets.Karaoke.Edit.Generator.Lyrics.Types;
using osu.Game.Rulesets.Karaoke.Edit.Utils;
using osu.Game.Rulesets.Karaoke.Objects;
using osu.Game.Rulesets.Karaoke.Objects.Properties;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) andy840119 <[email protected]>. Licensed under the GPL Licence.
// See the LICENCE file in the repository root for full licence text.

using osu.Framework.Localisation;
using osu.Game.Rulesets.Karaoke.Beatmaps;

namespace osu.Game.Rulesets.Karaoke.Edit.Generator.Beatmaps
{
/// <summary>
/// Base interface of the detector.
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IBeatmapPropertyDetector<out T>
{
/// <summary>
/// Determined if detect property from <see cref="KaraokeBeatmap"/> is supported.
/// </summary>
/// <param name="beatmap"></param>
/// <returns></returns>
bool CanDetect(KaraokeBeatmap beatmap) => GetInvalidMessage(beatmap) == null;

/// <summary>
/// Will get the invalid message if beatmap property is not able to be detected.
/// </summary>
/// <param name="beatmap"></param>
/// <returns></returns>
LocalisableString? GetInvalidMessage(KaraokeBeatmap beatmap);

/// <summary>
/// Detect the property from the beatmap.
/// </summary>
/// <param name="beatmap"></param>
/// <returns></returns>
T Detect(KaraokeBeatmap beatmap);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) andy840119 <[email protected]>. Licensed under the GPL Licence.
// See the LICENCE file in the repository root for full licence text.

using osu.Framework.Localisation;
using osu.Game.Rulesets.Karaoke.Beatmaps;

namespace osu.Game.Rulesets.Karaoke.Edit.Generator.Beatmaps
{
/// <summary>
/// Base interface of the auto-generator.
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IBeatmapPropertyGenerator<out T>
{
/// <summary>
/// Determined if detect property from <see cref="KaraokeBeatmap"/> is supported.
/// </summary>
/// <param name="beatmap"></param>
/// <returns></returns>
bool CanGenerate(KaraokeBeatmap beatmap) => GetInvalidMessage(beatmap) == null;

/// <summary>
/// Will get the invalid message if beatmap property is not able to be generated.
/// </summary>
/// <param name="beatmap"></param>
/// <returns></returns>
LocalisableString? GetInvalidMessage(KaraokeBeatmap beatmap);

/// <summary>
/// Generate the property from the beatmap.
/// </summary>
/// <param name="beatmap"></param>
/// <returns></returns>
T Generate(KaraokeBeatmap beatmap);
}
}
Loading

0 comments on commit b4b3893

Please sign in to comment.