Skip to content

Commit 9e2fa78

Browse files
committed
[+] TTM_EACHPAIRS和T_JUDGE_HOLD,以及Note.TimeInSeconds,以及重新实现BPMList.ToSecond并添加测试,以及各种重构等
1 parent fb029ab commit 9e2fa78

13 files changed

Lines changed: 165 additions & 75 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ finally
164164
## 开发者指南
165165
> 以下内容是面向对于本程序感兴趣,想要了解技术细节/调试bug/参与开发的开发者的。如果你只是普通用户,可以不必阅读以下内容;如果你遇到了bug,请通过[issue](https://github.com/MuNet-OSS/MuConvert/issues)进行反馈。
166166
167-
首先,推荐阅读:[simai语文档](TODO)
167+
首先,推荐阅读:[Simai语言文档](https://w.atwiki.jp/simai/pages/1002.html)
168168
169169
### MuConvert的设计理念
170170
- 转谱的本质就是transpiler。MuConvert严格遵循`源语言 ---parser--> 中间表示(IR) ---generator--> 目标语言`的transpiler通用设计模式,以确保代码的清晰和可维护性、减少冗余代码。

chart/BPMList.cs

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,18 @@ public class BPMList : List<BPM>
99
{
1010
public BPMList() {}
1111
public BPMList(IEnumerable<BPM> bpms): base(bpms) {}
12-
13-
private List<BPM> ToSeconds()
12+
13+
public Rational ToSecond(Rational barTime)
1414
{
15-
List<BPM> result = [this[0]];
1615
Utils.Assert(this[0].Time == 0, "BPM列表的开头必须为0时刻");
1716
Rational accumulation = 0;
18-
for (int i = 1; i < Count; i++)
17+
for (int i = 0; i < Count; i++)
1918
{
20-
accumulation += 240 / (Rational)this[i-1].Bpm * (this[i].Time - this[i-1].Time);
21-
result.Add(new BPM(accumulation, this[i].Bpm));
19+
var bpmRangeEnd = i < Count - 1 ? this[i + 1].Time : 999999;
20+
accumulation += 240 / (Rational)this[i].Bpm * (Utils.Min(barTime, bpmRangeEnd) - this[i].Time);
21+
if (barTime <= bpmRangeEnd) break;
2222
}
23-
return result;
24-
}
25-
26-
public Rational ToSecond(Rational barTime)
27-
{
28-
var bpmIdx = FindIndex(barTime);
29-
var seconds = ToSeconds()[bpmIdx].Time;
30-
seconds += (barTime - this[bpmIdx].Time) * (240 / (Rational)this[bpmIdx].Bpm);
31-
return seconds;
23+
return accumulation;
3224
}
3325

3426
public int FindIndex(Rational time)

chart/Duration.cs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public Duration(Note note)
3333
}
3434

3535
private BPMList BpmList => _note.Chart.BpmList;
36+
internal decimal InvariantBpm => BpmList[BpmList.FindIndex(_note.Time)].Bpm;
3637

3738
public Rational Bar
3839
{
@@ -43,9 +44,7 @@ public Rational Bar
4344
case Type.Bar:
4445
return _data;
4546
case Type.InvariantBar:
46-
var bpmIndex = BpmList.FindIndex(_note.Time);
47-
var invariantBpm = BpmList[bpmIndex].Bpm; // 音符开始时刻的bpm是不变bpm
48-
return ConvertTime(_data, (Rational)invariantBpm, null);
47+
return ConvertTime(_data, (Rational)InvariantBpm, null);
4948
case Type.Seconds:
5049
return ConvertTime(_data, 240, null); // seconds秒数可以等效为240bpm下的小节数
5150
default:
@@ -68,13 +67,9 @@ public Rational InvariantBar
6867
case Type.InvariantBar:
6968
return _data;
7069
case Type.Bar:
71-
var bpmIndex = BpmList.FindIndex(_note.Time);
72-
var invariantBpm = BpmList[bpmIndex].Bpm; // 音符开始时刻的bpm是不变bpm
73-
return ConvertTime(_data, null, (Rational)invariantBpm);
70+
return ConvertTime(_data, null, (Rational)InvariantBpm);
7471
case Type.Seconds:
75-
bpmIndex = BpmList.FindIndex(_note.Time);
76-
invariantBpm = BpmList[bpmIndex].Bpm; // 音符开始时刻的bpm是不变bpm
77-
return ConvertTime(_data, 240, (Rational)invariantBpm); // seconds秒数可以等效为240bpm下的小节数
72+
return ConvertTime(_data, 240, (Rational)InvariantBpm); // seconds秒数可以等效为240bpm下的小节数
7873
default:
7974
throw new InvalidOperationException();
8075
}
@@ -97,9 +92,7 @@ public Rational Seconds
9792
case Type.Bar:
9893
return ConvertTime(_data, null, 240); // seconds秒数可以等效为240bpm下的小节数
9994
case Type.InvariantBar:
100-
var bpmIndex = BpmList.FindIndex(_note.Time);
101-
var invariantBpm = BpmList[bpmIndex].Bpm; // 音符开始时刻的bpm是不变bpm
102-
return ConvertTime(_data, (Rational)invariantBpm, 240);
95+
return ConvertTime(_data, (Rational)InvariantBpm, 240);
10396
default:
10497
throw new InvalidOperationException();
10598
}

chart/Note.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public abstract class Note
1515

1616
public int FalseEachIdx = 0; // 如果>0,表示这是一个伪双押,数字越大、延后的时刻越多
1717

18-
public Rational TimeInSeconds => throw new NotImplementedException();
18+
public Rational TimeInSecond => Chart.ToSecond(Time);
1919

2020
public virtual Duration Duration
2121
{
@@ -40,6 +40,26 @@ protected Note(Chart chart, Rational time)
4040
}
4141

4242
public virtual string Modifiers => (IsBreak ? "b" : "") + (IsEx ? "x" : "");
43+
44+
// 当前音符落在了哪些BPM区间内、分别有多长。
45+
public List<(int bpmIdx, decimal bpm, Rational start, Rational len)> BpmRanges
46+
{
47+
get
48+
{
49+
List<(int, decimal, Rational, Rational)> result = [];
50+
var now = Time.CanonicalForm;
51+
var end = (Time + Duration.Bar).CanonicalForm;
52+
while (now < end)
53+
{
54+
var bpmIdx = Chart.BpmList.FindIndex(now);
55+
var curBpmRangeEnd = bpmIdx < Chart.BpmList.Count - 1 ? Chart.BpmList[bpmIdx + 1].Time : 999999; // 当前BPM区间的结束时刻
56+
var len = Utils.Min(end, curBpmRangeEnd) - now; // 音符落在本区间内的长度为,从当前时刻开始,到(本区间结束或音符结束的较早者)
57+
result.Add((bpmIdx, Chart.BpmList[bpmIdx].Bpm, now, len.CanonicalForm));
58+
now = (now + len).CanonicalForm;
59+
}
60+
return result;
61+
}
62+
}
4363
}
4464

