1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-13 16:13:34 +08:00

Merge pull request #27639 from 64ArthurAraujo/verify-unused-audio-at-end

Add verify checks to unused audio at the end
This commit is contained in:
Bartłomiej Dach 2024-03-21 12:19:53 +01:00 committed by GitHub
commit 3e764ae4f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 228 additions and 0 deletions

View File

@ -0,0 +1,145 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using Moq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Checks;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Storyboards;
using osuTK;
using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap;
namespace osu.Game.Tests.Editing.Checks
{
public class CheckUnusedAudioAtEndTest
{
private CheckUnusedAudioAtEnd check = null!;
private IBeatmap beatmapNotFullyMapped = null!;
private IBeatmap beatmapFullyMapped = null!;
[SetUp]
public void Setup()
{
check = new CheckUnusedAudioAtEnd();
beatmapNotFullyMapped = new Beatmap<HitObject>
{
HitObjects =
{
new HitCircle { StartTime = 0 },
new HitCircle { StartTime = 1_298 },
},
BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata { AudioFile = "abc123.jpg" }
}
};
beatmapFullyMapped = new Beatmap<HitObject>
{
HitObjects =
{
new HitCircle { StartTime = 0 },
new HitCircle { StartTime = 9000 },
},
BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata { AudioFile = "abc123.jpg" },
}
};
}
[Test]
public void TestEmptyBeatmap()
{
var context = getContext(new Beatmap<HitObject>());
var issues = check.Run(context).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckUnusedAudioAtEnd.IssueTemplateUnusedAudioAtEnd);
}
[Test]
public void TestAudioNotFullyUsed()
{
var context = getContext(beatmapNotFullyMapped);
var issues = check.Run(context).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckUnusedAudioAtEnd.IssueTemplateUnusedAudioAtEnd);
}
[Test]
public void TestAudioNotFullyUsedWithVideo()
{
var storyboard = new Storyboard();
var video = new StoryboardVideo("abc123.mp4", 0);
storyboard.GetLayer("Video").Add(video);
var mockWorkingBeatmap = getMockWorkingBeatmap(beatmapNotFullyMapped, storyboard);
var context = getContext(beatmapNotFullyMapped, mockWorkingBeatmap);
var issues = check.Run(context).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckUnusedAudioAtEnd.IssueTemplateUnusedAudioAtEndStoryboardOrVideo);
}
[Test]
public void TestAudioNotFullyUsedWithStoryboardElement()
{
var storyboard = new Storyboard();
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
storyboard.GetLayer("Background").Add(sprite);
var mockWorkingBeatmap = getMockWorkingBeatmap(beatmapNotFullyMapped, storyboard);
var context = getContext(beatmapNotFullyMapped, mockWorkingBeatmap);
var issues = check.Run(context).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckUnusedAudioAtEnd.IssueTemplateUnusedAudioAtEndStoryboardOrVideo);
}
[Test]
public void TestAudioFullyUsed()
{
var context = getContext(beatmapFullyMapped);
var issues = check.Run(context).ToList();
Assert.That(issues, Has.Count.EqualTo(0));
}
private BeatmapVerifierContext getContext(IBeatmap beatmap)
{
return new BeatmapVerifierContext(beatmap, getMockWorkingBeatmap(beatmap, new Storyboard()).Object);
}
private BeatmapVerifierContext getContext(IBeatmap beatmap, Mock<IWorkingBeatmap> workingBeatmap)
{
return new BeatmapVerifierContext(beatmap, workingBeatmap.Object);
}
private Mock<IWorkingBeatmap> getMockWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard)
{
var mockTrack = new TrackVirtualStore(new FramedClock()).GetVirtual(10000, "virtual");
var mockWorkingBeatmap = new Mock<IWorkingBeatmap>();
mockWorkingBeatmap.SetupGet(w => w.Beatmap).Returns(beatmap);
mockWorkingBeatmap.SetupGet(w => w.Track).Returns(mockTrack);
mockWorkingBeatmap.SetupGet(w => w.Storyboard).Returns(storyboard);
return mockWorkingBeatmap;
}
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps.ControlPoints;
@ -129,6 +130,7 @@ namespace osu.Game.Beatmaps
///
/// It's not super efficient so calls should be kept to a minimum.
/// </remarks>
/// <exception cref="InvalidOperationException">If <paramref name="beatmap"/> has no objects.</exception>
public static double GetLastObjectTime(this IBeatmap beatmap) => beatmap.HitObjects.Max(h => h.GetEndTime());
#region Helper methods

View File

@ -36,6 +36,7 @@ namespace osu.Game.Rulesets.Edit
new CheckConcurrentObjects(),
new CheckZeroLengthObjects(),
new CheckDrainLength(),
new CheckUnusedAudioAtEnd(),
// Timing
new CheckPreviewTime(),

View File

@ -0,0 +1,80 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit.Checks.Components;
using osu.Game.Storyboards;
namespace osu.Game.Rulesets.Edit.Checks
{
public class CheckUnusedAudioAtEnd : ICheck
{
public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Compose, "More than 20% unused audio at the end");
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
{
new IssueTemplateUnusedAudioAtEnd(this),
new IssueTemplateUnusedAudioAtEndStoryboardOrVideo(this),
};
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
{
double mappedLength = context.Beatmap.HitObjects.Any() ? context.Beatmap.GetLastObjectTime() : 0;
double trackLength = context.WorkingBeatmap.Track.Length;
double mappedPercentage = Math.Round(mappedLength / trackLength * 100);
if (mappedPercentage < 80)
{
double percentageLeft = Math.Abs(mappedPercentage - 100);
bool storyboardIsPresent = isAnyStoryboardElementPresent(context.WorkingBeatmap.Storyboard);
if (storyboardIsPresent)
{
yield return new IssueTemplateUnusedAudioAtEndStoryboardOrVideo(this).Create(percentageLeft);
}
else
{
yield return new IssueTemplateUnusedAudioAtEnd(this).Create(percentageLeft);
}
}
}
private bool isAnyStoryboardElementPresent(Storyboard storyboard)
{
foreach (var layer in storyboard.Layers)
{
foreach (var _ in layer.Elements)
{
return true;
}
}
return false;
}
public class IssueTemplateUnusedAudioAtEnd : IssueTemplate
{
public IssueTemplateUnusedAudioAtEnd(ICheck check)
: base(check, IssueType.Warning, "Currently there is {0}% unused audio at the end. Ensure the outro significantly contributes to the song, otherwise cut the outro.")
{
}
public Issue Create(double percentageLeft) => new Issue(this, percentageLeft);
}
public class IssueTemplateUnusedAudioAtEndStoryboardOrVideo : IssueTemplate
{
public IssueTemplateUnusedAudioAtEndStoryboardOrVideo(ICheck check)
: base(check, IssueType.Warning, "Currently there is {0}% unused audio at the end. Ensure the outro significantly contributes to the song, or is being occupied by the video or storyboard, otherwise cut the outro.")
{
}
public Issue Create(double percentageLeft) => new Issue(this, percentageLeft);
}
}
}