Skip to content

Commit 520798c

Browse files
authored
Merge pull request #7 from dudehacker/fix/c2s-zero-meter-timing
Handle zero C2S meter in UGC export
2 parents 1d752f9 + b12f659 commit 520798c

2 files changed

Lines changed: 53 additions & 17 deletions

File tree

‎generator/chu/UgcGenerator.cs‎

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public class UgcGenerator : IGenerator<ChuChart>
1212
private int RSL = 480 * 4;
1313
private List<Alert> alerts = [];
1414
public List<(string, string)> ExtraHeaders = [];
15-
15+
1616
private int useTil = 0; // 当前的 @USETIL 值
1717

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

5151
void Dfs(ChuNote n)
@@ -86,12 +86,24 @@ void Dfs(ChuNote n)
8686
}
8787
// 为了实现从上述 T函数 中的换算,所必要的信息。可通过CalcUgcBeats函数算出。
8888
private List<(int, int, int)> _ugcBeats = [];
89-
90-
private void FillUgcBeats(List<MET> metList)
89+
90+
private void FillUgcBeats(ChuChart chart)
9191
{
9292
_ugcBeats = [];
93-
foreach (var met in metList)
93+
foreach (var origMet in chart.MetList)
9494
{
95+
var met = origMet;
96+
if (met.Numerator <= 0)
97+
{
98+
// See https://github.com/MuNET-OSS/MuConvert/pull/7#discussion_r4217218971 ,
99+
// in official game, met.Numerator == 0 means there should be no "beat lines", which cannot be easily implemented in Umiguri.
100+
// But this will cause DivideByZeroException in the `T` function, so we just force met.Numerator to be equals with met.Denominator.
101+
alerts.Add(new Alert(Alert.LEVEL.Info, $"UgcGenerator 不支持使用MET拍号的分子小于等于0,来取消节拍提示线的语法。" +
102+
$"该处MET已被等效为\"MET {met.Time.WholePart} {(met.Time.FractionPart * 384).Round()} {met.Denominator} {met.Denominator}\",即节拍提示线还是会正常显示。",
103+
(chart, met.Time), relevantNote: $"MET {met.Time.WholePart} {(met.Time.FractionPart * 384).Round()} {met.Denominator} {met.Numerator}"));
104+
met = met with { Numerator = met.Denominator };
105+
}
106+
95107
if (_ugcBeats.Count == 0)
96108
{
97109
if (met.Time > 0) _ugcBeats.Add((0, 4, 4)); // 鲁棒性,补 @BEAT 0 4 4。不能continue,因为马上还要添加显式的那一条。
@@ -112,13 +124,15 @@ private void FillUgcBeats(List<MET> metList)
112124
}
113125
_ugcBeats.Add((ugcBar, met.Numerator, met.Denominator));
114126
}
127+
128+
if (_ugcBeats.Count == 0) _ugcBeats.Add((0, 4, 4));
115129
}
116130

117131
private string Serialize(ChuChart ugc)
118132
{
119133
ugc.Sort();
120-
FillUgcBeats(ugc.MetList);
121-
134+
FillUgcBeats(ugc);
135+
122136
var sb = new StringBuilder();
123137
sb.AppendLine($"' Created with MuConvert v{Utils.AppVersion}");
124138
sb.AppendLine("@VER\t8");
@@ -161,15 +175,15 @@ private string Serialize(ChuChart ugc)
161175
tilList[((t.Time + t.Duration).CanonicalForm, groupId)] = 1;
162176
}
163177
}
164-
178+
165179
foreach (var s in tilList.ToList()
166-
.OrderBy(x=>(x.Key.time, x.Key.groupId)))
167-
{
168-
var (m, o) = T(s.Key.time);
180+
.OrderBy(x => (x.Key.time, x.Key.groupId)))
181+
{
182+
var (m, o) = T(s.Key.time);
169183
sb.AppendLine(FormattableString.Invariant($"@TIL\t{s.Key.groupId}\t{m}'{o}\t{s.Value:0.00000}"));
170184
}
171185
#endregion
172-
186+
173187
sb.AppendLine("@MAINTIL\t0"); // 用户没有通过ExtraHeaders指定,则提供一个默认值
174188
sb.AppendLine("@ENDHEAD");
175189
sb.AppendLine();
@@ -232,7 +246,7 @@ private void AppendFollowerLines(StringBuilder sb, ChuNote n)
232246
}
233247

234248
private static string EncodeAirHeight(decimal value) => IToH36(Math.Clamp((int)Math.Round(Height_ToUgc(value) * 10), 0, 1295)).PadLeft(2, '0');
235-
249+
236250
private string AirColor(ChuNote n)
237251
{
238252
var color = AirColor_ToUgc(n);
@@ -241,7 +255,7 @@ private string AirColor(ChuNote n)
241255
return color;
242256
}
243257
private string CrushColor(ChuNote n) => AirCrush_Color_ToUgc[n.Color];
244-
private string CrushInterval(Rational? crushInterval) =>
258+
private string CrushInterval(Rational? crushInterval) =>
245259
crushInterval != null ? Utils.Tick(crushInterval.Value, RSL).ToString() : "$";
246260

247261
private string UCode(ChuNote n)

‎tests/chu/UgcTimeTests.cs‎

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ private static void AssertBeatEntriesEqual(
8484
}
8585
}
8686

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

9090
private static List<(int Bar, int Num, int Den)> GetGeneratorUgcBeats(UgcGenerator gen)
9191
=> GetInstanceField<List<(int, int, int)>>(gen, "_ugcBeats");
@@ -100,10 +100,32 @@ private static (int Bar, int Tick) GeneratorT(UgcGenerator gen, Rational time)
100100
public void FillUgcBeats_MatchesTerminalUgcBeats()
101101
{
102102
var gen = new UgcGenerator();
103-
FillUgcBeats(gen, LoadTerminalMetList());
103+
var chart = new ChuChart();
104+
chart.MetList.AddRange(LoadTerminalMetList());
105+
FillUgcBeats(gen, chart);
104106
AssertBeatEntriesEqual(LoadTerminalUgcBeats(), GetGeneratorUgcBeats(gen));
105107
}
106108

109+
[Fact]
110+
public void UgcGenerator_IgnoresZeroNumeratorMeterAndKeepsMappingLaterNotes()
111+
{
112+
var chart = new ChuChart();
113+
chart.MetList.AddRange([
114+
new MET(Rational.Zero, 4, 4),
115+
new MET(1, 1, 4),
116+
new MET(3, 0, 4),
117+
]);
118+
chart.BpmList.Add(new BPM(Rational.Zero, 120));
119+
chart.Notes.Add(new ChuNote { Type = ChuNoteType.Tap, Time = 3, Cell = 0, Width = 1 });
120+
121+
var (ugc, alerts) = new UgcGenerator().Generate(chart);
122+
123+
Assert.Single(alerts);
124+
Assert.Contains("@BEAT\t1\t1\t4", ugc);
125+
Assert.DoesNotContain("@BEAT\t9\t0\t4", ugc);
126+
Assert.Contains("#9'0:t01", ugc);
127+
}
128+
107129
public static IEnumerable<object[]> ParserTCases =>
108130
[
109131
[0, 0, Rational.Zero],

0 commit comments

Comments
 (0)