4565
[DebuggerDisplay("{DebuggerDisplay(),nq}")]

chart/Statistics.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using MuConvert.utils;
2+
using Rationals;
23

34
namespace MuConvert.chart;
45

@@ -101,4 +102,16 @@ public override string ToString()
101102
$"Firework: {Firework}"];
102103
return string.Join(", ", r);
103104
}
105+
106+
public int T_JUDGE_HLD { get; private set; } = 0;
107+
public int TTM_EACHPAIRS { get; private set; } = 0;
108+
109+
private Rational _now = -1; // 计算双押个数用
110+
111+
private int getProgJudgeGrid(decimal bpm)
112+
{
113+
if (bpm < 15) return 3;
114+
int exp = (int)Math.Min(Math.Floor(Math.Log2((double)bpm / 15)), 6);
115+
return 6 * (int)Math.Pow(2, exp);
116+
}
104117
}

generator/MA2Generator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ private void AddTap(Tap tap, int bar, int tick)
228228
var stats_judge = new Dictionary<string, int>
229229
{
230230
["TAP"] = statsNoteType["TAP"] + statsNoteType["STR"] + statsNoteType["TTP"],
231-
["HLD"] = 0, // TODO 还在研究中
231+
["HLD"] = stats.T_JUDGE_HLD,
232232
["SLD"] = statsNoteType["SLD"],
233233
};
234234
foreach (var (k, v) in stats_judge)
@@ -237,7 +237,7 @@ private void AddTap(Tap tap, int bar, int tick)
237237
}
238238
result.AppendLine($"T_JUDGE_ALL\t{stats_judge.Sum(x=>x.Value)}");
239239

