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