@@ -69,8 +69,8 @@ private string Serialize(ChuChart chart, List<Alert> alerts)
6969 List < string > resultLines = [ ] ;
7070 foreach ( var n in chart . Notes )
7171 {
72- var line = FormatNote ( n , alerts ) ;
73- if ( line != null ) resultLines . Add ( line ) ;
72+ var lines = FormatNote ( n , alerts ) ;
73+ resultLines . AddRange ( lines ) ;
7474 }
7575
7676 foreach ( var line in resultLines . Concat ( slaLines ) . OrderBy ( _lineOrder ) )
@@ -87,82 +87,103 @@ private string Serialize(ChuChart chart, List<Alert> alerts)
8787 }
8888 }
8989
90- private static string AirColorTag ( ChuNote n , List < Alert > alerts )
90+ private List < string > FormatNote ( ChuNote n , List < Alert > alerts )
9191 {
92- if ( C2sAllowedColors . Contains ( n . Tag ) ) return n . Tag ;
93- else
92+ var ( m , o ) = Utils . BarAndTick ( n . Time , RSL ) ;
93+ List < List < string > > results = [ ] ;
94+
95+ if ( n . Type is ChuNoteType . Tap or ChuNoteType . Flick or ChuNoteType . Mine )
9496 {
95- if ( n . Tag != "" ) alerts . Add ( new Alert ( Alert . LEVEL . Warning , string . Format ( Locale . C2SUnsupportedAirColor , "C2S Generator" , n . Type , n . Tag ) , n . Time ) ) ;
96- return "DEF" ;
97+ var name = n . Type switch
98+ {
99+ ChuNoteType . Tap => n . IsAir ? n . AirDirection . ToString ( )
100+ : ( n . IsEx ? "CHR" : "TAP" ) ,
101+ ChuNoteType . Flick => "FLK" ,
102+ ChuNoteType . Mine => "MNE" ,
103+ _ => throw Utils . Fail ( ) ,
104+ } ;
105+ List < string > r = [ name , m . ToString ( ) , o . ToString ( ) , n . Cell . ToString ( ) , n . Width . ToString ( ) ] ;
106+
107+ if ( n . Type == ChuNoteType . Tap && n . IsEx ) r . Add ( ExDirections_ToUgc [ n . Ex ! . Value ] ) ; // CHR
108+ else if ( n . Type == ChuNoteType . Flick ) r . Add ( n . Ex == ExDirection . RS ? "R" : "L" ) ; // FLK
109+ else if ( n . IsAir )
110+ { // AIR
111+ var targetStr = AsC2sPreviousStr ( n . TargetNote ) ;
112+ Utils . Assert ( targetStr != null , "MuConvert内部错误:Air音符的TargetNote出现了不合法的类型!" ) ;
113+ r . AddRange ( targetStr ! , AirColor_ToUgc ( n ) ) ;
114+ }
115+
116+ results . Add ( r ) ;
117+ if ( n . SpeedGroup != 0 ) // 分音符变速组 SLA 的处理
118+ results . Add ( [ "SLA" , ..r [ 1 ..5 ] , "1" , n . SpeedGroup . ToString ( ) ] ) ;
97119 }
98- }
99- private static string AirCrushColorTag ( ChuNote n , List < Alert > alerts )
100- {
101- if ( C2sAllowedCrushColors . Contains ( n . Tag ) ) return n . Tag ;
102120 else
103121 {
104- if ( n . Tag != "" ) alerts . Add ( new Alert ( Alert . LEVEL . Warning , string . Format ( Locale . C2SUnsupportedAirColor , "C2S Generator" , n . Type , n . Tag ) , n . Time ) ) ;
105- return "DEF" ;
106- }
107- }
108-
109- private static string FLKTag ( ChuNote n ) => n . Tag is "L" or "R" ? n . Tag : "L" ;
110-
111- private string ? FormatNote ( ChuNote n , List < Alert > alerts )
112- {
113- var ( m , o ) = Utils . BarAndTick ( n . Time , RSL ) ;
114- var durTicks = Utils . Tick ( n . Duration , RSL ) ;
115- if ( IsChainContinueSegments ( n ) )
116- { // 特殊地,对于slide的后续段:为了保证能接上,必须保证start tick接在前一个音符的endTime的后面,duration也采用end-start的方式计算durTicks。否则可能会,因为舍入的误差,造成没有办法接起来。
117- ( m , o ) = Utils . BarAndTick ( n . Previous ! . EndTime , RSL ) ;
118- durTicks = Utils . Tick ( n . EndTime , RSL ) - Utils . Tick ( n . Previous ! . EndTime , RSL ) ;
119- }
120- var result = n . Type switch
121- {
122- "TAP" or "CHR" => $ "{ n . Type } \t { m } \t { o } \t { n . Cell } \t { n . Width } ",
123- "HLD" or "HXD" => $ "{ n . Type } \t { m } \t { o } \t { n . Cell } \t { n . Width } \t { durTicks } ",
124- "SLD" or "SLC" or "SXD" or "SXC" => $ "{ n . Type } \t { m } \t { o } \t { n . Cell } \t { n . Width } \t { durTicks } \t { n . EndCell } \t { n . EndWidth } \t SLD",
125- "FLK" => $ "FLK\t { m } \t { o } \t { n . Cell } \t { n . Width } \t { FLKTag ( n ) } ",
126- "AIR" or "AUR" or "AUL" or "ADW" or "ADR" or "ADL" =>
127- $ "{ n . Type } \t { m } \t { o } \t { n . Cell } \t { n . Width } \t { n . TargetNote } \t { AirColorTag ( n , alerts ) } ",
128- "AHD" or "AHX" => $ "{ n . Type } \t { m } \t { o } \t { n . Cell } \t { n . Width } \t { n . TargetNote } \t { durTicks } \t { AirColorTag ( n , alerts ) } ",
129- "ASD" or "ASC" => FormatAsdAsc ( n , m , o , durTicks , alerts ) ,
130- "ALD" => FormatAld ( n , m , o , durTicks , alerts ) ,
131- "MNE" => $ "MNE\t { m } \t { o } \t { n . Cell } \t { n . Width } ",
132- _ => alert ( ) ,
133- } ;
134- if ( result == null ) return null ;
135- if ( n . Type is "CHR" or "HXD" or "SXD" or "SXC" ) result += $ "\t { n . Tag } ";
136-
137- #region 分音符变速组 SLA 的处理
138- if ( n . SpeedGroup != 0 )
139- {
140- slaLines . Add ( $ "SLA\t { m } \t { o } \t { n . Cell } \t { n . Width } \t 1\t { n . SpeedGroup } ") ;
141- if ( durTicks > 0 )
122+ var name = ( n . Type , n . IsAir ) switch
123+ {
124+ ( ChuNoteType . Hold , true ) => n . IsEx ? "AHX" : "AHD" ,
125+ ( ChuNoteType . Hold , false ) => n . IsEx ? "HXD" : "HLD" ,
126+ ( ChuNoteType . Slide , true ) => "ASD" ,
127+ ( ChuNoteType . Slide , false ) => n . IsEx ? "SXD" : "SLD" ,
128+ ( ChuNoteType . Crush , true ) => "ALD" ,
129+ _ => throw Utils . Fail ( ) ,
130+ } ;
131+
132+ var start = ( n . Time , n . Cell , n . Width , n . Height ) ;
133+ foreach ( var seg in n . Segments )
142134 {
143- var ( endM , endO ) = Utils . BarAndTick ( n . EndTime , RSL ) ;
144- var ( endC , endW ) = IsSlide ( n ) || IsAirSlide ( n ) || IsAirCrush ( n ) ? ( n . EndCell , n . EndWidth ) : ( n . Cell , n . Width ) ;
145- slaLines . Add ( $ "SLA\t { endM } \t { endO } \t { endC } \t { endW } \t 1\t { n . SpeedGroup } ") ;
135+ // 必备的:name bar tick cell width
136+ var ( sB , sT ) = Utils . BarAndTick ( start . Time , RSL ) ;
137+ List < string > r = [ name , sB . ToString ( ) , sT . ToString ( ) , start . Cell . ToString ( ) , start . Width . ToString ( ) ] ;
138+
139+ // 前驱targetNote 或 crushInterval
140+ if ( NeedsTargetNote ( n ) )
141+ {
142+ var targetStr = AsC2sPreviousStr ( n . TargetNote ) ;
143+ Utils . Assert ( targetStr != null , "MuConvert内部错误:Air音符的TargetNote出现了不合法的类型!" ) ;
144+ r . Add ( targetStr ! ) ;
145+ }
146+ else if ( n . Type == ChuNoteType . Crush )
147+ r . Add ( Utils . Tick ( n . CrushInterval ?? 100 , RSL ) . ToString ( ) ) ;
148+
149+ // Air且不是Air-Hold的情况,这里要加上height
150+ if ( n . IsAir && n . Type != ChuNoteType . Hold )
151+ r . Add ( $ "{ n . Height : F1} ") ;
152+
153+ // 持续时间
154+ // 为了保持两段之间紧密相接,durTicks必须通过end和start的tick直接作差得到,不能把seg.Length直接转tick,
155+ // 不然可能会由于舍入误差,导致差1tick接不上等情况。
156+ var endTime = start . Time + seg . Length ;
157+ var durTicks = Utils . Tick ( endTime , RSL ) - Utils . Tick ( start . Time , RSL ) ;
158+ r . Add ( durTicks . ToString ( ) ) ;
159+
160+ // 结束的 cell width [height]
161+ if ( n . Type is ChuNoteType . Slide or ChuNoteType . Crush )
162+ {
163+ r . AddRange ( [ seg . EndCell . ToString ( ) , seg . EndWidth . ToString ( ) ] ) ;
164+ if ( n . IsAir ) r . Add ( $ "{ seg . EndHeight : F1} ") ;
165+ }
166+
167+ // 颜色
168+ if ( n . IsAir ) r . Add ( n . Color . ToString ( ) ) ;
169+ else if ( n . Type == ChuNoteType . Slide ) r . Add ( "SLD" ) ; // C2S 1.13版本以上,Slide在最后,还需要加一个”SLD“后缀
170+
171+ // Ex音符的话,要加上Ex方向
172+ if ( n . IsEx ) r . Add ( ExDirections_ToUgc [ n . Ex ! . Value ] ) ;
173+
174+ results . Add ( r ) ;
175+ start = ( endTime , seg . EndCell , seg . EndWidth , seg . EndHeight ) ;
176+ if ( n . SpeedGroup != 0 ) // 分音符变速组 SLA 的处理
177+ results . Add ( [ "SLA" , ..r [ 1 ..5 ] , "1" , n . SpeedGroup . ToString ( ) ] ) ;
178+ }
179+ if ( n . SpeedGroup != 0 )
180+ { // 分音符变速组 SLA 的处理:参照PenguinTools的实现,要把最后一段的结束点,也加到SLA上
181+ var ( sB , sT ) = Utils . BarAndTick ( start . Time , RSL ) ; // 这里的start.Time,其实是最后一段的endTime
182+ List < string > sParts = [ sB . ToString ( ) , sT . ToString ( ) , start . Cell . ToString ( ) , start . Width . ToString ( ) ] ; // start.Cell/Width,实际上也是最后一段的endCell/endWidth
183+ results . Add ( [ "SLA" , ..sParts , "1" , n . SpeedGroup . ToString ( ) ] ) ;
146184 }
147185 }
148- #endregion
149186
150- return result ;
151-
152- string ? alert ( )
153- {
154- alerts . Add ( new Alert ( Alert . LEVEL . Warning , Locale . C2SUnknownNoteType , n . Time ) ) ;
155- return null ;
156- }
157- }
158-
159- private static string FormatAsdAsc ( ChuNote n , int m , int o , int durTicks , List < Alert > alerts )
160- {
161- return FormattableString . Invariant ( $ "{ n . Type } \t { m } \t { o } \t { n . Cell } \t { n . Width } \t { n . TargetNote } \t { n . Height : F1} \t { durTicks } \t { n . EndCell } \t { n . EndWidth } \t { n . EndHeight : F1} \t { AirColorTag ( n , alerts ) } ") ;
162- }
163-
164- private static string FormatAld ( ChuNote n , int m , int o , int durTicks , List < Alert > alerts )
165- {
166- return FormattableString . Invariant ( $ "ALD\t { m } \t { o } \t { n . Cell } \t { n . Width } \t { Utils . Tick ( n . CrushInterval , RSL ) } \t { n . Height : F1} \t { durTicks } \t { n . EndCell } \t { n . EndWidth } \t { n . EndHeight : F1} \t { AirCrushColorTag ( n , alerts ) } ") ;
187+ return results . Select ( x=> string . Join ( '\t ' , x ) ) . ToList ( ) ;
167188 }
168189}
0 commit comments