Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 28 additions & 14 deletions generator/chu/UgcGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class UgcGenerator : IGenerator<ChuChart>
private int RSL = 480 * 4;
private List<Alert> alerts = [];
public List<(string, string)> ExtraHeaders = [];

private int useTil = 0; // 当前的 @USETIL 值

/**
Expand Down Expand Up @@ -45,7 +45,7 @@ private List<ChuNote> SortedNotesForConnectingPrevious(ChuChart chart)
// 2. 遍历 chart.Notes,对每个 ChuNote 以 DFS 方式把它本身以及它所有 Next 子孙依次加入结果。
var result = new List<ChuNote>(chart.Notes.Count);
var visited = new HashSet<ChuNote>();
foreach (var root in chart.Notes.Where(x=>x.TargetNote == null)) Dfs(root);
foreach (var root in chart.Notes.Where(x => x.TargetNote == null)) Dfs(root);
return result;

void Dfs(ChuNote n)
Expand Down Expand Up @@ -86,12 +86,24 @@ void Dfs(ChuNote n)
}
// 为了实现从上述 T函数 中的换算,所必要的信息。可通过CalcUgcBeats函数算出。
private List<(int, int, int)> _ugcBeats = [];
private void FillUgcBeats(List<MET> metList)

private void FillUgcBeats(ChuChart chart)
{
_ugcBeats = [];
foreach (var met in metList)
foreach (var origMet in chart.MetList)
{
var met = origMet;
if (met.Numerator <= 0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Denominator 为 0 时该分支不会修正或跳过拍号,随后 T 会用零分母构造 Rational,导致导出仍然抛异常。请在写入 _ugcBeats 前对 Denominator <= 0 发出警告并跳过该条目。

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At generator/chu/UgcGenerator.cs, line 96:

<comment>`Denominator` 为 0 时该分支不会修正或跳过拍号,随后 `T` 会用零分母构造 `Rational`,导致导出仍然抛异常。请在写入 `_ugcBeats` 前对 `Denominator <= 0` 发出警告并跳过该条目。</comment>

<file context>
@@ -90,13 +90,15 @@ void Dfs(ChuNote n)
         {
-            if (met.Numerator <= 0 || met.Denominator <= 0)
+            var met = origMet;
+            if (met.Numerator <= 0)
             {
-                alerts.Add(new Alert(Alert.LEVEL.Warning,
</file context>
Suggested change
if (met.Numerator <= 0)
if (met.Denominator <= 0)
{
alerts.Add(new Alert(Alert.LEVEL.Warning,
$"UGC Generator忽略无效拍号: {met.Numerator}/{met.Denominator}", met.Time));
continue;
}
if (met.Numerator <= 0)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

目前为止没见过Denominator为0的情况,我想也没有意义,所以如果真的遇到的话,我觉得报个错是合理的

{
// See https://github.com/MuNET-OSS/MuConvert/pull/7#discussion_r4217218971 ,
// in official game, met.Numerator == 0 means there should be no "beat lines", which cannot be easily implemented in Umiguri.
// But this will cause DivideByZeroException in the `T` function, so we just force met.Numerator to be equals with met.Denominator.
alerts.Add(new Alert(Alert.LEVEL.Info, $"UgcGenerator 不支持使用MET拍号的分子小于等于0,来取消节拍提示线的语法。" +
$"该处MET已被等效为\"MET {met.Time.WholePart} {(met.Time.FractionPart * 384).Round()} {met.Denominator} {met.Denominator}\",即节拍提示线还是会正常显示。",
(chart, met.Time), relevantNote: $"MET {met.Time.WholePart} {(met.Time.FractionPart * 384).Round()} {met.Denominator} {met.Numerator}"));
met = met with { Numerator = met.Denominator };
Comment thread
Starrah marked this conversation as resolved.
}

if (_ugcBeats.Count == 0)
{
if (met.Time > 0) _ugcBeats.Add((0, 4, 4)); // 鲁棒性,补 @BEAT 0 4 4。不能continue,因为马上还要添加显式的那一条。
Expand All @@ -112,13 +124,15 @@ private void FillUgcBeats(List<MET> metList)
}
_ugcBeats.Add((ugcBar, met.Numerator, met.Denominator));
}

if (_ugcBeats.Count == 0) _ugcBeats.Add((0, 4, 4));
Comment thread
Starrah marked this conversation as resolved.
}

private string Serialize(ChuChart ugc)
{
ugc.Sort();
FillUgcBeats(ugc.MetList);
FillUgcBeats(ugc);

var sb = new StringBuilder();
sb.AppendLine($"' Created with MuConvert v{Utils.AppVersion}");
sb.AppendLine("@VER\t8");
Expand Down Expand Up @@ -161,15 +175,15 @@ private string Serialize(ChuChart ugc)
tilList[((t.Time + t.Duration).CanonicalForm, groupId)] = 1;
}
}

foreach (var s in tilList.ToList()
.OrderBy(x=>(x.Key.time, x.Key.groupId)))
{
var (m, o) = T(s.Key.time);
.OrderBy(x => (x.Key.time, x.Key.groupId)))
{
var (m, o) = T(s.Key.time);
sb.AppendLine(FormattableString.Invariant($"@TIL\t{s.Key.groupId}\t{m}'{o}\t{s.Value:0.00000}"));
}
#endregion

sb.AppendLine("@MAINTIL\t0"); // 用户没有通过ExtraHeaders指定,则提供一个默认值
sb.AppendLine("@ENDHEAD");
sb.AppendLine();
Expand Down Expand Up @@ -232,7 +246,7 @@ private void AppendFollowerLines(StringBuilder sb, ChuNote n)
}

private static string EncodeAirHeight(decimal value) => IToH36(Math.Clamp((int)Math.Round(Height_ToUgc(value) * 10), 0, 1295)).PadLeft(2, '0');

private string AirColor(ChuNote n)
{
var color = AirColor_ToUgc(n);
Expand All @@ -241,7 +255,7 @@ private string AirColor(ChuNote n)
return color;
}
private string CrushColor(ChuNote n) => AirCrush_Color_ToUgc[n.Color];
private string CrushInterval(Rational? crushInterval) =>
private string CrushInterval(Rational? crushInterval) =>
crushInterval != null ? Utils.Tick(crushInterval.Value, RSL).ToString() : "$";

private string UCode(ChuNote n)
Expand Down
28 changes: 25 additions & 3 deletions tests/chu/UgcTimeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ private static void AssertBeatEntriesEqual(
}
}

private static void FillUgcBeats(UgcGenerator gen, List<MET> metList)
=> InvokeInstance<object>(gen, "FillUgcBeats", metList);
private static void FillUgcBeats(UgcGenerator gen, ChuChart chart)
=> InvokeInstance<object>(gen, "FillUgcBeats", chart);

private static List<(int Bar, int Num, int Den)> GetGeneratorUgcBeats(UgcGenerator gen)
=> GetInstanceField<List<(int, int, int)>>(gen, "_ugcBeats");
Expand All @@ -100,10 +100,32 @@ private static (int Bar, int Tick) GeneratorT(UgcGenerator gen, Rational time)
public void FillUgcBeats_MatchesTerminalUgcBeats()
{
var gen = new UgcGenerator();
FillUgcBeats(gen, LoadTerminalMetList());
var chart = new ChuChart();
chart.MetList.AddRange(LoadTerminalMetList());
FillUgcBeats(gen, chart);
AssertBeatEntriesEqual(LoadTerminalUgcBeats(), GetGeneratorUgcBeats(gen));
}

[Fact]
public void UgcGenerator_IgnoresZeroNumeratorMeterAndKeepsMappingLaterNotes()
{
var chart = new ChuChart();
chart.MetList.AddRange([
new MET(Rational.Zero, 4, 4),
new MET(1, 1, 4),
new MET(3, 0, 4),
]);
chart.BpmList.Add(new BPM(Rational.Zero, 120));
chart.Notes.Add(new ChuNote { Type = ChuNoteType.Tap, Time = 3, Cell = 0, Width = 1 });

var (ugc, alerts) = new UgcGenerator().Generate(chart);

Assert.Single(alerts);
Assert.Contains("@BEAT\t1\t1\t4", ugc);
Assert.DoesNotContain("@BEAT\t9\t0\t4", ugc);
Assert.Contains("#9'0:t01", ugc);
}

public static IEnumerable<object[]> ParserTCases =>
[
[0, 0, Rational.Zero],
Expand Down
Loading