Skip to content

Commit cffe63f

Browse files
committed
[WIP] SimaiGenerator初步
1 parent b9d39fe commit cffe63f

3 files changed

Lines changed: 177 additions & 5 deletions

File tree

Program.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,6 @@ private static int Main(string[] args)
1616
{
1717
return root.Parse(args).Invoke();
1818
}
19-
catch (NotImplementedException ex)
20-
{
21-
Console.Error.WriteLine(ex.Message);
22-
return 1;
23-
}
2419
catch (ConversionException ex)
2520
{
2621
PrintAlerts(ex.Alerts, "转换失败:");

chart/BPMList.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ public int FindIndex(Rational time)
3838
return i-1;
3939
}
4040

41+
public bool IsBpmChanged(Rational start, Rational end)
42+
{
43+
var bpmIdx = FindIndex(start);
44+
return bpmIdx < Count - 1 && this[bpmIdx + 1].Time < end;
45+
}
46+
4147
private string DebuggerDisplay()
4248
{
4349
var result = "";

generator/SimaiGenerator.cs

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
using System.Numerics;
2+
using System.Text;
3+
using MuConvert.chart;
4+
using MuConvert.utils;
5+
using Rationals;
6+
using static MuConvert.utils.Alert.LEVEL;
7+
8+
namespace MuConvert.generator;
9+
10+
class SimaiNote
11+
{
12+
public Rational Time;
13+
public string Note;
14+
public bool IsBpm;
15+
16+
public SimaiNote(Rational time, string note, bool isBpm = false)
17+
{
18+
Time = time;
19+
Note = note;
20+
IsBpm = isBpm;
21+
}
22+
};
23+
24+
public class SimaiGenerator : IGenerator
25+
{
26+
private readonly List<Alert> alerts = [];
27+
private StringBuilder result = new StringBuilder();
28+
#pragma warning disable CS8618
29+
private Chart chart;
30+
#pragma warning restore CS8618
31+
32+
private List<SimaiNote> buf = [];
33+
private int bpmIdx = 0; // 当前遍历到了哪个bpm
34+
35+
private BigInteger bufBarWholepart = 0; // 以下两个用于控制分小节写入
36+
private bool lastWriteEmpty = false;
37+
private Dictionary<Slide, SimaiNote> sharedHeadBuf = new(); // 以下两个用于控制星星头的缓存,确保同头星星可以直接被连接到正确的simai语句上
38+
private Rational sharedHeadBufTime = 0;
39+
40+
private void flush()
41+
{
42+
throw new NotImplementedException();
43+
}
44+
45+
private string DurationStr(Note note)
46+
{
47+
var ib = note.Duration.InvariantBar;
48+
// 当发生以下两种情况之一时,返回bar格式原值;否则返回秒
49+
// 1. duration全程bpm没有变化
50+
// 2. 算出来的InvariantBar,分母小于等于16分音
51+
if (ib.Denominator <= 16 || !chart.BpmList.IsBpmChanged(note.Time, note.Time + ib))
52+
{
53+
return $"[{ib.Denominator}:{ib.Numerator}]";
54+
}
55+
else
56+
{ // 返回绝对时间
57+
return $"[#{(decimal)note.Duration.Seconds:0.###}]";
58+
}
59+
}
60+
61+
public (string, List<Alert>) Generate(Chart _chart)
62+
{
63+
if (chart != null) throw new Exception(Locale.InstanceMultipleUsage);
64+
chart = _chart;
65+
66+
int noteIdx = 0;
67+
while (noteIdx < chart.Notes.Count)
68+
{
69+
var note = chart.Notes[noteIdx];
70+
var time = note.Time;
71+
72+
// 先看是否引发bpm change,如果是的话,则本次循环只结算这个bpm change
73+
BPM? bpmChange = null;
74+
if (bpmIdx < chart.BpmList.Count && time >= chart.BpmList[bpmIdx].Time)
75+
{
76+
bpmChange = chart.BpmList[bpmIdx];
77+
time = chart.BpmList[bpmIdx].Time;
78+
}
79+
80+
// 基于时间的缓存清理
81+
while (time.WholePart > bufBarWholepart)
82+
{ // 如果进入了新的小节,则写入上一小节缓存下来的数据
83+
flush();
84+
bufBarWholepart++;
85+
}
86+
if (time != sharedHeadBufTime)
87+
{ // 清空sharedHeadBuf
88+
sharedHeadBuf.Clear();
89+
sharedHeadBufTime = time;
90+
}
91+
92+
if (bpmChange != null)
93+
{ // 本次循环只结算这个bpm change
94+
buf.Add(new SimaiNote(bpmChange.Time, $"({bpmChange.Bpm})", true));
95+
continue;
96+
}
97+
98+
string res;
99+
if (note is Hold hold)
100+
{
101+
res = $"{hold.Key}h{hold.Modifiers}{DurationStr(hold)}";
102+
}
103+
else if (note is Tap tap)
104+
{
105+
var starModifier = tap is Star ? "$" : "";
106+
res = $"{tap.Key}{starModifier}{tap.Modifiers}";
107+
}
108+
else if (note is TouchHold th)
109+
{
110+
res = $"{th.TouchArea}h{th.Modifiers}{DurationStr(th)}";
111+
}
112+
else if (note is Touch touch)
113+
{
114+
res = $"{touch.TouchArea}{touch.Modifiers}";
115+
}
116+
else if (note is Slide slide)
117+
{
118+
// 处理头
119+
if (slide.SharedHeadWith != null) res = "*";
120+
else if (slide.OwnHead != null)
121+
{
122+
var starModifier = slide.OwnHead is not Star ? "@" : "";
123+
res = $"{slide.Key}{starModifier}{slide.OwnHead.Modifiers}";
124+
}
125+
else res = $"{slide.Key}?";
126+
127+
bool durationOccured = false;
128+
foreach (var seg in slide.segments)
129+
{
130+
res += seg.Type.ToSimai(seg.StartKey);
131+
res += seg.EndKey;
132+
133+
if (seg.Duration != null)
134+
{
135+
durationOccured = true;
136+
}
137+
else if (durationOccured)
138+
{ // 如果此前有某段出现过时间,说明是分段时间的写法。但我自己却没有时间,说明是非法的写法
139+
alerts.Add(new Alert(Error, Locale.InvalidSlide, (chart, time), null, res));
140+
throw new ConversionException(alerts);
141+
}
142+
}
143+
144+
if (slide.SharedHeadWith != null)
145+
{
146+
if (!sharedHeadBuf.TryGetValue(slide.SharedHeadWithRoot, out var src))
147+
{ // 没找到前一段星星对应的字符串
148+
alerts.Add(new Alert(Error, Locale.MA2CNSlideNoPrevious, (chart, time), null, res));
149+
throw new ConversionException(alerts);
150+
}
151+
Utils.Assert(src.Time == time);
152+
src.Note += res;
153+
res = "";
154+
}
155+
else
156+
{
157+
var simaiNote = new SimaiNote(time, res);
158+
buf.Add(simaiNote);
159+
res = ""; // 我自己加进simaiNote里去,循环外面的公共逻辑就不要加了
160+
sharedHeadBuf[slide] = simaiNote;
161+
}
162+
}
163+
else throw Utils.Fail("SimaiGenerator遇到了未知的Note对象");
164+
165+
if (!string.IsNullOrEmpty(res)) buf.Add(new SimaiNote(time, res));
166+
noteIdx++;
167+
}
168+
169+
return ("", alerts);
170+
}
171+
}

0 commit comments

Comments
 (0)