240-
result.AppendLine($"TTM_EACHPAIRS\t{0}"); // TODO 还在研究中
240+
result.AppendLine($"TTM_EACHPAIRS\t{stats.TTM_EACHPAIRS}");
241241

242242
result.AppendLine($"TTM_SCR_TAP\t{(statsScoring["TAP"] + statsScoring["TOUCH"]) * 500}");
243243
result.AppendLine($"TTM_SCR_BRK\t{statsScoring["BREAK"] * 2600}");

generator/MA2_103Generator.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace MuConvert.generator;
2+
3+
public class MA2_103Generator : MA2Generator
4+
{
5+
// TODO
6+
}

i18n/Locale.Designer.cs

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/BPMList测试.cs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using MuConvert.chart;
2+
using Rationals;
3+
using static MuConvert.Tests.TestUtils;
4+
5+
namespace MuConvert.Tests;
6+
7+
public class BPMList测试
8+
{
9+
private static List<BPM> ToSecondsReference(BPMList bpmList)
10+
{
11+
// 复制 BPMList.ToSeconds 的实现(该方法是 private),用于对照测试
12+
List<BPM> result = [bpmList[0]];
13+
Assert.True(bpmList[0].Time == 0, "BPM列表的开头必须为0时刻");
14+
Rational accumulation = 0;
15+
for (int i = 1; i < bpmList.Count; i++)
16+
{
17+
accumulation += 240 / (Rational)bpmList[i - 1].Bpm * (bpmList[i].Time - bpmList[i - 1].Time);
18+
result.Add(new BPM(accumulation, bpmList[i].Bpm));
19+
}
20+
21+
return result;
22+
}
23+
24+
private static Rational ToSecondsReference(BPMList bpmList, Rational barTime)
25+
{
26+
var bpmIdx = bpmList.FindIndex(barTime);
27+
var secondsList = ToSecondsReference(bpmList);
28+
var seconds = secondsList[bpmIdx].Time;
29+
seconds += (barTime - bpmList[bpmIdx].Time) * (240 / (Rational)bpmList[bpmIdx].Bpm);
30+
return seconds;
31+
}
32+
33+
[Fact]
34+
public void BPMList_ToSeconds函数测试()
35+
{
36+
var chart = LoadOneChart(out _);
37+
var bpms = chart.BpmList;
38+
39+
// 固定挑选一组“看起来像随机但其实静态”的时间点:
40+
// - 0 与几个常见分拍
41+
// - 若干 BPM 切换点(从解析结果里取前几个),以及它们的 ±1/384 邻域(最容易出 off-by-one)
42+
var times = new List<Rational>
43+
{
44+
new(0),
45+
new(1, 384),
46+
new(1, 4),
47+
new(123456, 78910), // 一个稳定的非平凡分数(不依赖谱面内容)
48+
};
49+
50+
// 取前 8 个 BPM 事件时刻(这张谱 BPM 变化很多;这里只取前缀,足够覆盖多段逻辑)
51+
foreach (var b in bpms.Take(8))
52+
times.Add(b.Time);
53+
54+
foreach (var t in bpms.Take(8).Select(b => b.Time))
55+
{
56+
times.Add(t - new Rational(1, 384));
57+
times.Add(t + new Rational(1, 384));
58+
}
59+
60+
foreach (var barTime in times)
61+
{
62+
if (barTime < 0) continue;
63+
64+
var expected = ToSecondsReference(bpms, barTime);
65+
var actual = bpms.ToSecond(barTime);
66+
Assert.Equal(expected, actual);
67+
}
68+
}
69+
}

tests/ChartShift测试.cs

Lines changed: 7 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
using System.Text;
2-
using MuConvert.chart;
3-
using MuConvert.maidata;
4-
using MuConvert.parser;
5-
using MuConvert.utils;
1+
using MuConvert.chart;
62
using Rationals;
73
using static MuConvert.Tests.TestUtils;
84

@@ -15,28 +11,6 @@ public class ChartShift测试
1511
{
1612
private static readonly Rational QuarterBar = new(1, 4);
1713

18-
private static Chart LoadChart(out List<Alert> alerts)
19-
{
20-
var repo = FindRepoRoot();
21-
var maidataPath = Path.Combine(repo.FullName, "tests", "testset", "官谱", "Xaleid◆scopiX [DX]", "maidata.txt");
22-
Assert.True(File.Exists(maidataPath), $"Missing test maidata: {maidataPath}");
23-
24-
var maidata = new Maidata(File.ReadAllText(maidataPath, Encoding.UTF8));
25-
Assert.True(maidata.Levels.ContainsKey(6), "Expected lv6 (inote_6) in maidata.");
26-
var chartInfo = maidata.Levels[6];
27-
28-
var (chart, parseAlerts) = new SimaiParser(clockCount: maidata.ClockCount)
29-
.Parse(chartInfo.Inote);
30-
alerts = parseAlerts;
31-
chart.Sort();
32-
33-
Assert.NotEmpty(chart.Notes);
34-
Assert.NotEmpty(chart.BpmList);
35-
Assert.True(chart.BpmList[0].Time == 0, "sanity");
36-
Assert.DoesNotContain(alerts, a => a.Level >= Alert.LEVEL.Error);
37-
return chart;
38-
}
39-
4014
private static List<(Rational Time, int FalseEachIdx)> NotesInStableOrder(Chart c) =>
4115
c.Notes.OrderBy(n => n.Time).ThenBy(n => n.FalseEachIdx).Select(n => (n.Time, n.FalseEachIdx)).ToList();
4216

@@ -117,7 +91,7 @@ private static void AssertBpmAndNotesMatchPositiveShiftOffset(
11791
[Fact]
11892
public void Shift_Zero()
11993
{
120-
var chart = LoadChart(out _);
94+
var chart = LoadOneChart(out _);
12195
var notesBefore = NotesInStableOrder(chart);
12296
var bpmsBefore = BpmInOrder(chart);
12397
var startBpm = chart.StartBpm;
@@ -133,7 +107,7 @@ public void Shift_Zero()
133107
[Fact]
134108
public void Shift_PositiveQuarterBar()
135109
{
136-
var chart = LoadChart(out _);
110+
var chart = LoadOneChart(out _);
137111
var notesBefore = NotesInStableOrder(chart);
138112
var bpmsBefore = BpmInOrder(chart);
139113

@@ -149,7 +123,7 @@ public void Shift_PositiveQuarterBar()
149123
[Fact]
150124
public void Shift_NegativeQuarterBar()
151125
{
152-
var chart = LoadChart(out _);
126+
var chart = LoadOneChart(out _);
153127
var notesBefore = NotesInStableOrder(chart);
154128
var bpmsBefore = BpmInOrder(chart);
155129
var userOffset = -QuarterBar;
@@ -163,7 +137,7 @@ public void Shift_NegativeQuarterBar()
163137
[Fact]
164138
public void Shift_PositiveThenNegativeQuarterBar_RoundTrip()
165139
{
166-
var chart = LoadChart(out _);
140+
var chart = LoadOneChart(out _);
167141
var notesBefore = NotesInStableOrder(chart);
168142
var bpmsBefore = BpmInOrder(chart);
169143

@@ -179,7 +153,7 @@ public void Shift_PositiveThenNegativeQuarterBar_RoundTrip()
179153
[Fact]
180154
public void Shift_WithExplicitBpm_ScalesPositiveOffsetInBarSpace()
181155
{
182-
var chart = LoadChart(out _);
156+
var chart = LoadOneChart(out _);
183157
var startBpm = chart.StartBpm;
184158
var halfBar = new Rational(1, 2);
185159

@@ -188,7 +162,7 @@ public void Shift_WithExplicitBpm_ScalesPositiveOffsetInBarSpace()
188162
chart.Shift(halfBar);
189163
var shiftedDefaultBpm = chart.Notes.OrderBy(n => n.Time).ThenBy(n => n.FalseEachIdx).First().Time;
190164

191-
var chartScaled = LoadChart(out _);
165+
var chartScaled = LoadOneChart(out _);
192166
chartScaled.Shift(halfBar, bpm: startBpm * 2);
193167
var shiftedDoubleBpmArg = chartScaled.Notes.OrderBy(n => n.Time).ThenBy(n => n.FalseEachIdx).First().Time;
194168

0 commit comments

Comments
 (0)