├── .gitattributes
├── .gitignore
├── .idea
└── .idea.MaiLib
│ └── .idea
│ ├── .gitignore
│ ├── encodings.xml
│ ├── indexLayout.xml
│ └── vcs.xml
├── .vscode
└── settings.json
├── Chart
├── Chart.cs
├── IChart.cs
├── Ma2.cs
├── Simai.cs
└── XMaiL.cs
├── ChartDefinitions
├── BPMChanges.cs
├── IXmlUtility.cs
├── MeasureChanges.cs
├── TrackInformation.cs
└── XmlInformation.cs
├── ChartPack.cs
├── Compiler
├── Compiler.cs
├── ICompiler.cs
└── SimaiCompiler.cs
├── Enums
├── ChartEnum.cs
├── NoteEnum.cs
├── SimaiFormalSpec.txt
└── TokenEnum.cs
├── LICENSE.txt
├── MaiLib.csproj
├── MaiLib.sln
├── Note
├── BPMChange.cs
├── Hold.cs
├── INote.cs
├── MeasureChange.cs
├── Note.cs
├── Rest.cs
├── Slide.cs
├── SlideEachSet.cs
├── SlideGroup.cs
└── Tap.cs
├── Parser
├── CodeBlocks
│ ├── BPM.cs
│ ├── BeginSeq.cs
│ ├── ChartDef.cs
│ ├── HoldComp.cs
│ ├── HoldDuration.cs
│ ├── ICodeBlock.cs
│ ├── Key.cs
│ ├── KeyComp.cs
│ ├── Measure.cs
│ ├── MeasureDuration.cs
│ ├── NormalNote.cs
│ ├── NoteComp.cs
│ ├── NoteSeq.cs
│ ├── Sensor.cs
│ ├── SingleNote.cs
│ ├── SlideComp.cs
│ ├── SlideConnectedComp.cs
│ ├── SlideConnectedMeasuredSeq.cs
│ ├── SlideConnectedSeq.cs
│ ├── SlideDuration.cs
│ ├── SlideGroupComp.cs
│ ├── SlideSeq.cs
│ ├── SlideSet.cs
│ ├── SlideTimeDuration.cs
│ ├── SlideType.cs
│ ├── StartPostfix.cs
│ ├── TapComp.cs
│ ├── TimeDuration.cs
│ └── TouchNote.cs
├── IParser.cs
├── Ma2parser.cs
├── SimaiParserR.cs
├── SimaiScanner.cs
└── Simaiparser.cs
├── README.md
├── Tokenizer
├── ITokenizer.cs
├── Ma2Tokenizer.cs
└── SimaiTokenizer.cs
└── XMaiLTemplete.xml
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Oo]ut/
33 | [Ll]og/
34 | [Ll]ogs/
35 |
36 | # Visual Studio 2015/2017 cache/options directory
37 | .vs/
38 | # Uncomment if you have tasks that create the project's static files in wwwroot
39 | #wwwroot/
40 |
41 | # Visual Studio 2017 auto generated files
42 | Generated\ Files/
43 |
44 | # MSTest test Results
45 | [Tt]est[Rr]esult*/
46 | [Bb]uild[Ll]og.*
47 |
48 | # NUnit
49 | *.VisualState.xml
50 | TestResult.xml
51 | nunit-*.xml
52 |
53 | # Build Results of an ATL Project
54 | [Dd]ebugPS/
55 | [Rr]eleasePS/
56 | dlldata.c
57 |
58 | # Benchmark Results
59 | BenchmarkDotNet.Artifacts/
60 |
61 | # .NET Core
62 | project.lock.json
63 | project.fragment.lock.json
64 | artifacts/
65 |
66 | # ASP.NET Scaffolding
67 | ScaffoldingReadMe.txt
68 |
69 | # StyleCop
70 | StyleCopReport.xml
71 |
72 | # Files built by Visual Studio
73 | *_i.c
74 | *_p.c
75 | *_h.h
76 | *.ilk
77 | *.meta
78 | *.obj
79 | *.iobj
80 | *.pch
81 | *.pdb
82 | *.ipdb
83 | *.pgc
84 | *.pgd
85 | *.rsp
86 | *.sbr
87 | *.tlb
88 | *.tli
89 | *.tlh
90 | *.tmp
91 | *.tmp_proj
92 | *_wpftmp.csproj
93 | *.log
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio LightSwitch build output
298 | **/*.HTMLClient/GeneratedArtifacts
299 | **/*.DesktopClient/GeneratedArtifacts
300 | **/*.DesktopClient/ModelManifest.xml
301 | **/*.Server/GeneratedArtifacts
302 | **/*.Server/ModelManifest.xml
303 | _Pvt_Extensions
304 |
305 | # Paket dependency manager
306 | .paket/paket.exe
307 | paket-files/
308 |
309 | # FAKE - F# Make
310 | .fake/
311 |
312 | # CodeRush personal settings
313 | .cr/personal
314 |
315 | # Python Tools for Visual Studio (PTVS)
316 | __pycache__/
317 | *.pyc
318 |
319 | # Cake - Uncomment if you are using it
320 | # tools/**
321 | # !tools/packages.config
322 |
323 | # Tabs Studio
324 | *.tss
325 |
326 | # Telerik's JustMock configuration file
327 | *.jmconfig
328 |
329 | # BizTalk build output
330 | *.btp.cs
331 | *.btm.cs
332 | *.odx.cs
333 | *.xsd.cs
334 |
335 | # OpenCover UI analysis results
336 | OpenCover/
337 |
338 | # Azure Stream Analytics local run output
339 | ASALocalRun/
340 |
341 | # MSBuild Binary and Structured Log
342 | *.binlog
343 |
344 | # NVidia Nsight GPU debugger configuration file
345 | *.nvuser
346 |
347 | # MFractors (Xamarin productivity tool) working folder
348 | .mfractor/
349 |
350 | # Local History for Visual Studio
351 | .localhistory/
352 |
353 | # BeatPulse healthcheck temp database
354 | healthchecksdb
355 |
356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
357 | MigrationBackup/
358 |
359 | # Ionide (cross platform F# VS Code tools) working folder
360 | .ionide/
361 |
362 | # Fody - auto-generated XML schema
363 | FodyWeavers.xsd
--------------------------------------------------------------------------------
/.idea/.idea.MaiLib/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Rider ignored files
5 | /contentModel.xml
6 | /projectSettingsUpdater.xml
7 | /.idea.MaiLib.iml
8 | /modules.xml
9 | # Editor-based HTTP Client requests
10 | /httpRequests/
11 | # Datasource local storage ignored files
12 | /dataSources/
13 | /dataSources.local.xml
14 |
--------------------------------------------------------------------------------
/.idea/.idea.MaiLib/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/.idea.MaiLib/.idea/indexLayout.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/.idea.MaiLib/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "chartconverttool",
4 | "chartconverttoolversion",
5 | "cref",
6 | "maimai",
7 | "smsg"
8 | ]
9 | }
--------------------------------------------------------------------------------
/Chart/IChart.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | ///
4 | /// Provide interface for charts
5 | ///
6 | internal interface IChart
7 | {
8 | ///
9 | /// Updates all information
10 | ///
11 | void Update();
12 |
13 | ///
14 | /// Check if this chart is valid
15 | ///
16 | ///
17 | bool CheckValidity();
18 |
19 | ///
20 | /// Export this Chart
21 | ///
22 | /// String representation of this chart
23 | string Compose();
24 |
25 | ///
26 | /// Temporarily export this chart into given format
27 | ///
28 | /// Assigned chart format
29 | /// String representation of this chart in assigned format
30 | string Compose(ChartEnum.ChartVersion chartVersion);
31 |
32 | ///
33 | /// Shift the chart notes by defined overall tick
34 | ///
35 | /// Tick to add for offset
36 | void ShiftByOffset(int overallTick);
37 |
38 | ///
39 | /// Shift the chart notes by defined overall tick
40 | ///
41 | /// Bar to add for offset
42 | /// Tick to add for offset
43 | void ShiftByOffset(int bar, int tick);
44 |
45 | ///
46 | /// Rotate the notes by specified method.
47 | ///
48 | /// Clockwise90, Clockwise 180, Counterclockwise90, Counterclockwise 180, UpSideDown, LeftToRight
49 | void RotateNotes(NoteEnum.FlipMethod method);
50 |
51 | ///
52 | /// Get appropriate time stamp of given tick
53 | ///
54 | /// Time stamp of bar and note
55 | /// this.bpmChanges!=null
56 | double GetTimeStamp(int bar, int tick);
57 |
58 | ///
59 | /// Extracts the special slide containers created by Simai
60 | ///
61 | /// If slide container is casted wrongly, this exception will be raised
62 | void ExtractSlideEachGroup();
63 |
64 | ///
65 | /// Composes slides into slide groups to deal with connected slides
66 | ///
67 | /// Returns exceptions when a note cannot be casted into slide group
68 | /// Returns exceptions when missing slides after composing
69 | void ComposeSlideGroup();
70 |
71 | ///
72 | /// Composes slides into groups when they have same start time
73 | ///
74 | /// Returns exceptions when note cannot be casted into required types
75 | void ComposeSlideEachGroup();
76 | }
--------------------------------------------------------------------------------
/Chart/Ma2.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | using static ChartEnum;
4 | using System.Text;
5 |
6 | ///
7 | /// Implementation of chart in ma2 format.
8 | ///
9 | public class Ma2 : Chart, ICompiler
10 | {
11 | #region Constructors
12 |
13 | ///
14 | /// Default Constructor.
15 | ///
16 | public Ma2()
17 | {
18 | ChartType = ChartType.Standard;
19 | ChartVersion = ChartVersion.Ma2_103;
20 | }
21 |
22 | ///
23 | /// Construct Ma2 with given notes, bpm change definitions and measure change definitions.
24 | ///
25 | /// Notes in Ma2
26 | /// BPM Changes: Initial BPM is NEEDED!
27 | /// Measure Changes: Initial Measure is NEEDED!
28 | public Ma2(List notes, BPMChanges bpmChanges, MeasureChanges measureChanges)
29 | {
30 | Notes = new List(notes);
31 | BPMChanges = new BPMChanges(bpmChanges);
32 | MeasureChanges = new MeasureChanges(measureChanges);
33 | ChartType = ChartType.Standard;
34 | ChartVersion = ChartVersion.Ma2_103;
35 | this.Update();
36 | }
37 |
38 | ///
39 | /// Construct GoodBrother from location specified
40 | ///
41 | /// MA2 location
42 | public Ma2(string location)
43 | {
44 | string[]? tokens = new Ma2Tokenizer().Tokens(location);
45 | Chart? takenIn = new Ma2Parser().ChartOfToken(tokens);
46 | Notes = new List(takenIn.Notes);
47 | BPMChanges = new BPMChanges(takenIn.BPMChanges);
48 | MeasureChanges = new MeasureChanges(takenIn.MeasureChanges);
49 | StoredChart = new List>(takenIn.StoredChart);
50 | Information = new Dictionary(takenIn.Information);
51 | ChartType = ChartType.Standard;
52 | ChartVersion = ChartVersion.Ma2_103;
53 | this.Update();
54 | }
55 |
56 | ///
57 | /// Construct Ma2 with tokens given
58 | ///
59 | /// Tokens given
60 | public Ma2(string[] tokens)
61 | {
62 | Chart? takenIn = new Ma2Parser().ChartOfToken(tokens);
63 | Notes = takenIn.Notes;
64 | BPMChanges = takenIn.BPMChanges;
65 | MeasureChanges = takenIn.MeasureChanges;
66 | StoredChart = new List>(takenIn.StoredChart);
67 | Information = new Dictionary(takenIn.Information);
68 | ChartType = ChartType.Standard;
69 | ChartVersion = ChartVersion.Ma2_103;
70 | this.Update();
71 | }
72 |
73 | ///
74 | /// Construct Ma2 with existing values
75 | ///
76 | /// Existing good brother
77 | public Ma2(Chart takenIn)
78 | {
79 | Notes = new List(takenIn.Notes);
80 | BPMChanges = new BPMChanges(takenIn.BPMChanges);
81 | MeasureChanges = new MeasureChanges(takenIn.MeasureChanges);
82 | StoredChart = new List>(takenIn.StoredChart);
83 | Information = new Dictionary(takenIn.Information);
84 | ChartType = ChartType.Standard;
85 | ChartVersion = ChartVersion.Ma2_103;
86 | this.Update();
87 | }
88 |
89 | #endregion
90 |
91 | public override bool CheckValidity()
92 | {
93 | bool result = this is null;
94 | // Not yet implemented
95 | return result;
96 | }
97 |
98 | public string GenerateNoteStatistics()
99 | {
100 | StringBuilder builder = new();
101 | builder.Append($"T_REC_TAP\t{NormalTapNum}\n");
102 | builder.Append($"T_REC_BRK\t{BreakTapNum}\n");
103 | builder.Append($"T_REC_XTP\t{ExTapNum}\n");
104 | if (ChartVersion is ChartVersion.Ma2_104) builder.Append($"T_REC_BXX\t{BreakExTapNum}\n");
105 | builder.Append($"T_REC_HLD\t{NormalHoldNum}\n");
106 | builder.Append($"T_REC_XHO\t{ExHoldNum}\n");
107 | if (ChartVersion is ChartVersion.Ma2_104)
108 | {
109 | builder.Append($"T_REC_BHO\t{BreakHoldNum}\n");
110 | builder.Append($"T_REC_BXH\t{BreakExHoldNum}\n");
111 | }
112 |
113 | builder.Append($"T_REC_STR\t{NormalSlideStartNum}\n");
114 | builder.Append($"T_REC_BST\t{BreakSlideStartNum}\n");
115 | builder.Append($"T_REC_XST\t{ExSlideStartNum}\n");
116 | if (ChartVersion is ChartVersion.Ma2_104) builder.Append($"T_REC_XBS\t{BreakExSlideStartNum}\n");
117 | builder.Append($"T_REC_TTP\t{TouchTapNum}\n");
118 | builder.Append($"T_REC_THO\t{TouchHoldNum}\n");
119 | builder.Append($"T_REC_SLD\t{NormalSlideNum}\n");
120 | if (ChartVersion is ChartVersion.Ma2_104) builder.Append($"T_REC_BSL\t{BreakSlideNum}\n");
121 | builder.Append($"T_REC_ALL\t{AllNoteRecNum}\n");
122 |
123 | builder.Append($"T_NUM_TAP\t{TapNum}\n");
124 | builder.Append($"T_NUM_BRK\t{BreakNum}\n");
125 | builder.Append($"T_NUM_HLD\t{HoldNum}\n");
126 | builder.Append($"T_NUM_SLD\t{SlideNum}\n");
127 | builder.Append($"T_NUM_ALL\t{AllNoteNum}\n");
128 |
129 | builder.Append($"T_JUDGE_TAP\t{TapJudgeNum}\n");
130 | builder.Append($"T_JUDGE_HLD\t{HoldJudgeNum}\n");
131 | builder.Append($"T_JUDGE_SLD\t{SlideJudgeNum}\n");
132 | builder.Append($"T_JUDGE_ALL\t{AllJudgeNum}\n");
133 |
134 | builder.Append($"TTM_EACHPAIRS\t{EachPairsNum}\n");
135 | builder.Append($"TTM_SCR_TAP\t{TapScore}\n");
136 | builder.Append($"TTM_SCR_BRK\t{BreakScore}\n");
137 | builder.Append($"TTM_SCR_HLD\t{HoldScore}\n");
138 | builder.Append($"TTM_SCR_SLD\t{SlideScore}\n");
139 | builder.Append($"TTM_SCR_ALL\t{AllScore}\n");
140 | builder.Append($"TTM_SCR_S\t{ScoreS}\n");
141 | builder.Append($"TTM_SCR_SS\t{ScoreSs}\n");
142 | builder.Append($"TTM_RAT_ACV\t{RatedAchievement}\n");
143 | return builder.ToString();
144 | }
145 |
146 | public override string Compose()
147 | {
148 | Update();
149 | switch (ChartVersion)
150 | {
151 | case ChartVersion.Ma2_103:
152 | case ChartVersion.Ma2_104:
153 | base.Update();
154 | StringBuilder result = new StringBuilder();
155 | string targetVersion;
156 | switch (ChartVersion)
157 | {
158 | case ChartVersion.Ma2_103:
159 | targetVersion = "1.03.00";
160 | break;
161 | case ChartVersion.Ma2_104:
162 | targetVersion = "1.04.00";
163 | break;
164 | default:
165 | return base.Compose(ChartVersion);
166 | }
167 |
168 | string header1 = $"VERSION\t0.00.00\t{targetVersion}\nFES_MODE\t{(IsUtage ? 1 : 0)}\n";
169 | string header2 = $"RESOLUTION\t{Definition}\nCLK_DEF\t{Definition}\nCOMPATIBLE_CODE\tMA2\n";
170 | result.Append(header1);
171 | result.Append(BPMChanges.InitialChange);
172 | result.Append(MeasureChanges.InitialChange);
173 | result.Append(header2);
174 | result.Append("\n");
175 |
176 | result.Append(BPMChanges.Compose());
177 | result.Append(MeasureChanges.Compose());
178 | result.Append("\n");
179 |
180 | foreach (List? bar in StoredChart)
181 | foreach (Note? x in bar)
182 | if (!x.Compose(ChartVersion).Equals(""))
183 | result.Append(x.Compose(ChartVersion) + "\n");
184 | result.Append("\n");
185 | result.Append(GenerateNoteStatistics());
186 | return result.ToString();
187 | default:
188 | return base.Compose();
189 | }
190 | }
191 |
192 | ///
193 | /// Override and compose with given arrays
194 | ///
195 | /// Override BPM array
196 | /// Override Measure array
197 | /// Good Brother with override array
198 | public override string Compose(BPMChanges bpm, MeasureChanges measure)
199 | {
200 | return new Ma2(Notes, bpm, measure).Compose(ChartVersion);
201 | }
202 |
203 | public override void Update()
204 | {
205 | ExtractSlideEachGroup();
206 | base.Update();
207 | }
208 | }
--------------------------------------------------------------------------------
/Chart/Simai.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | using static MaiLib.NoteEnum;
4 | using static MaiLib.ChartEnum;
5 | using System.Text;
6 |
7 | public class Simai : Chart
8 | {
9 | #region Constructors
10 |
11 | ///
12 | /// Empty constructor
13 | ///
14 | public Simai()
15 | {
16 | ChartType = ChartType.Standard;
17 | ChartVersion = ChartVersion.Simai;
18 | }
19 |
20 | ///
21 | /// Constructs Simai chart directly from path specified
22 | ///
23 | ///
24 | public Simai(string location)
25 | {
26 | string[]? tokens = new SimaiTokenizer().Tokens(location);
27 | Chart? chart = new SimaiParser().ChartOfToken(tokens);
28 | Notes = new List(chart.Notes);
29 | BPMChanges = new BPMChanges(chart.BPMChanges);
30 | MeasureChanges = new MeasureChanges(chart.MeasureChanges);
31 | Information = new Dictionary(chart.Information);
32 | ChartType = ChartType.Standard;
33 | ChartVersion = ChartVersion.Simai;
34 | Update();
35 | }
36 |
37 | ///
38 | /// Construct Simai from given parameters
39 | ///
40 | /// Notes to take in
41 | /// BPM change to take in
42 | /// Measure change to take in
43 | public Simai(List notes, BPMChanges bpmChanges, MeasureChanges measureChanges)
44 | {
45 | Notes = notes;
46 | BPMChanges = bpmChanges;
47 | MeasureChanges = measureChanges;
48 | ChartType = ChartType.Standard;
49 | ChartVersion = ChartVersion.Simai;
50 | this.Update();
51 | }
52 |
53 | public Simai(Chart takenIn)
54 | {
55 | Notes = takenIn.Notes;
56 | BPMChanges = takenIn.BPMChanges;
57 | MeasureChanges = takenIn.MeasureChanges;
58 | ChartType = ChartType.Standard;
59 | ChartVersion = ChartVersion.Simai;
60 | this.Update();
61 | }
62 |
63 | #endregion
64 |
65 | public override string Compose()
66 | {
67 | Update();
68 | switch (ChartVersion)
69 | {
70 | case ChartVersion.Simai:
71 | case ChartVersion.SimaiFes:
72 | StringBuilder result = new StringBuilder();
73 | Note? mostDelayedNote = Notes.MaxBy(note => note.LastTickStamp);
74 | if (mostDelayedNote is not null)
75 | {
76 | TotalDelay = mostDelayedNote.LastTickStamp - StoredChart.Count * Definition;
77 | }
78 |
79 | int delayBar = TotalDelay / Definition + 2;
80 | //Console.WriteLine(chart.Compose());
81 | //foreach (BPMChange x in chart.BPMChanges.ChangeNotes)
82 | //{
83 | // Console.WriteLine("BPM Change verified in " + x.Bar + " " + x.Tick + " of BPM" + x.BPM);
84 | //}
85 | List? firstBpm = [];
86 | foreach (Note? bpm in Notes)
87 | if (bpm.NoteSpecificGenre is NoteSpecificGenre.BPM)
88 | firstBpm.Add(bpm);
89 | // if (firstBpm.Count > 1)
90 | // {
91 | // chart.Chart[0][0] = firstBpm[1];
92 | // }
93 | foreach (List? bar in StoredChart)
94 | {
95 | Note lastNote = new MeasureChange();
96 | int currentQuaver = 0;
97 | int commaCompiled = 0;
98 | //result += bar[1].Bar;
99 | foreach (Note? x in bar)
100 | {
101 | //if (x.Bar == 6)
102 | //{
103 | // Console.WriteLine("This is bar 6");
104 | //}
105 | switch (lastNote.NoteSpecificGenre)
106 | {
107 | case NoteSpecificGenre.MEASURE:
108 | currentQuaver = (lastNote as MeasureChange ??
109 | throw new Exception("This note is not measure change")).Quaver;
110 | break;
111 | case NoteSpecificGenre.BPM:
112 | break;
113 | default:
114 | if (x.IsOfSameTime(lastNote) && x.IsNote && lastNote.IsNote)
115 | {
116 | result.Append("/");
117 | }
118 | else
119 | {
120 | result.Append(",");
121 | commaCompiled++;
122 | }
123 |
124 | break;
125 | }
126 |
127 | result.Append(x.Compose(ChartVersion));
128 | lastNote = x;
129 | }
130 |
131 | result.Append(",\n");
132 | commaCompiled++;
133 | if (commaCompiled != currentQuaver)
134 | {
135 | Console.WriteLine("Notes in bar: " + bar[0].Bar);
136 | foreach (Note? x in bar) Console.WriteLine(x.Compose(ChartVersion.Debug));
137 | Console.WriteLine(result);
138 | Console.WriteLine("Expected comma number: " + currentQuaver);
139 | Console.WriteLine("Actual comma number: " + commaCompiled);
140 | throw new NullReferenceException("COMMA COMPILED MISMATCH IN BAR " + bar[0].Bar);
141 | }
142 | }
143 |
144 | for (int i = 0; i < delayBar + 1; i++) result.Append("{1},\n");
145 | result.Append("E\n");
146 | return result.ToString();
147 | default:
148 | return base.Compose();
149 | }
150 | }
151 |
152 | public override bool CheckValidity()
153 | {
154 | bool result = this == null;
155 | // Not yet implemented
156 | return result;
157 | }
158 |
159 | ///
160 | /// Reconstruct the chart with given arrays
161 | ///
162 | /// New BPM Changes
163 | /// New Measure Changes
164 | /// New Composed Chart
165 | public override string Compose(BPMChanges bpm, MeasureChanges measure)
166 | {
167 | BPMChanges? sourceBPM = BPMChanges;
168 | MeasureChanges? sourceMeasures = MeasureChanges;
169 | BPMChanges = bpm;
170 | MeasureChanges = measure;
171 | Update();
172 |
173 | string? result = Compose();
174 | BPMChanges = sourceBPM;
175 | MeasureChanges = sourceMeasures;
176 | Update();
177 | return result;
178 | }
179 |
180 | public override void Update()
181 | {
182 | ComposeSlideGroup();
183 | ComposeSlideEachGroup();
184 | base.Update();
185 | }
186 | }
--------------------------------------------------------------------------------
/Chart/XMaiL.cs:
--------------------------------------------------------------------------------
1 | using System.Xml;
2 |
3 | namespace MaiLib;
4 |
5 | ///
6 | /// Using xml to store maicharts
7 | ///
8 | public class XMaiL : Chart, ICompiler
9 | {
10 | ///
11 | /// Storage of Xml file
12 | ///
13 | private readonly XmlDocument StoredXMailL;
14 |
15 | #region Constructors
16 |
17 | ///
18 | /// Default constructor
19 | ///
20 | public XMaiL()
21 | {
22 | Notes = [];
23 | BPMChanges = new BPMChanges();
24 | MeasureChanges = new MeasureChanges();
25 | StoredChart = [];
26 | Information = [];
27 | StoredXMailL = new XmlDocument();
28 | Update();
29 | }
30 |
31 | ///
32 | /// Construct XMaiL with given notes, bpm change definitions and measure change definitions.
33 | ///
34 | /// Notes in XMaiL
35 | /// BPM Changes: Initial BPM is NEEDED!
36 | /// Measure Changes: Initial Measure is NEEDED!
37 | public XMaiL(List notes, BPMChanges bpmChanges, MeasureChanges measureChanges)
38 | {
39 | Notes = notes;
40 | BPMChanges = bpmChanges;
41 | MeasureChanges = measureChanges;
42 | StoredChart = [];
43 | Information = [];
44 | StoredXMailL = new XmlDocument();
45 | Update();
46 | }
47 |
48 | ///
49 | /// Construct XMaiL with tokens given
50 | ///
51 | /// Tokens given
52 | public XMaiL(string[] tokens)
53 | {
54 | Chart? takenIn = new Ma2Parser().ChartOfToken(tokens);
55 | Notes = takenIn.Notes;
56 | BPMChanges = takenIn.BPMChanges;
57 | MeasureChanges = takenIn.MeasureChanges;
58 | StoredChart = [];
59 | Information = [];
60 | StoredXMailL = new XmlDocument();
61 | Update();
62 | }
63 |
64 | ///
65 | /// Construct XMaiL with existing values
66 | ///
67 | /// Existing good brother
68 | public XMaiL(Chart takenIn)
69 | {
70 | Notes = takenIn.Notes;
71 | BPMChanges = takenIn.BPMChanges;
72 | MeasureChanges = takenIn.MeasureChanges;
73 | StoredChart = [];
74 | Information = [];
75 | StoredXMailL = new XmlDocument();
76 | Update();
77 | }
78 |
79 | #endregion
80 |
81 | public override bool CheckValidity()
82 | {
83 | bool result = this == null;
84 | // Not yet implemented
85 | return result;
86 | }
87 |
88 | public override string Compose()
89 | {
90 | return StoredXMailL.ToString() ?? throw new NullReferenceException();
91 | }
92 |
93 | public override string Compose(BPMChanges bpm, MeasureChanges measure)
94 | {
95 | throw new NotImplementedException();
96 | }
97 |
98 | public override void Update()
99 | {
100 | XmlDeclaration? xmlDecl = StoredXMailL.CreateXmlDeclaration("1.0", "UTF-8", null);
101 | StoredXMailL.AppendChild(xmlDecl);
102 | XmlElement? root = StoredXMailL.CreateElement("XMaiL");
103 | XmlAttribute? rootVersion = StoredXMailL.CreateAttribute("1.0");
104 | root.Attributes.Append(rootVersion);
105 | StoredXMailL.AppendChild(root);
106 | XmlElement? information = StoredXMailL.CreateElement("TrackInformation");
107 | }
108 | }
--------------------------------------------------------------------------------
/ChartDefinitions/BPMChanges.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | public class BPMChanges
4 | {
5 | #region Constructors
6 |
7 | ///
8 | /// Construct with changes listed
9 | ///
10 | /// Bar which contains changes
11 | /// Tick in bar contains changes
12 | /// Specified BPM changes
13 | public BPMChanges(List bar, List tick, List bpm)
14 | {
15 | ChangeNotes = [];
16 | for (int i = 0; i < bar.Count; i++)
17 | {
18 | BPMChange candidate = new(bar[i], tick[i], bpm[i]);
19 | ChangeNotes.Add(candidate);
20 | }
21 |
22 | Update();
23 | }
24 |
25 | ///
26 | /// Construct empty BPMChange List
27 | ///
28 | public BPMChanges()
29 | {
30 | ChangeNotes = [];
31 | Update();
32 | }
33 |
34 | ///
35 | /// Construct BPMChanges with existing one
36 | ///
37 | ///
38 | public BPMChanges(BPMChanges takenIn)
39 | {
40 | ChangeNotes = [.. takenIn.ChangeNotes];
41 | }
42 |
43 | #endregion
44 |
45 | public List ChangeNotes { get; private set; }
46 |
47 | ///
48 | /// Returns first definitions
49 | ///
50 | public string InitialChange
51 | {
52 | get
53 | {
54 | if (ChangeNotes.Count > 4)
55 | {
56 | string? result = "BPM_DEF" + "\t";
57 | for (int x = 0; x < 4; x++)
58 | {
59 | result = result + string.Format("{0:F3}", ChangeNotes[x].BPM);
60 | result += "\t";
61 | }
62 |
63 | return result + "\n";
64 | }
65 | else
66 | {
67 | int times = 0;
68 | string? result = "BPM_DEF" + "\t";
69 | foreach (BPMChange? x in ChangeNotes)
70 | {
71 | result += string.Format("{0:F3}", x.BPM);
72 | result += "\t";
73 | times++;
74 | }
75 |
76 | while (times < 4)
77 | {
78 | result += string.Format("{0:F3}", ChangeNotes[0].BPM);
79 | result += "\t";
80 | times++;
81 | }
82 |
83 | return result + "\n";
84 | }
85 | }
86 | }
87 |
88 |
89 | ///
90 | /// Add BPMChange to change notes
91 | ///
92 | ///
93 | public void Add(BPMChange takeIn)
94 | {
95 | ChangeNotes.Add(takeIn);
96 | Update();
97 | }
98 |
99 | ///
100 | /// Compose change notes according to BPMChanges
101 | ///
102 | public void Update()
103 | {
104 | List adjusted = [];
105 | Note lastNote = new Rest();
106 | foreach (BPMChange? x in ChangeNotes)
107 | if (!(x.Bar == lastNote.Bar && x.Tick == lastNote.Tick && x.BPM == lastNote.BPM))
108 | {
109 | adjusted.Add(x);
110 | lastNote = x;
111 | }
112 |
113 | // Console.WriteLine(adjusted.Count);
114 | ChangeNotes = [.. adjusted];
115 | if (ChangeNotes.Count != adjusted.Count) throw new Exception("Adjusted BPM Note number not matching");
116 | }
117 |
118 | ///
119 | /// See if the BPMChange is valid
120 | ///
121 | /// True if valid, false elsewise
122 | public bool CheckValidity()
123 | {
124 | bool result = true;
125 | return result;
126 | }
127 |
128 | ///
129 | /// Compose BPMChanges in beginning of MA2
130 | ///
131 | ///
132 | public string Compose()
133 | {
134 | string? result = "";
135 | for (int i = 0; i < ChangeNotes.Count; i++)
136 | result += "BPM" + "\t" + ChangeNotes[i].Bar + "\t" + ChangeNotes[i].Tick + "\t" + ChangeNotes[i].BPM + "\n";
137 | //result += "BPM" + "\t" + bar[i] + "\t" + tick[i] + "\t" + String.Format("{0:F3}", bpm[i])+"\n";
138 | return result;
139 | }
140 | }
--------------------------------------------------------------------------------
/ChartDefinitions/IXmlUtility.cs:
--------------------------------------------------------------------------------
1 | using System.Xml;
2 |
3 | namespace MaiLib;
4 |
5 | ///
6 | /// Provide handful methods for Xml
7 | ///
8 | internal interface IXmlUtility
9 | {
10 | /////
11 | ///// Load and construct Xml document from given location.
12 | /////
13 | ///// Location to find
14 | //public void Load(string location);
15 |
16 | ///
17 | /// Save Xml to specified location.
18 | ///
19 | /// Location to save
20 | public void Save(string location);
21 |
22 | ///
23 | /// Return nodes with specified name
24 | ///
25 | ///
26 | /// XmlNodeList having name specified
27 | public XmlNodeList GetMatchNodes(string name);
28 |
29 | ///
30 | /// Update the information using given takeinValue.
31 | ///
32 | public void Update();
33 | }
--------------------------------------------------------------------------------
/ChartDefinitions/MeasureChanges.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | ///
4 | /// Store measure change notes in a chart
5 | ///
6 | public class MeasureChanges
7 | {
8 | #region Constructors
9 |
10 | ///
11 | /// Construct an empty Measure Change
12 | ///
13 | public MeasureChanges()
14 | {
15 | Bar = [];
16 | Tick = [];
17 | Quavers = [];
18 | Beats = [];
19 | ChangeNotes = [];
20 | }
21 |
22 | ///
23 | /// Construct Measure Change with existing one
24 | ///
25 | public MeasureChanges(MeasureChanges takeIn)
26 | {
27 | Bar = new List(takeIn.Bar);
28 | Tick = new List(takeIn.Tick);
29 | Quavers = new List(takeIn.Quavers);
30 | Beats = new List(takeIn.Beats);
31 | ChangeNotes = new List(takeIn.ChangeNotes);
32 | }
33 |
34 | ///
35 | /// Take in initial quavers and beats, incase MET_CHANGE is not specified
36 | ///
37 | /// Initial Quaver
38 | /// Initial Beat
39 | public MeasureChanges(int initialQuaver, int initialBeat)
40 | {
41 | Bar = [];
42 | Tick = [];
43 | Quavers = [];
44 | Beats = [];
45 | ChangeNotes = [];
46 | Quavers.Add(initialQuaver);
47 | Beats.Add(initialBeat);
48 | }
49 |
50 | ///
51 | /// Construct a measure of given beats
52 | ///
53 | ///
54 | ///
55 | ///
56 | ///
57 | public MeasureChanges(List bar, List tick, List quavers, List beats)
58 | {
59 | Bar = bar;
60 | Tick = tick;
61 | Quavers = quavers;
62 | Beats = beats;
63 | ChangeNotes = [];
64 | }
65 |
66 | #endregion
67 |
68 | public List Bar { get; }
69 | public List Tick { get; }
70 | public List Quavers { get; }
71 | public List Beats { get; }
72 | public List ChangeNotes { get; }
73 |
74 | public int InitialQuavers
75 | {
76 | get
77 | {
78 | if (Quavers is null || Quavers.Count == 0)
79 | {
80 | return 4;
81 | }
82 | else return Quavers[0];
83 | }
84 | }
85 |
86 | public int InitialBeats
87 | {
88 | get
89 | {
90 | if (Beats is null || Beats.Count == 0)
91 | {
92 | return 4;
93 | }
94 | else return Beats[0];
95 | }
96 | }
97 |
98 | ///
99 | /// Return first definitions
100 | ///
101 | public string InitialChange => "MET_DEF" + "\t" + InitialQuavers + "\t" + InitialBeats + "\n";
102 |
103 | ///
104 | /// Add new measure changes to MeasureChanges
105 | ///
106 | /// Bar which changes
107 | /// Tick which changes
108 | /// Quavers which changes
109 | /// Beat which changes
110 | public void Add(int bar, int tick, int quavers, int beats)
111 | {
112 | Bar.Add(bar);
113 | Tick.Add(tick);
114 | Quavers.Add(quavers);
115 | Beats.Add(beats);
116 | }
117 |
118 | public bool CheckValidity()
119 | {
120 | bool result = Bar.IndexOf(0) == 0;
121 | result = result && Tick.IndexOf(0) == 0;
122 | result = result && !Quavers[0].Equals(null);
123 | result = result && !Beats[0].Equals(null);
124 | return result;
125 | }
126 |
127 | public string Compose()
128 | {
129 | string? result = "";
130 | if (Bar.Count == 0)
131 | result += "MET" + "\t" + 0 + "\t" + 0 + "\t" + 4 + "\t" + 4 + "\n";
132 | else
133 | for (int i = 0; i < Bar.Count; i++)
134 | result += "MET" + "\t" + Bar[i] + "\t" + Tick[i] + "\t" + Quavers[i] + "\t" + Beats[i] + "\n";
135 | return result;
136 | }
137 |
138 | public void Update()
139 | {
140 | }
141 | }
--------------------------------------------------------------------------------
/ChartPack.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | ///
4 | /// Construct a collection to store charts in relate of SD and DX.
5 | ///
6 | public abstract class ChartPack : Chart, IChart
7 | {
8 | ///
9 | /// Stores shared information
10 | ///
11 | private TrackInformation? globalInformation;
12 |
13 | ///
14 | /// Stores SD and DX chart
15 | /// [0] SD [1] DX
16 | ///
17 | private List[] sddxCharts;
18 |
19 | ///
20 | /// Default constructor
21 | ///
22 | public ChartPack()
23 | {
24 | sddxCharts = new List[2];
25 | }
26 |
27 | ///
28 | /// Accesses this.sddxCharts
29 | ///
30 | /// this.sddxCharts
31 | public List[] SDDXCharts
32 | {
33 | get => sddxCharts;
34 | set => sddxCharts = value;
35 | }
36 |
37 | ///
38 | /// Accesses this.globalInformation
39 | ///
40 | /// this.globalInformation
41 | public TrackInformation? GlobalInformation
42 | {
43 | get => globalInformation;
44 | set => globalInformation = value;
45 | }
46 |
47 | // public abstract bool CheckValidity();
48 |
49 | public override string Compose()
50 | {
51 | throw new NotImplementedException();
52 | }
53 |
54 | // public abstract void Update();
55 | }
--------------------------------------------------------------------------------
/Compiler/Compiler.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 |
3 | namespace MaiLib;
4 |
5 | public abstract class Compiler : ICompiler
6 | {
7 | ///
8 | /// Stores difficulty keywords
9 | ///
10 | /// Difficulty
11 | public static readonly string[] Difficulty =
12 | ["Easy", "Basic", "Advanced", "Expert", "Master", "Remaster", "Utage"];
13 |
14 | ///
15 | /// Stores the rotate dictionary
16 | ///
17 | public Dictionary RotateDictionary = new()
18 | { { "17", "UpSideDown" }, { "305", "LeftToRight" }, { "417", "Clockwise90" } };
19 |
20 | #region Constructors
21 |
22 | ///
23 | /// Construct compiler of a single song.
24 | ///
25 | /// Folder
26 | /// Output folder
27 | public Compiler(string location, string targetLocation)
28 | {
29 | CompiledChart = [];
30 | Charts = [];
31 | MusicXML = new XmlInformation(location);
32 | MusicXML.Update();
33 | Information = MusicXML.InformationDict;
34 |
35 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
36 | GlobalSep = "\\";
37 | else
38 | GlobalSep = "/";
39 | }
40 |
41 | ///
42 | /// Empty constructor.
43 | ///
44 | public Compiler()
45 | {
46 | CompiledChart = [];
47 | Charts = [];
48 | Information = [];
49 | MusicXML = new XmlInformation();
50 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
51 | GlobalSep = "\\";
52 | else
53 | GlobalSep = "/";
54 | }
55 |
56 | #endregion
57 |
58 | ///
59 | /// Stores chart collections
60 | ///
61 | public List Charts { get; set; }
62 |
63 | ///
64 | /// Stores global information
65 | ///
66 | public Dictionary Information { get; set; }
67 |
68 | ///
69 | /// Stores read in music XML file
70 | ///
71 | public XmlInformation MusicXML { get; set; }
72 |
73 | ///
74 | /// Stores the path separator
75 | ///
76 | public string GlobalSep { get; set; }
77 |
78 | ///
79 | /// Stores the information of Compiled Chart
80 | ///
81 | public List CompiledChart { get; set; }
82 |
83 | public bool CheckValidity()
84 | {
85 | bool result = true;
86 | foreach (Chart? x in Charts) result = result && x.CheckValidity();
87 | return result;
88 | }
89 |
90 | public abstract string Compose();
91 |
92 | public void TakeInformation(Dictionary information)
93 | {
94 | Information = information;
95 | }
96 |
97 | ///
98 | /// Return compose of specified chart.
99 | ///
100 | /// Chart to compose
101 | /// Maidata of specified chart WITHOUT headers
102 | public abstract string Compose(Chart chart);
103 |
104 | ///
105 | /// Compose utage charts
106 | ///
107 | /// switch to produce utage
108 | /// Corresponding utage chart
109 | public abstract string Compose(bool isUtage, List ma2files);
110 |
111 | ///
112 | /// Return the chart bpm change table of MaiCompiler
113 | ///
114 | /// First BPM change table of this.charts
115 | public BPMChanges SymbolicBPMTable()
116 | {
117 | BPMChanges? bpmTable = new BPMChanges();
118 | bool foundTable = false;
119 | for (int i = 0; i < Charts.Count && !foundTable; i++)
120 | if (Charts[i] != null)
121 | {
122 | bpmTable = Charts[i].BPMChanges;
123 | foundTable = true;
124 | }
125 |
126 | return bpmTable;
127 | }
128 |
129 | ///
130 | /// Return the first note of master chart
131 | ///
132 | /// The first note of the master chart, or first note of the Utage chart if isUtage is turned true
133 | /// Throws null reference exception if the chart does not exist
134 | public Note SymbolicFirstNote(bool isUtage)
135 | {
136 | if (!isUtage)
137 | return Charts[4].FirstNote ?? throw new NullReferenceException("Null first note: master chart is invalid");
138 |
139 | if (isUtage)
140 | {
141 | Note? firstNote;
142 | bool foundFirstNote = false;
143 | for (int i = Charts.Count; i >= 0 && !foundFirstNote; i++)
144 | if (Charts[i] != null)
145 | {
146 | firstNote = Charts[i].FirstNote;
147 | foundFirstNote = true;
148 | }
149 |
150 | return Charts[0].FirstNote ?? throw new NullReferenceException("Null first note: utage chart is invalid");
151 | }
152 |
153 | throw new NullReferenceException(
154 | "This compiler contains invalid Master Chart and is not Utage Chart: no first note is returned");
155 | }
156 |
157 | ///
158 | /// Generate one line summary of this track with ID, name, genre and difficulty
159 | ///
160 | ///
161 | public string GenerateOneLineSummary()
162 | {
163 | // TODO: Shift to format
164 | string? result = "";
165 | if (Charts.Equals(null)) throw new NullReferenceException("This compiler has empty chat list!");
166 | result += "(" + Information["Music ID"] + ") " + Information["Name"] + ", " + Information["Genre"] + ", ";
167 | if (!Information["Easy"].Equals("")) result += Information["Easy"] + "/";
168 | if (!Information["Basic"].Equals(""))
169 | result += Information["Basic"];
170 | else result += "-";
171 | if (!Information["Advanced"].Equals(""))
172 | result += "/" + Information["Advanced"];
173 | else result += "/-";
174 | if (!Information["Expert"].Equals(""))
175 | result += "/" + Information["Expert"];
176 | else result += "/-";
177 | if (!Information["Master"].Equals(""))
178 | result += "/" + Information["Master"];
179 | else result += "/-";
180 | if (!Information["Remaster"].Equals(""))
181 | result += "/" + Information["Remaster"];
182 | else result += "/-";
183 | if (!Information["Utage"].Equals("")) result += "\\" + Information["Utage"];
184 |
185 | return result;
186 | }
187 | }
--------------------------------------------------------------------------------
/Compiler/ICompiler.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | ///
4 | /// Provide interface for Compilers
5 | ///
6 | public interface ICompiler
7 | {
8 | ///
9 | /// Intake information to compile data.
10 | ///
11 | /// TakeInformation to provide
12 | public void TakeInformation(Dictionary information);
13 |
14 | ///
15 | /// Compose given chart to specific format.
16 | ///
17 | /// Corresponding chart
18 | public string Compose();
19 |
20 | ///
21 | /// Check if the chart given is valid to print.
22 | ///
23 | /// True if valid, false otherwise
24 | public bool CheckValidity();
25 | }
--------------------------------------------------------------------------------
/Compiler/SimaiCompiler.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Reflection;
3 | using static MaiLib.NoteEnum;
4 |
5 | namespace MaiLib;
6 |
7 | ///
8 | /// Compile various Ma2 Charts
9 | ///
10 | public class SimaiCompiler : Compiler
11 | {
12 | public bool StrictDecimalLevel { get; set; }
13 |
14 | public string Result { get; private set; }
15 |
16 | ///
17 | /// Construct compiler of a single song.
18 | ///
19 | /// Folder
20 | /// Output folder
21 | public SimaiCompiler(bool strictDecimal, string location, string targetLocation)
22 | {
23 | StrictDecimalLevel = strictDecimal;
24 | for (int i = 0; i < 7; i++) Charts.Add(new Simai());
25 | MusicXML = new XmlInformation(location);
26 | Information = MusicXML.InformationDict;
27 | //Construct Charts
28 | {
29 | if (!Information["Easy"].Equals("") &&
30 | File.Exists(location + Information.GetValueOrDefault("Easy Chart Path")))
31 | Charts[0] = new Ma2(location + Information.GetValueOrDefault("Easy Chart Path"));
32 | if (!Information["Basic"].Equals("") &&
33 | File.Exists(location + Information.GetValueOrDefault("Basic Chart Path")))
34 | //Console.WriteLine("Have basic: "+ location + this.Information.GetValueOrDefault("Basic Chart Path"));
35 | Charts[1] = new Ma2(location + Information.GetValueOrDefault("Basic Chart Path"));
36 | if (!Information["Advanced"].Equals("") &&
37 | File.Exists(location + Information.GetValueOrDefault("Advanced Chart Path")))
38 | Charts[2] = new Ma2(location + Information.GetValueOrDefault("Advanced Chart Path"));
39 | if (!Information["Expert"].Equals("") &&
40 | File.Exists(location + Information.GetValueOrDefault("Expert Chart Path")))
41 | Charts[3] = new Ma2(location + Information.GetValueOrDefault("Expert Chart Path"));
42 | if (!Information["Master"].Equals("") &&
43 | File.Exists(location + Information.GetValueOrDefault("Master Chart Path")))
44 | Charts[4] = new Ma2(location + Information.GetValueOrDefault("Master Chart Path"));
45 | if (!Information["Remaster"].Equals("") &&
46 | File.Exists(location + Information.GetValueOrDefault("Remaster Chart Path")))
47 | Charts[5] = new Ma2(location + Information.GetValueOrDefault("Remaster Chart Path"));
48 | if (!Information["Utage"].Equals("") &&
49 | File.Exists(location + Information.GetValueOrDefault("Utage Chart Path")))
50 | Charts[6] = new Ma2(location + Information.GetValueOrDefault("Utage Chart Path"));
51 | }
52 |
53 | Result = Compose();
54 | //Console.WriteLine(result);
55 | }
56 |
57 | ///
58 | /// Construct compiler of a single song.
59 | ///
60 | /// Folder
61 | /// Output folder
62 | /// True if for utage
63 | public SimaiCompiler(bool strictDecimal, string location, string targetLocation, bool forUtage)
64 | {
65 | StrictDecimalLevel = strictDecimal;
66 | string[]? ma2files = Directory.GetFiles(location, "*.ma2");
67 | Charts = [];
68 | MusicXML = new XmlInformation(location);
69 | Information = MusicXML.InformationDict;
70 | bool rotate = false;
71 | string? rotateParameter = "";
72 | foreach (KeyValuePair pair in RotateDictionary)
73 | if (MusicXML.TrackID.Equals(pair.Key))
74 | {
75 | rotateParameter = pair.Value;
76 | rotate = true;
77 | }
78 |
79 | foreach (string? ma2file in ma2files)
80 | {
81 | Ma2? chartCandidate = new Ma2(ma2file);
82 | if (rotate)
83 | {
84 | bool rotateParameterIsValid = Enum.TryParse(rotateParameter, out FlipMethod rotateParameterEnum);
85 | if (rotateParameterIsValid)
86 | {
87 | chartCandidate.RotateNotes((rotateParameterEnum));
88 | }
89 | else
90 | {
91 | throw new Exception("The given rotation method is invalid. Given: " + rotateParameter);
92 | }
93 | }
94 |
95 | Charts.Add(chartCandidate);
96 | }
97 |
98 | List? ma2List = [.. ma2files];
99 |
100 | Result = Compose(true, ma2List);
101 | //Console.WriteLine(result);
102 | }
103 |
104 | ///
105 | /// Empty constructor.
106 | ///
107 | public SimaiCompiler()
108 | {
109 | StrictDecimalLevel = false;
110 | for (int i = 0; i < 7; i++) Charts.Add(new Simai());
111 | Charts = [];
112 | Information = [];
113 | MusicXML = new XmlInformation();
114 | Result = "";
115 | }
116 |
117 | public void WriteOut(string targetLocation, bool overwrite)
118 | {
119 | StreamWriter? sw = new StreamWriter(targetLocation + GlobalSep + "maidata.txt", !overwrite);
120 | {
121 | sw.WriteLine(Result);
122 | }
123 | sw.Close();
124 | // MusicXML = new XmlInformation(){ InformationDict = this.Information};
125 | // MusicXML.WriteOutInformation($"{targetLocation}/Music.xml");
126 | }
127 |
128 | public override string Compose()
129 | {
130 | string? result = "";
131 | // Console.WriteLine("StrictDecimal: "+StrictDecimalLevel);
132 | // Console.ReadKey();
133 | //Add Information
134 | {
135 | string? beginning = "";
136 | beginning +=
137 | $"&title={Information.GetValueOrDefault("Name")}{Information.GetValueOrDefault("SDDX Suffix")}\n";
138 | beginning += $"&wholebpm={Information.GetValueOrDefault("BPM")}\n";
139 | beginning += $"&artist={Information.GetValueOrDefault("Composer")}\n";
140 | beginning += $"&artistid={Information.GetValueOrDefault("Composer ID")}\n";
141 | beginning += $"&des={Information.GetValueOrDefault("Master Chart Maker")}\n";
142 | beginning += $"&shortid={Information.GetValueOrDefault("Music ID")}\n";
143 | beginning += $"&genre={Information.GetValueOrDefault("Genre")}\n";
144 | beginning += $"&genreid={Information.GetValueOrDefault("Genre ID")}\n";
145 | beginning += $"&cabinet={(MusicXML.IsDXChart ? "DX" : "SD")}\n";
146 | beginning += $"&version={MusicXML.TrackVersion}\n";
147 | beginning += "&ChartConverter=Neskol\n";
148 | beginning += "&ChartConvertTool=MaichartConverter\n";
149 | // string assemblyVersion = FileVersionInfo.GetVersionInfo(typeof(SimaiCompiler).Assembly.Location).ProductVersion ?? "Alpha Testing";
150 | // if (assemblyVersion.Contains('+')) assemblyVersion = assemblyVersion.Split('+')[0];
151 | // beginning += "&ChartConvertToolVersion=" + assemblyVersion + "\n";
152 | beginning += $"&ChartConvertToolVersion={Assembly.GetExecutingAssembly().GetName().Version}\n";
153 | beginning += "&smsg=See https://github.com/Neskol/MaichartConverter for updates\n";
154 | beginning += "\n";
155 |
156 | if (Information.TryGetValue("Easy", out string? easy) &&
157 | Information.TryGetValue("Easy Chart Maker", out string? easyMaker))
158 | {
159 | string difficultyCandidate = easy;
160 | if (StrictDecimalLevel && Information.TryGetValue("Easy Decimal", out string? decimalLevel))
161 | {
162 | difficultyCandidate = decimalLevel;
163 | }
164 |
165 | beginning += "&lv_1=" + difficultyCandidate + "\n";
166 | beginning += "&des_1=" + easyMaker + "\n";
167 | beginning += "\n";
168 | }
169 |
170 | if (Information.TryGetValue("Basic", out string? basic) &&
171 | Information.TryGetValue("Basic Chart Maker", out string? basicMaker))
172 | {
173 | string difficultyCandidate = basic;
174 | if (StrictDecimalLevel && Information.TryGetValue("Basic Decimal", out string? decimalLevel))
175 | {
176 | difficultyCandidate = decimalLevel;
177 | }
178 |
179 | beginning += "&lv_2=" + difficultyCandidate + "\n";
180 | beginning += "&des_2=" + basicMaker + "\n";
181 | beginning += "\n";
182 | }
183 |
184 |
185 | if (Information.TryGetValue("Advanced", out string? advance) &&
186 | Information.TryGetValue("Advanced Chart Maker", out string? advanceMaker))
187 | {
188 | string difficultyCandidate = advance;
189 | if (StrictDecimalLevel && Information.TryGetValue("Advanced Decimal", out string? decimalLevel))
190 | {
191 | difficultyCandidate = decimalLevel;
192 | }
193 |
194 | beginning += "&lv_3=" + difficultyCandidate + "\n";
195 | beginning += "&des_3=" + advanceMaker + "\n";
196 | beginning += "\n";
197 | }
198 |
199 |
200 | if (Information.TryGetValue("Expert", out string? expert) &&
201 | Information.TryGetValue("Expert Chart Maker", out string? expertMaker))
202 | {
203 | string difficultyCandidate = expert;
204 | if (StrictDecimalLevel && Information.TryGetValue("Expert Decimal", out string? decimalLevel))
205 | {
206 | difficultyCandidate = decimalLevel;
207 | }
208 |
209 | beginning += "&lv_4=" + difficultyCandidate + "\n";
210 | beginning += "&des_4=" + expertMaker + "\n";
211 | beginning += "\n";
212 | }
213 |
214 |
215 | if (Information.TryGetValue("Master", out string? master) &&
216 | Information.TryGetValue("Master Chart Maker", out string? masterMaker))
217 | {
218 | string difficultyCandidate = master;
219 | if (StrictDecimalLevel && Information.TryGetValue("Master Decimal", out string? decimalLevel))
220 | {
221 | difficultyCandidate = decimalLevel;
222 | }
223 |
224 | beginning += "&lv_5=" + difficultyCandidate + "\n";
225 | beginning += "&des_5=" + masterMaker + "\n";
226 | beginning += "\n";
227 | }
228 |
229 |
230 | if (Information.TryGetValue("Remaster", out string? remaster) &&
231 | Information.TryGetValue("Remaster Chart Maker", out string? remasterMaker))
232 | {
233 | string difficultyCandidate = remaster;
234 | if (StrictDecimalLevel && Information.TryGetValue("Remaster Decimal", out string? decimalLevel))
235 | {
236 | difficultyCandidate = decimalLevel;
237 | }
238 |
239 | beginning += "&lv_6=" + difficultyCandidate + "\n";
240 | beginning += "&des_6=" + remasterMaker;
241 | beginning += "\n";
242 | beginning += "\n";
243 | }
244 |
245 | result += beginning;
246 | }
247 | Console.WriteLine("Finished writing header of " + Information.GetValueOrDefault("Name"));
248 |
249 | //Compose Charts
250 | {
251 | for (int i = 0; i < Charts.Count; i++)
252 | {
253 | // Console.WriteLine("Processing chart: " + i);
254 | if (Charts[i] != null && !Information[Difficulty[i]].Equals(""))
255 | {
256 | string? isDxChart = Information.GetValueOrDefault("SDDX Suffix");
257 | if (!Charts[i].IsDxChart) isDxChart = "";
258 | result += "&inote_" + (i + 1) + "=\n";
259 | result += Compose(Charts[i]);
260 | CompiledChart.Add(Information.GetValueOrDefault("Name") + isDxChart + " [" + Difficulty[i] + "]");
261 | }
262 |
263 | result += "\n";
264 | }
265 | }
266 | Console.WriteLine("Finished composing.");
267 | return result;
268 | }
269 |
270 | ///
271 | /// Return compose of specified chart.
272 | ///
273 | /// Chart to compose
274 | /// Maidata of specified chart WITHOUT headers
275 | public override string Compose(Chart chart)
276 | {
277 | return new Simai(chart).Compose();
278 | }
279 |
280 | ///
281 | /// Compose utage Charts
282 | ///
283 | /// switch to produce utage
284 | /// Corresponding utage chart
285 | public override string Compose(bool isUtage, List ma2files)
286 | {
287 | string? result = "";
288 | //Add Information
289 |
290 | string? beginning = "";
291 | beginning += "&title=" + Information.GetValueOrDefault("Name") + "[宴]" + "\n";
292 | beginning += "&wholebpm=" + Information.GetValueOrDefault("BPM") + "\n";
293 | beginning += "&artist=" + Information.GetValueOrDefault("Composer") + "\n";
294 | beginning += "&des=" + Information.GetValueOrDefault("Master Chart Maker") + "\n";
295 | beginning += "&shortid=" + Information.GetValueOrDefault("Music ID") + "\n";
296 | beginning += "&genre=" + Information.GetValueOrDefault("Genre") + "\n";
297 | beginning += "&cabinet=";
298 | if (MusicXML.IsDXChart)
299 | beginning += "DX\n";
300 | else
301 | beginning += "SD\n";
302 | beginning += "&version=" + MusicXML.TrackVersion + "\n";
303 | beginning += "&ChartConverter=Neskol\n";
304 | beginning += "&ChartConvertTool=MaichartConverter\n";
305 | string assemblyVersion =
306 | FileVersionInfo.GetVersionInfo(typeof(SimaiCompiler).Assembly.Location).ProductVersion ?? "Alpha Testing";
307 | if (assemblyVersion.Contains('+')) assemblyVersion = assemblyVersion.Split('+')[0];
308 | beginning += "&ChartConvertToolVersion=" + assemblyVersion + "\n";
309 | beginning += "&smsg=See https://github.com/Neskol/MaichartConverter for updates\n";
310 | beginning += "\n";
311 |
312 | int defaultChartIndex = 7;
313 | if (ma2files.Count > 1)
314 | {
315 | defaultChartIndex = 2;
316 | foreach (string? ma2file in ma2files)
317 | {
318 | string difficultyCandidate = Information["Utage"].Equals("") ? "宴" : $"{Information["Utage"]}?";
319 | if (StrictDecimalLevel && Information.TryGetValue("Utage Decimal", out string? decimalLevel))
320 | {
321 | difficultyCandidate = $"{decimalLevel}?";
322 | }
323 |
324 | beginning += $"&lv_{defaultChartIndex}={difficultyCandidate}\n\n";
325 | defaultChartIndex++;
326 | }
327 |
328 | defaultChartIndex = 0;
329 | }
330 | else
331 | {
332 | string difficultyCandidate = Information["Utage"].Equals("") ? "宴" : $"{Information["Utage"]}?";
333 | if (StrictDecimalLevel && Information.TryGetValue("Utage Decimal", out string? decimalLevel))
334 | {
335 | difficultyCandidate = $"{decimalLevel}?";
336 | }
337 |
338 | beginning += $"&lv_{defaultChartIndex}={difficultyCandidate}\n\n";
339 | }
340 |
341 |
342 | result += beginning;
343 | Console.WriteLine("Finished writing header of " + Information.GetValueOrDefault("Name"));
344 |
345 | //Compose Charts
346 |
347 | if (defaultChartIndex < 7)
348 | {
349 | for (int i = 0; i < Charts.Count; i++)
350 | {
351 | // Console.WriteLine("Processing chart: " + i);
352 | string? isDxChart = "Utage";
353 | result += "&inote_" + (i + 2) + "=\n";
354 | result += Compose(Charts[i]);
355 | CompiledChart.Add(Information.GetValueOrDefault("Name") + isDxChart + " [宴]");
356 | result += "\n";
357 | }
358 | }
359 | else
360 | {
361 | result += "&inote_7=\n";
362 | result += Compose(Charts[0]);
363 | CompiledChart.Add(Information.GetValueOrDefault("Name") + "Utage" + " [宴]");
364 | }
365 |
366 | Console.WriteLine("Finished composing.");
367 | return result;
368 | }
369 | }
--------------------------------------------------------------------------------
/Enums/ChartEnum.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.Design;
2 |
3 | namespace MaiLib;
4 |
5 | public class ChartEnum
6 | {
7 | public enum ChartType
8 | {
9 | Standard,
10 | StandardUtage,
11 | DX,
12 | DXFestival,
13 | DXUtage
14 | }
15 |
16 | public enum ChartVersion
17 | {
18 | Debug,
19 | Simai,
20 | SimaiFes,
21 | Ma2_103,
22 | Ma2_104
23 | }
24 | }
--------------------------------------------------------------------------------
/Enums/NoteEnum.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 |
3 | namespace MaiLib;
4 |
5 | ///
6 | /// Holds enums of the Note
7 | ///
8 | public class NoteEnum
9 | {
10 | ///
11 | /// Flags the special state of each note
12 | ///
13 | public enum SpecialState
14 | {
15 | /// Normal note, nothing special
16 | Normal,
17 |
18 | /// Break note
19 | Break,
20 |
21 | /// EX Note
22 | EX,
23 |
24 | /// EX Break
25 | BreakEX,
26 |
27 | /// Connecting Slide
28 | ConnectingSlide
29 | }
30 |
31 | ///
32 | /// Defines the general category of notes
33 | ///
34 | public enum NoteGenre
35 | {
36 | REST,
37 | TAP,
38 | HOLD,
39 | SLIDE,
40 | BPM,
41 | MEASURE
42 | }
43 |
44 | ///
45 | /// Defines the specific genre of notes
46 | ///
47 | public enum NoteSpecificGenre
48 | {
49 | REST,
50 | TAP,
51 | SLIDE_START,
52 | HOLD,
53 | SLIDE,
54 | SLIDE_EACH,
55 | SLIDE_GROUP,
56 | BPM,
57 | MEASURE
58 | }
59 |
60 | ///
61 | /// Defines the possible Note Type
62 | ///
63 | public enum NoteType
64 | {
65 | // Dummy Rest Note
66 | /// Rest Note
67 | RST,
68 |
69 | // TAP Enums
70 | /// Normal Tap Note
71 | TAP,
72 |
73 | /// Start of a Slide as Tap
74 | STR,
75 |
76 | /// Imaginary Slide Start that does not appear in a slide
77 | NST,
78 |
79 | /// Singular Sldie Start but does not have consecutive slide
80 | NSS,
81 |
82 | /// Touch Tap
83 | TTP,
84 |
85 | // Hold Enums
86 | /// Normal Hold
87 | HLD,
88 |
89 | /// Touch Hold
90 | THO,
91 |
92 | // Slide Enums
93 | /// Straight Star
94 | SI_,
95 |
96 | /// Circular Star Left
97 | SCL,
98 |
99 | /// Circular Star Right
100 | SCR,
101 |
102 | /// Line not intercepting Crossing Center
103 | SV_,
104 |
105 | /// Line not intercepting Crossing Center
106 | SUL,
107 |
108 | /// Line not intercepting Crossing Center
109 | SUR,
110 |
111 | /// WIFI Star
112 | SF_,
113 |
114 | /// Inflecting Line Left
115 | SLL,
116 |
117 | /// Inflecting Line Right
118 | SLR,
119 |
120 | /// Self-winding Left
121 | SXL,
122 |
123 | /// Self-winding Right
124 | SXR,
125 |
126 | /// Line not intercepting Crossing Center
127 | SSL,
128 |
129 | /// Line not intercepting Crossing Center
130 | SSR,
131 |
132 | /// Composite Slide Each
133 | SLIDE_EACH,
134 |
135 | //Function Notes
136 | BPM,
137 | MEASURE
138 | }
139 |
140 |
141 | ///
142 | /// Defines the possible flip methods of notes
143 | ///
144 | public enum FlipMethod
145 | {
146 | UpSideDown,
147 | Clockwise90,
148 | Clockwise180,
149 | Counterclockwise90,
150 | Counterclockwise180,
151 | LeftToRight
152 | }
153 | }
--------------------------------------------------------------------------------
/Enums/SimaiFormalSpec.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Neskol/MaiLib/dcaaea5eb85bf15e19b0a705b1905acee45f9318/Enums/SimaiFormalSpec.txt
--------------------------------------------------------------------------------
/Enums/TokenEnum.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | public class TokenEnum
4 | {
5 | public enum TokenType
6 | {
7 | LPAREN,
8 | RPAREN,
9 | LBRACE,
10 | RBRACE,
11 | LBRACKET,
12 | RBRACKET,
13 | LANGLE,
14 | RANGLE,
15 | ASTERISK,
16 | COMMA,
17 | COLON,
18 | DASH,
19 | DOLLAR,
20 | SLASH,
21 | SHARP,
22 | DOT,
23 | A,
24 | B,
25 | C,
26 | D,
27 | E,
28 | F,
29 | SV,
30 | LV,
31 | P,
32 | Q,
33 | S,
34 | Z,
35 | NUM1,
36 | NUM2,
37 | NUM3,
38 | NUM4,
39 | NUM5,
40 | NUM6,
41 | NUM7,
42 | NUM8,
43 | NUM9,
44 | NUM0,
45 | BREAK,
46 | EX,
47 | HOLD,
48 | FIREWORK,
49 | EOS,
50 | BLANK
51 | }
52 | }
--------------------------------------------------------------------------------
/MaiLib.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | 1.0.8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/MaiLib.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.3.32901.215
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MaiLib", "MaiLib.csproj", "{1C85F932-30E4-4562-98EF-DB86B61303F3}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {1C85F932-30E4-4562-98EF-DB86B61303F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {1C85F932-30E4-4562-98EF-DB86B61303F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {1C85F932-30E4-4562-98EF-DB86B61303F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {1C85F932-30E4-4562-98EF-DB86B61303F3}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {CB1A7F44-7985-4140-B27B-B5618862592F}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/Note/BPMChange.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | using static NoteEnum;
4 | using static ChartEnum;
5 |
6 | ///
7 | /// BPMChange note for Simai
8 | ///
9 | public class BPMChange : Note
10 | {
11 | #region Constructors
12 |
13 | ///
14 | /// Construct Empty
15 | ///
16 | public BPMChange()
17 | {
18 | NoteType = NoteEnum.NoteType.BPM;
19 | Key = "";
20 | Update();
21 | }
22 |
23 | ///
24 | /// Construct BPMChange with given bar, tick, BPM
25 | ///
26 | /// Bar
27 | /// tick
28 | /// BPM
29 | public BPMChange(int bar, int startTime, double BPM)
30 | {
31 | NoteType = NoteEnum.NoteType.BPM;
32 | Bar = bar;
33 | Tick = startTime;
34 | this.BPM = BPM;
35 | Update();
36 | }
37 |
38 | ///
39 | /// Construct BPMChange with take in value
40 | ///
41 | /// Take in BPMChange
42 | public BPMChange(BPMChange takeIn)
43 | {
44 | NoteType = NoteEnum.NoteType.BPM;
45 | Bar = takeIn.Bar;
46 | Tick = takeIn.Tick;
47 | BPM = takeIn.BPM;
48 | Update();
49 | }
50 |
51 | ///
52 | /// Construct BPMChange with take in value
53 | ///
54 | /// Take in note
55 | public BPMChange(Note takeIn)
56 | {
57 | NoteType = NoteEnum.NoteType.BPM;
58 | Bar = takeIn.Bar;
59 | Tick = takeIn.Tick;
60 | Update();
61 | }
62 |
63 | #endregion
64 |
65 | public override double BPM { get; protected internal set; }
66 |
67 | public double BPMTimeUnit => 60 / BPM * 4 / Definition;
68 |
69 | public override NoteGenre NoteGenre => NoteEnum.NoteGenre.BPM;
70 |
71 | public override bool IsNote => true;
72 |
73 | public override NoteSpecificGenre NoteSpecificGenre => NoteEnum.NoteSpecificGenre.BPM;
74 |
75 |
76 | public override bool CheckValidity() => BPM != 0;
77 |
78 | public override string Compose(ChartVersion format)
79 | {
80 | switch (format)
81 | {
82 | case ChartVersion.Simai:
83 | case ChartVersion.SimaiFes:
84 | return "(" + BPM + ")";
85 | case ChartVersion.Debug:
86 | return "(" + BPM + "_" + Tick + ")";
87 | default:
88 | return "";
89 | }
90 | }
91 |
92 | public override bool Equals(object? obj)
93 | {
94 | bool result = false;
95 | if (this == obj && this == null)
96 | {
97 | result = true;
98 | }
99 | else if (this != null && obj != null)
100 | {
101 | BPMChange? candidate = (BPMChange)obj;
102 | if (GetHashCode() == candidate.GetHashCode())
103 | result = true;
104 | else if (Bar == candidate.Bar)
105 | if (Tick == candidate.Tick && BPM == candidate.BPM)
106 | result = true;
107 | }
108 |
109 | return result;
110 | }
111 |
112 | public override Note NewInstance()
113 | {
114 | Note result = new BPMChange(this);
115 | return result;
116 | }
117 |
118 | public override int GetHashCode() => base.GetHashCode();
119 | }
--------------------------------------------------------------------------------
/Note/Hold.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | using static MaiLib.NoteEnum;
4 | using static MaiLib.ChartEnum;
5 |
6 | ///
7 | /// Constructs Hold Note
8 | ///
9 | public class Hold : Note
10 | {
11 | #region Constructors
12 |
13 | ///
14 | /// Construct a Hold Note
15 | ///
16 | /// HLD,XHO
17 | /// Key of the hold note
18 | /// Bar of the hold note
19 | /// Tick of the hold note
20 | /// Last time of the hold note in ticks
21 | public Hold(NoteType noteType, int bar, int startTick, string key, int lastLength)
22 | {
23 | NoteType = noteType;
24 | Key = key;
25 | Bar = bar;
26 | Tick = startTick;
27 | LastLength = lastLength;
28 | SpecialEffect = false;
29 | TouchSize = "M1";
30 | // Update();
31 | }
32 |
33 | ///
34 | /// Construct a Hold Note
35 | ///
36 | /// HLD,XHO
37 | /// Key of the hold note
38 | /// Bar of the hold note
39 | /// Tick of the hold note
40 | /// Last time of the hold note in seconds
41 | public Hold(NoteType noteType, int bar, int startTick, string key, double lastTime)
42 | {
43 | NoteType = noteType;
44 | Key = key;
45 | Bar = bar;
46 | Tick = startTick;
47 | CalculatedLastTime = lastTime;
48 | SpecialEffect = false;
49 | TouchSize = "M1";
50 | // Update();
51 | }
52 |
53 | ///
54 | /// Construct a Touch Hold Note
55 | ///
56 | /// THO
57 | /// Key of the hold note
58 | /// Bar of the hold note
59 | /// Tick of the hold note
60 | /// Last time of the hold note in seconds
61 | /// Store if the touch note ends with special effect
62 | /// Determines how large the touch note is
63 | public Hold(NoteType noteType, int bar, int startTick, string key, double lastTime, bool specialEffect,
64 | string touchSize)
65 | {
66 | NoteType = noteType;
67 | Key = key;
68 | Bar = bar;
69 | Tick = startTick;
70 | CalculatedLastTime = lastTime;
71 | SpecialEffect = specialEffect;
72 | TouchSize = touchSize;
73 | // Update();
74 | }
75 |
76 | ///
77 | /// Construct a Touch Hold Note
78 | ///
79 | /// THO
80 | /// Key of the hold note
81 | /// Bar of the hold note
82 | /// Tick of the hold note
83 | /// Last time of the hold note
84 | /// Store if the touch note ends with special effect
85 | /// Determines how large the touch note is
86 | public Hold(NoteType noteType, int bar, int startTime, string key, int lastTime, bool specialEffect,
87 | string touchSize)
88 | {
89 | NoteType = noteType;
90 | Key = key;
91 | Bar = bar;
92 | Tick = startTime;
93 | LastLength = lastTime;
94 | SpecialEffect = specialEffect;
95 | TouchSize = touchSize;
96 | // Update();
97 | }
98 |
99 | ///
100 | /// Construct a Hold from another note
101 | ///
102 | /// The intake note
103 | /// Will raise exception if touch size is null
104 | public Hold(Note inTake)
105 | {
106 | inTake.CopyOver(this);
107 | if (inTake.NoteGenre is not NoteEnum.NoteGenre.HOLD)
108 | {
109 | TouchSize = ((Hold)inTake).TouchSize ?? throw new NullReferenceException();
110 | SpecialEffect = ((Hold)inTake).SpecialEffect;
111 | }
112 | else
113 | {
114 | TouchSize = "M1";
115 | SpecialEffect = false;
116 | }
117 | }
118 |
119 | #endregion
120 |
121 | public override NoteGenre NoteGenre => NoteGenre.HOLD;
122 |
123 | public override bool IsNote => true;
124 |
125 | public override NoteSpecificGenre NoteSpecificGenre => NoteEnum.NoteSpecificGenre.HOLD;
126 |
127 | //TODO: REWRITE THIS
128 | public override bool CheckValidity() => true;
129 |
130 | public override string Compose(ChartVersion format)
131 | {
132 | string? result = "";
133 | switch (NoteType)
134 | {
135 | case NoteType.HLD:
136 | switch (format)
137 | {
138 | case ChartVersion.Simai:
139 | case ChartVersion.SimaiFes:
140 | default:
141 | result += KeyNum + 1;
142 | if (NoteSpecialState == SpecialState.Break)
143 | result += "b";
144 | else if (NoteSpecialState == SpecialState.EX)
145 | result += "x";
146 | else if (NoteSpecialState == SpecialState.BreakEX) result += "bx";
147 | if (SpecialEffect) result += "f";
148 | result += "h";
149 | if (TickBPMDisagree || Delayed)
150 | result += GenerateAppropriateLength(FixedLastLength);
151 | else
152 | result += GenerateAppropriateLength(LastLength);
153 | if (format is ChartVersion.Debug) result += "_" + Tick;
154 | break;
155 | case ChartVersion.Ma2_103:
156 | string typeCandidate =
157 | NoteSpecialState is SpecialState.EX || NoteSpecialState is SpecialState.BreakEX
158 | ? "XHO"
159 | : NoteType.ToString();
160 | result = typeCandidate + "\t" + Bar + "\t" + Tick + "\t" + Key + "\t" + LastLength;
161 | break;
162 | case ChartVersion.Ma2_104:
163 | switch (NoteSpecialState)
164 | {
165 | case SpecialState.EX:
166 | result += "EX";
167 | break;
168 | case SpecialState.Break:
169 | result += "BR";
170 | break;
171 | case SpecialState.BreakEX:
172 | result += "BX";
173 | break;
174 | case SpecialState.ConnectingSlide:
175 | result += "CN";
176 | break;
177 | default:
178 | result += "NM";
179 | break;
180 | }
181 |
182 | result += NoteType + "\t" + Bar + "\t" + Tick + "\t" + Key + "\t" + LastLength;
183 | break;
184 | }
185 |
186 | break;
187 | case NoteType.THO:
188 | switch (format)
189 | {
190 | case ChartVersion.Simai:
191 | case ChartVersion.SimaiFes:
192 | default:
193 | result += KeyGroup + (KeyNum + 1).ToString();
194 | if (NoteSpecialState == SpecialState.Break)
195 | result += "b";
196 | else if (NoteSpecialState == SpecialState.EX)
197 | result += "x";
198 | else if (NoteSpecialState == SpecialState.BreakEX) result += "bx";
199 | if (SpecialEffect) result += "f";
200 | result += "h";
201 | if (TickBPMDisagree || Delayed)
202 | result += GenerateAppropriateLength(FixedLastLength);
203 | else
204 | result += GenerateAppropriateLength(LastLength);
205 | if (format is ChartVersion.Debug) result += "_" + Tick;
206 | break;
207 | case ChartVersion.Ma2_103:
208 | result = NoteType + "\t" + Bar + "\t" + Tick + "\t" + KeyNum + "\t" + LastLength + "\t" +
209 | KeyGroup + "\t" + (SpecialEffect ? 1 : 0) + "\t" + TouchSize;
210 | break;
211 | case ChartVersion.Ma2_104:
212 | switch (NoteSpecialState)
213 | {
214 | case SpecialState.EX:
215 | result += "EX";
216 | break;
217 | case SpecialState.Break:
218 | result += "BR";
219 | break;
220 | case SpecialState.BreakEX:
221 | result += "BX";
222 | break;
223 | case SpecialState.ConnectingSlide:
224 | result += "CN";
225 | break;
226 | default:
227 | result += "NM";
228 | break;
229 | }
230 |
231 | result += NoteType + "\t" + Bar + "\t" + Tick + "\t" + KeyNum + "\t" + LastLength + "\t" +
232 | KeyGroup + "\t" + (SpecialEffect ? 1 : 0) + "\t" + TouchSize;
233 | break;
234 | }
235 |
236 | break;
237 | }
238 |
239 | return result;
240 | }
241 |
242 | public override Note NewInstance() => new Hold(this);
243 | }
--------------------------------------------------------------------------------
/Note/INote.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | using static MaiLib.ChartEnum;
4 |
5 | ///
6 | /// Provide interface and basic functions for Notes
7 | ///
8 | internal interface INote
9 | {
10 | ///
11 | /// Convert note to string for writing
12 | ///
13 | /// 0 if Simai, 1 if Ma2
14 | string Compose(ChartVersion format);
15 |
16 | ///
17 | /// See if current note has all information needed
18 | ///
19 | /// True if qualified, false otherwise
20 | bool CheckValidity();
21 |
22 | ///
23 | /// Updates this note instance.
24 | ///
25 | /// True if Calculated Times is defined, false otherwise
26 | bool Update();
27 |
28 | ///
29 | /// Flip the note according to the method specified
30 | ///
31 | /// UpSideDown, LeftToRight, Clockwise90/180, Counterclockwise90/180
32 | void Flip(NoteEnum.FlipMethod method);
33 |
34 | ///
35 | /// Give time stamp given overall tick
36 | ///
37 | /// Note.Bar*384+Note.Tick
38 | /// Appropriate time stamp in seconds
39 | double GetTimeStamp(int overallTick);
40 | }
--------------------------------------------------------------------------------
/Note/MeasureChange.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | using static NoteEnum;
4 | using static ChartEnum;
5 |
6 | ///
7 | /// Defines measure change note that indicates a measure change in bar.
8 | ///
9 | public class MeasureChange : Note
10 | {
11 | #region Constructors
12 |
13 | ///
14 | /// Construct Empty
15 | ///
16 | public MeasureChange()
17 | {
18 | NoteType = NoteType.MEASURE;
19 | Tick = 0;
20 | Quaver = 0;
21 | Update();
22 | }
23 |
24 | ///
25 | /// Construct BPMChange with given bar, tick, BPM
26 | ///
27 | /// Bar
28 | /// Tick
29 | /// Quaver
30 | public MeasureChange(int bar, int tick, int quaver)
31 | {
32 | NoteType = NoteType.MEASURE;
33 | Bar = bar;
34 | Tick = tick;
35 | Quaver = quaver;
36 | Update();
37 | }
38 |
39 | ///
40 | /// Construct measureChange from another takeIn
41 | ///
42 | /// Another measure change note
43 | public MeasureChange(MeasureChange takeIn)
44 | {
45 | NoteType = NoteType.MEASURE;
46 | Bar = takeIn.Bar;
47 | Tick = takeIn.Tick;
48 | Quaver = takeIn.Quaver;
49 | Update();
50 | }
51 |
52 | #endregion
53 |
54 | ///
55 | /// Return this.quaver
56 | ///
57 | /// Quaver
58 | public int Quaver { get; }
59 |
60 | public override NoteEnum.NoteGenre NoteGenre => NoteEnum.NoteGenre.MEASURE;
61 |
62 | public override bool IsNote => false;
63 |
64 | public override NoteEnum.NoteSpecificGenre NoteSpecificGenre => NoteEnum.NoteSpecificGenre.MEASURE;
65 |
66 | public override bool CheckValidity()
67 | {
68 | return Quaver > 0;
69 | }
70 |
71 | public override string Compose(ChartVersion format)
72 | {
73 | switch (format)
74 | {
75 | case ChartVersion.Simai:
76 | case ChartVersion.SimaiFes:
77 | return "{" + Quaver + "}";
78 | case ChartVersion.Debug:
79 | return "{" + Quaver + "_" + Tick + "}";
80 | default:
81 | return "";
82 | }
83 | }
84 |
85 | public override Note NewInstance()
86 | {
87 | Note result = new MeasureChange(this);
88 | return result;
89 | }
90 | }
--------------------------------------------------------------------------------
/Note/Rest.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | using static NoteEnum;
4 | using static ChartEnum;
5 |
6 | ///
7 | /// Construct Rest Note solely for Simai
8 | ///
9 | public class Rest : Note
10 | {
11 | #region Constructors
12 |
13 | ///
14 | /// Construct empty
15 | ///
16 | public Rest()
17 | {
18 | NoteType = NoteType.RST;
19 | Bar = 0;
20 | Tick = 0;
21 | Update();
22 | }
23 |
24 | ///
25 | /// Construct Rest Note with given information
26 | ///
27 | /// Bar to take in
28 | /// Start to take in
29 | public Rest(int bar, int startTime)
30 | {
31 | NoteType = NoteType.RST;
32 | Bar = bar;
33 | Tick = startTime;
34 | Update();
35 | }
36 |
37 | ///
38 | /// Construct with Note provided
39 | ///
40 | /// Note to take in
41 | public Rest(Note n)
42 | {
43 | NoteType = NoteType.RST;
44 | Bar = n.Bar;
45 | Tick = n.Tick;
46 | BPMChangeNotes = n.BPMChangeNotes;
47 | Update();
48 | }
49 |
50 | #endregion
51 |
52 | public override NoteGenre NoteGenre => NoteGenre.REST;
53 |
54 | public override bool IsNote => false;
55 |
56 | public override NoteSpecificGenre NoteSpecificGenre => NoteSpecificGenre.REST;
57 |
58 | public override bool CheckValidity()
59 | {
60 | throw new NotImplementedException();
61 | }
62 |
63 | public override string Compose(ChartVersion format)
64 | {
65 | // return "r_" + this.Tick;
66 | switch (format)
67 | {
68 | case ChartVersion.Debug:
69 | return "r_" + Tick;
70 | default:
71 | return "";
72 | }
73 | }
74 |
75 | public override Note NewInstance()
76 | {
77 | Note result = new Rest(this);
78 | return result;
79 | }
80 | }
--------------------------------------------------------------------------------
/Note/Slide.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | using static MaiLib.NoteEnum;
4 | using static MaiLib.ChartEnum;
5 |
6 | ///
7 | /// Construct a Slide note (With START!)
8 | ///
9 | public class Slide : Note
10 | {
11 | #region Constructors
12 |
13 | ///
14 | /// Empty Constructor
15 | ///
16 | public Slide()
17 | {
18 | }
19 |
20 | ///
21 | /// Construct a Slide Note (Valid only if Start Key matches a start!)
22 | ///
23 | ///
24 | /// SI_(Straight),SCL,SCR,SV_(Line not intercepting Crossing Center),SUL,SUR,SF_(Wifi),SLL(Infecting
25 | /// Line),SLR(Infecting),SXL(Self winding),SXR(Self winding),SSL,SSR
26 | ///
27 | /// 0-7
28 | /// Bar in
29 | /// Start Time
30 | /// Last Time
31 | /// 0-7
32 | public Slide(NoteType noteType, int bar, int startTick, string key, int waitLength, int lastLength, string endKey)
33 | {
34 | NoteType = noteType;
35 | Key = key;
36 | Bar = bar;
37 | Tick = startTick;
38 | WaitLength = waitLength;
39 | LastLength = lastLength;
40 | EndKey = endKey;
41 | // Delayed = WaitLength != 96;
42 | Update();
43 | }
44 |
45 | ///
46 | /// Construct a Slide Note (Valid only if Start Key matches a start!)
47 | ///
48 | ///
49 | /// SI_(Straight),SCL,SCR,SV_(Line not intercepting Crossing Center),SUL,SUR,SF_(Wifi),SLL(Infecting
50 | /// Line),SLR(Infecting),SXL(Self winding),SXR(Self winding),SSL,SSR
51 | ///
52 | /// 0-7
53 | /// Bar in
54 | /// Start Time
55 | /// Wait Time in Seconds
56 | /// Last Time in Seconds
57 | /// 0-7
58 | public Slide(NoteType noteType, int bar, int startTick, double waitTime, double lastTime, string key, string endKey)
59 | {
60 | NoteType = noteType;
61 | Key = key;
62 | Bar = bar;
63 | Tick = startTick;
64 | CalculatedWaitTime = waitTime;
65 | CalculatedLastTime = lastTime;
66 | EndKey = endKey;
67 | // Delayed = WaitLength != 96;
68 | // Update(); // This update could cause issue when change table is not yet assigned or update.
69 | }
70 |
71 | ///
72 | /// Construct a Slide from another note
73 | ///
74 | /// The intake note
75 | public Slide(Note inTake)
76 | {
77 | inTake.CopyOver(this);
78 | }
79 |
80 | #endregion
81 |
82 | public override bool Delayed => WaitLength != (Definition / 4);
83 |
84 | public override NoteGenre NoteGenre => NoteGenre.SLIDE;
85 |
86 | public override bool IsNote => true;
87 |
88 | public override NoteSpecificGenre NoteSpecificGenre => NoteSpecificGenre.SLIDE;
89 |
90 | //TODO: REWRITE THIS
91 | public override bool CheckValidity()
92 | {
93 | return true;
94 | }
95 |
96 | public override string Compose(ChartVersion format)
97 | {
98 | string? result = "";
99 | switch (format)
100 | {
101 | case ChartVersion.Simai:
102 | case ChartVersion.SimaiFes:
103 | default:
104 | switch (NoteType)
105 | {
106 | #region DetailedSlideTypes
107 |
108 | case NoteType.SI_:
109 | result += "-";
110 | break;
111 | case NoteType.SV_:
112 | result += "v";
113 | break;
114 | case NoteType.SF_:
115 | result += "w";
116 | break;
117 | case NoteType.SCL:
118 | if (int.Parse(Key) == 0 || int.Parse(Key) == 1 || int.Parse(Key) == 6 || int.Parse(Key) == 7)
119 | result += "<";
120 | else
121 | result += ">";
122 | break;
123 | case NoteType.SCR:
124 | if (int.Parse(Key) == 0 || int.Parse(Key) == 1 || int.Parse(Key) == 6 || int.Parse(Key) == 7)
125 | result += ">";
126 | else
127 | result += "<";
128 | break;
129 | case NoteType.SUL:
130 | result += "p";
131 | break;
132 | case NoteType.SUR:
133 | result += "q";
134 | break;
135 | case NoteType.SSL:
136 | result += "s";
137 | break;
138 | case NoteType.SSR:
139 | result += "z";
140 | break;
141 | case NoteType.SLL:
142 | result += "V" + GenerateInflection(this);
143 | break;
144 | case NoteType.SLR:
145 | result += "V" + GenerateInflection(this);
146 | break;
147 | case NoteType.SXL:
148 | result += "pp";
149 | break;
150 | case NoteType.SXR:
151 | result += "qq";
152 | break;
153 |
154 | #endregion
155 | }
156 |
157 | result += (EndKeyNum + 1).ToString();
158 | if (NoteSpecialState == SpecialState.Break)
159 | result += "b";
160 | else if (NoteSpecialState == SpecialState.EX)
161 | result += "x";
162 | else if (NoteSpecialState == SpecialState.BreakEX) result += "bx";
163 | if (TickBPMDisagree || Delayed)
164 | {
165 | //result += GenerateAppropriateLength(this.LastLength, this.BPM);
166 | if (NoteSpecialState != SpecialState.ConnectingSlide)
167 | result += GenerateAppropriateLength(LastLength, BPM);
168 | else result += GenerateAppropriateLength(FixedLastLength); //TODO: FIX THIS LAST LENGTH
169 | }
170 | else
171 | {
172 | result += GenerateAppropriateLength(LastLength);
173 | }
174 |
175 | if (format is ChartVersion.Debug)
176 | {
177 | result += "_" + this.Tick;
178 | result += "_" + this.Key;
179 | }
180 |
181 | break;
182 | case ChartVersion.Ma2_103:
183 | result = NoteType + "\t" + Bar + "\t" + Tick + "\t" + Key + "\t" + WaitLength + "\t" + LastLength +
184 | "\t" +
185 | EndKey;
186 | break;
187 | case ChartVersion.Ma2_104:
188 | switch (NoteSpecialState)
189 | {
190 | case SpecialState.EX:
191 | result += "EX";
192 | break;
193 | case SpecialState.Break:
194 | result += "BR";
195 | break;
196 | case SpecialState.BreakEX:
197 | result += "BX";
198 | break;
199 | case SpecialState.ConnectingSlide:
200 | result += "CN";
201 | break;
202 | default:
203 | result += "NM";
204 | break;
205 | }
206 |
207 | result += NoteType + "\t" + Bar + "\t" + Tick + "\t" + Key + "\t" + WaitLength + "\t" + LastLength +
208 | "\t" +
209 | EndKey;
210 | break;
211 | }
212 |
213 | return result;
214 | }
215 |
216 | ///
217 | /// Return inflection point of SLL and SLR
218 | ///
219 | /// This note
220 | /// Infection point of this note
221 | public static int GenerateInflection(Note x)
222 | {
223 | int result = x.KeyNum + 1;
224 | if (x.NoteType is NoteType.SLR)
225 | result += 2;
226 | else if (x.NoteType is NoteType.SLL) result -= 2;
227 |
228 | if (result > 8)
229 | result -= 8;
230 | else if (result < 1) result += 8;
231 |
232 | if (result == x.KeyNum + 1 || result == x.EndKeyNum + 1)
233 | {
234 | //Deal with result;
235 | if (result > 4)
236 | result -= 4;
237 | else if (result <= 4) result += 4;
238 |
239 | //Deal with note type;
240 | if (x.NoteType is NoteType.SLL)
241 | x.NoteType = NoteType.SLR;
242 | else if (x.NoteType is NoteType.SLR)
243 | x.NoteType = NoteType.SLL;
244 | else
245 | throw new InvalidDataException("INFLECTION CANNOT BE USED OTHER THAN SLL AND SLR!");
246 | }
247 |
248 | return result;
249 | }
250 |
251 | public override Note NewInstance()
252 | {
253 | Note result = new Slide(this);
254 | return result;
255 | }
256 | }
--------------------------------------------------------------------------------
/Note/SlideEachSet.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | using static MaiLib.NoteEnum;
4 | using static MaiLib.ChartEnum;
5 |
6 | public class SlideEachSet : Note
7 | {
8 | public List InternalSlides;
9 |
10 | #region Constructors
11 |
12 | public SlideEachSet()
13 | {
14 | InternalSlides = [];
15 | Update();
16 | }
17 |
18 | public SlideEachSet(Note x) : base(x)
19 | {
20 | SlideStart = x.NoteSpecificGenre is NoteSpecificGenre.SLIDE_START ? x : null;
21 | InternalSlides = [];
22 | if (x.NoteSpecificGenre is NoteSpecificGenre.SLIDE_EACH)
23 | {
24 | SlideEachSet? candidate =
25 | x as SlideEachSet ?? throw new InvalidOperationException("This is not a SLIDE EACH");
26 | InternalSlides.AddRange(candidate.InternalSlides);
27 | SlideStart = candidate.SlideStart;
28 | }
29 | else if (x.NoteSpecificGenre is NoteSpecificGenre.SLIDE)
30 | {
31 | Slide? candidate = x as Slide ?? throw new InvalidOperationException("This is not a SLIDE");
32 | InternalSlides.Add(candidate);
33 | }
34 | else if (x.NoteSpecificGenre is NoteSpecificGenre.SLIDE_GROUP)
35 | {
36 | SlideGroup? candidate = x as SlideGroup ?? throw new InvalidOperationException("This is not a SLIDE GROUP");
37 | InternalSlides.Add(candidate);
38 | }
39 |
40 | Update();
41 | }
42 |
43 | public SlideEachSet(int bar, int startTime, Note slideStart, List internalSlides) : base(slideStart)
44 | {
45 | SlideStart = slideStart;
46 | InternalSlides = new List(internalSlides);
47 | if (internalSlides.Count > 0)
48 | {
49 | EndKey = InternalSlides.Last().EndKey;
50 | NoteType = NoteType.SLIDE_EACH;
51 | Bar = bar;
52 | Tick = startTime;
53 | WaitLength = InternalSlides.Last().WaitLength;
54 | LastLength = InternalSlides.Last().LastLength;
55 | // Delayed = WaitLength != 96;
56 | }
57 |
58 | Update();
59 | }
60 |
61 | #endregion
62 |
63 | public Note? SlideStart { get; set; }
64 | public override NoteSpecificGenre NoteSpecificGenre => NoteSpecificGenre.SLIDE_EACH;
65 |
66 | public override NoteGenre NoteGenre => NoteGenre.SLIDE;
67 |
68 | public Note? FirstIdentifier
69 | {
70 | get
71 | {
72 | if (SlideStart != null) return SlideStart;
73 | return InternalSlides.Count > 0 ? InternalSlides.First() : null;
74 | }
75 | }
76 |
77 | public Note? FirstSlide => InternalSlides.First();
78 | public Note? LastSlide => InternalSlides.Last();
79 |
80 | public override bool IsNote => true;
81 |
82 | public void AddCandidateNote(Tap x)
83 | {
84 | if (x.NoteSpecificGenre is NoteSpecificGenre.SLIDE_START && (InternalSlides.Count == 0 ||
85 | (InternalSlides.Count > 0 &&
86 | InternalSlides.First().Key.Equals(x.Key) &&
87 | x.IsOfSameTime(InternalSlides.First()))))
88 | SlideStart = x;
89 | else throw new InvalidOperationException("THE INTAKE NOTE IS NOT VALID SLIDE START");
90 | }
91 |
92 | public void AddCandidateNote(Slide x)
93 | {
94 | if ((SlideStart == null && InternalSlides.Count == 0) ||
95 | (SlideStart != null && x.Key.Equals(SlideStart.Key) && x.IsOfSameTime(SlideStart)) ||
96 | (InternalSlides.Count > 0 && InternalSlides.First().Key.Equals(x.Key) &&
97 | x.IsOfSameTime(InternalSlides.First())))
98 | InternalSlides.Add(x);
99 | else throw new InvalidOperationException("THE INTAKE NOTE IS NOT VALID SLIDE OR THIS NOTE IS NOT VALID");
100 | }
101 |
102 | public void AddCandidateNote(List x)
103 | {
104 | foreach (Slide? candidate in x) AddCandidateNote(candidate);
105 | }
106 |
107 | public bool TryAddCandidateNote(Tap x)
108 | {
109 | bool result = false;
110 | if (FirstIdentifier != null && x.Key.Equals(FirstIdentifier.Key) && FirstIdentifier.IsOfSameTime(x))
111 | {
112 | result = true;
113 | AddCandidateNote(x);
114 | }
115 |
116 | return result;
117 | }
118 |
119 | public bool TryAddCandidateNote(Slide x)
120 | {
121 | bool result = false;
122 | if (FirstIdentifier != null && x.Key.Equals(FirstIdentifier.Key) && FirstIdentifier.IsOfSameTime(x))
123 | {
124 | result = true;
125 | AddCandidateNote(x);
126 | }
127 |
128 | return result;
129 | }
130 |
131 | public bool TryAddCandidateNote(List x)
132 | {
133 | bool result = false;
134 | foreach (Slide? candidate in x)
135 | if (FirstIdentifier != null && candidate.Key.Equals(FirstIdentifier.Key) &&
136 | FirstIdentifier.IsOfSameTime(candidate))
137 | {
138 | result = result || true;
139 | AddCandidateNote(candidate);
140 | }
141 |
142 | return result;
143 | }
144 |
145 | public override bool CheckValidity()
146 | {
147 | bool result = SlideStart == null || SlideStart.NoteSpecificGenre is NoteSpecificGenre.SLIDE_START;
148 |
149 | if (SlideStart == null && InternalSlides == null) result = false;
150 | else if (SlideStart != null && InternalSlides.Count > 0 &&
151 | !InternalSlides.First().IsOfSameTime(SlideStart)) result = false;
152 | else if (InternalSlides.Count > 0)
153 | foreach (Slide? x in InternalSlides)
154 | {
155 | Note? referencingNote = SlideStart == null ? InternalSlides.First() : SlideStart;
156 | result = result && x.IsOfSameTime(referencingNote);
157 | }
158 |
159 | return result;
160 | }
161 |
162 | public override void Flip(FlipMethod method)
163 | {
164 | if (SlideStart != null) SlideStart.Flip(method);
165 | for (int i = 0; i < InternalSlides.Count; i++) InternalSlides[i].Flip(method);
166 | Update();
167 | }
168 |
169 | public bool ContainsSlide(Note slide)
170 | {
171 | return InternalSlides.Contains(slide);
172 | }
173 |
174 | public override string Compose(ChartVersion format)
175 | {
176 | string? result = "";
177 | string? separateSymbol = "";
178 | switch (format)
179 | {
180 | case ChartVersion.Simai:
181 | case ChartVersion.SimaiFes:
182 | separateSymbol = "*";
183 | if (InternalSlides.Count == 0 && SlideStart != null) result += SlideStart.Compose(format) + "$";
184 | else if (InternalSlides.Count > 0 && SlideStart == null)
185 | result +=
186 | new Tap(InternalSlides.First()) { NoteSpecialState = SpecialState.Normal }.Compose(format) +
187 | "?";
188 | else if (SlideStart != null) result += SlideStart.Compose(format);
189 | for (int i = 0; i < InternalSlides.Count; i++)
190 | if (i < InternalSlides.Count - 1)
191 | result += InternalSlides[i].Compose(format) + separateSymbol;
192 | else result += InternalSlides[i].Compose(format);
193 | break;
194 | case ChartVersion.Ma2_103:
195 | case ChartVersion.Ma2_104:
196 | if (SlideStart != null) SlideStart.Compose(format);
197 | foreach (Slide x in InternalSlides)
198 | {
199 | x.Compose(format);
200 | }
201 |
202 | break;
203 | default:
204 | if (InternalSlides.Count == 0 && SlideStart != null) result += SlideStart.Compose(format) + "\n";
205 | else if (InternalSlides.Count > 0 && SlideStart == null)
206 | result +=
207 | new Tap(InternalSlides.First()) { NoteSpecialState = SpecialState.Normal }.Compose(format) +
208 | "\n";
209 | else if (SlideStart != null) result += SlideStart.Compose(format);
210 | separateSymbol = "\n";
211 | for (int i = 0; i < InternalSlides.Count; i++)
212 | if (i < InternalSlides.Count - 1)
213 | result += InternalSlides[i].Compose(format) + separateSymbol;
214 | else result += InternalSlides[i].Compose(format);
215 | break;
216 | }
217 |
218 | return result;
219 | }
220 |
221 | public override Note NewInstance()
222 | {
223 | return new SlideEachSet(this);
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/Note/SlideGroup.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | using static NoteEnum;
4 | using static ChartEnum;
5 |
6 | public class SlideGroup : Slide
7 | {
8 | #region Constructors
9 |
10 | public SlideGroup()
11 | {
12 | InternalSlides = [];
13 | NoteSpecialState = SpecialState.Normal;
14 | Update();
15 | }
16 |
17 | public SlideGroup(Note inTake) : base(inTake)
18 | {
19 | InternalSlides =
20 | [
21 | (Slide)inTake
22 | ];
23 | NoteSpecialState = inTake.NoteSpecialState;
24 | Update();
25 | }
26 |
27 | public SlideGroup(List slideCandidate)
28 | {
29 | InternalSlides = [.. slideCandidate];
30 | NoteSpecialState = slideCandidate.First().NoteSpecialState;
31 | Update();
32 | }
33 |
34 | #endregion
35 |
36 | public int SlideCount => InternalSlides.Count;
37 |
38 | public override NoteSpecificGenre NoteSpecificGenre => NoteSpecificGenre.SLIDE_GROUP;
39 |
40 | public List InternalSlides { get; }
41 |
42 | public Slide FirstSlide => InternalSlides.First();
43 | public Slide LastSlide => InternalSlides.Last();
44 |
45 | public void AddConnectingSlide(Slide candidate)
46 | {
47 | InternalSlides.Add(candidate);
48 | Update();
49 | }
50 |
51 | public override void Flip(FlipMethod method)
52 | {
53 | foreach (Slide? x in InternalSlides)
54 | x.Flip(method);
55 | Update();
56 | }
57 |
58 | public bool ContainsSlide(Note slide)
59 | {
60 | return InternalSlides.Contains(slide);
61 | }
62 |
63 | ///
64 | /// By default this does not compose festival format - compose all internal slide in this
. Also, since
65 | /// this contradicts with the ma2 note ordering, this method cannot compose in ma2 format.
66 | ///
67 | /// 0 if simai, 1 if ma2
68 | /// the composed simai slide group
69 | public override string Compose(ChartVersion format)
70 | {
71 | string? result = "";
72 | foreach (Slide? x in InternalSlides)
73 | result += x.Compose(format);
74 |
75 | return result;
76 | }
77 |
78 | public override bool Equals(object? obj)
79 | {
80 | bool result = (this == null && obj == null) || (this != null && obj != null);
81 | if (result && obj != null)
82 | for (int i = 0; i < InternalSlides.Count; i++)
83 | {
84 | SlideGroup? localGroup = (SlideGroup)obj;
85 | result = result && InternalSlides[i].Equals(localGroup.InternalSlides[i]);
86 | }
87 |
88 | return result;
89 | }
90 |
91 | public override int GetHashCode()
92 | {
93 | return base.GetHashCode();
94 | }
95 |
96 | public override bool Update()
97 | {
98 | // base.Update();
99 | if (InternalSlides.Any(slide => slide.NoteSpecialState is SpecialState.Break))
100 | NoteSpecialState = SpecialState.Break;
101 | if (InternalSlides.Count > 0) InternalSlides.First().NoteSpecialState = NoteSpecialState;
102 | bool result = false;
103 | if (SlideCount > 0 && InternalSlides.Last().LastLength == 0)
104 | throw new InvalidOperationException("THE LAST SLIDE IN THIS GROUP DOES NOT HAVE LAST TIME ASSIGNED");
105 | if (SlideCount > 0 && Key != null)
106 | {
107 | foreach (Slide? x in InternalSlides)
108 | if (x.LastLength == 0)
109 | x.LastLength = InternalSlides.Last().LastLength;
110 |
111 | while (Tick >= Definition)
112 | {
113 | Tick -= Definition;
114 | Bar++;
115 | }
116 |
117 | // string noteInformation = "This note is "+this.NoteType+", in tick "+ this.tickStamp+", ";
118 | //this.tickTimeStamp = this.GetTimeStamp(this.tickStamp);
119 | int totalWaitLength = 0;
120 | int totalLastLength = 0;
121 | foreach (Slide? x in InternalSlides)
122 | {
123 | totalWaitLength += x.WaitLength;
124 | totalLastLength += x.LastLength;
125 | }
126 |
127 | WaitTickStamp = TickStamp + totalWaitLength;
128 | //this.waitTimeStamp = this.GetTimeStamp(this.waitTickStamp);
129 | LastTickStamp = WaitTickStamp + totalLastLength;
130 | //this.lastTimeStamp = this.GetTimeStamp(this.lastTickStamp);
131 | if (CalculatedLastTime > 0 && CalculatedWaitTime > 0) result = true;
132 | }
133 |
134 | if (SlideCount > 0 && (Key == null || Key != InternalSlides[0].Key))
135 | {
136 | Note inTake = InternalSlides[0];
137 | inTake.CopyOver(this);
138 | }
139 |
140 | return result;
141 | }
142 | }
--------------------------------------------------------------------------------
/Note/Tap.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | using static MaiLib.NoteEnum;
4 | using static MaiLib.ChartEnum;
5 |
6 | ///
7 | /// Tap note
8 | ///
9 | public class Tap : Note
10 | {
11 | ///
12 | /// Empty Constructor Tap Note
13 | ///
14 | public Tap()
15 | {
16 | TouchSize = "M1";
17 | SpecialEffect = false;
18 | Update();
19 | }
20 |
21 | #region Constructor
22 |
23 | ///
24 | /// Construct a Tap note
25 | ///
26 | /// TAP,STR,BRK,BST,XTP,XST,TTP; NST or NSS
27 | /// 0-7 representing each key
28 | /// Bar location
29 | /// Start Location
30 | public Tap(NoteType noteType, int bar, int startTime, string key)
31 | {
32 | NoteType = noteType;
33 | Key = key;
34 | Bar = bar;
35 | Tick = startTime;
36 | SpecialEffect = false;
37 | TouchSize = "M1";
38 | Update();
39 | }
40 |
41 | ///
42 | /// Construct a Touch note with parameter taken in
43 | ///
44 | /// "TTP"
45 | /// Bar location
46 | /// Start Location
47 | /// Key
48 | /// Effect after touch
49 | /// L=larger notes M=Regular
50 | public Tap(NoteType noteType, int bar, int startTime, string key, bool specialEffect, string touchSize)
51 | {
52 | NoteType = noteType;
53 | Key = key;
54 | Bar = bar;
55 | Tick = startTime;
56 | SpecialEffect = specialEffect;
57 | TouchSize = touchSize;
58 | Update();
59 | }
60 |
61 | ///
62 | /// Construct a Tap note form another note
63 | ///
64 | /// The intake note
65 | /// Will raise exception if touch size is null
66 | public Tap(Note inTake)
67 | {
68 | inTake.CopyOver(this);
69 | //NoteType = inTake.NoteGenre.Equals("TAP") ? inTake.NoteType : "TAP";
70 | if (inTake.NoteGenre is NoteEnum.NoteGenre.TAP)
71 | {
72 | TouchSize = ((Tap)inTake).TouchSize ?? throw new NullReferenceException();
73 | SpecialEffect = ((Tap)inTake).SpecialEffect;
74 | }
75 | else
76 | {
77 | TouchSize = "M1";
78 | SpecialEffect = false;
79 | NoteType = NoteType.TAP;
80 | }
81 | }
82 |
83 | #endregion
84 |
85 | public override NoteGenre NoteGenre => NoteGenre.TAP;
86 |
87 | public override bool IsNote =>
88 | // if (this.NoteType.Equals("NST"))
89 | // {
90 | // return false;
91 | // }
92 | // else return true;
93 | true;
94 |
95 | public override NoteSpecificGenre NoteSpecificGenre
96 | {
97 | get
98 | {
99 | NoteSpecificGenre result;
100 | switch (NoteType)
101 | {
102 | case NoteType.STR:
103 | case NoteType.NST:
104 | case NoteType.NSS:
105 | result = NoteSpecificGenre.SLIDE_START;
106 | break;
107 | case NoteType.TTP:
108 | case NoteType.TAP:
109 | default:
110 | result = NoteSpecificGenre.TAP;
111 | break;
112 | }
113 |
114 | return result;
115 | }
116 | }
117 |
118 | //TODO: REWRITE THIS
119 | public override bool CheckValidity()
120 | {
121 | return true;
122 | }
123 |
124 | public override string Compose(ChartVersion format)
125 | {
126 | string? result = "";
127 | switch (format)
128 | {
129 | case ChartVersion.Simai:
130 | case ChartVersion.SimaiFes:
131 | default:
132 | switch (NoteType)
133 | {
134 | case NoteType.NST:
135 | result += (KeyNum + 1).ToString() + "!";
136 | break;
137 | case NoteType.NSS:
138 | result += (KeyNum + 1).ToString() + "$";
139 | break;
140 | case NoteType.TTP:
141 | result += KeyGroup + (KeyNum + 1).ToString();
142 | break;
143 | default:
144 | result += (KeyNum + 1).ToString();
145 | break;
146 | }
147 |
148 | if (NoteSpecialState == SpecialState.Break)
149 | result += "b";
150 | else if (NoteSpecialState == SpecialState.EX)
151 | result += "x";
152 | else if (NoteSpecialState == SpecialState.BreakEX) result += "bx";
153 | if (SpecialEffect) result += "f";
154 | if (format is ChartVersion.Debug) result += "_" + Tick;
155 | break;
156 | case ChartVersion.Ma2_103:
157 | string typeCandidate = NoteType.ToString();
158 | switch (NoteSpecialState)
159 | {
160 | case SpecialState.EX:
161 | typeCandidate = NoteSpecificGenre is NoteSpecificGenre.SLIDE_START ? "XST" : "XTP";
162 | break;
163 | case SpecialState.Break:
164 | case SpecialState.BreakEX:
165 | typeCandidate = NoteSpecificGenre is NoteSpecificGenre.SLIDE_START ? "BST" : "BRK";
166 | break;
167 | }
168 |
169 | result = NoteType is NoteType.TTP
170 | ? typeCandidate + "\t" +
171 | Bar + "\t" +
172 | Tick + "\t" +
173 | KeyNum + "\t" +
174 | KeyGroup + "\t" +
175 | (SpecialEffect ? 1 : 0) + "\t" +
176 | TouchSize
177 | : typeCandidate + "\t" + Bar + "\t" + Tick + "\t" + Key;
178 | break;
179 | case ChartVersion.Ma2_104:
180 | typeCandidate = NoteType.ToString();
181 | switch (NoteSpecialState)
182 | {
183 | case SpecialState.EX:
184 | typeCandidate = "EX" + typeCandidate;
185 | break;
186 | case SpecialState.Break:
187 | typeCandidate = "BR" + typeCandidate;
188 | break;
189 | case SpecialState.BreakEX:
190 | typeCandidate = "BX" + typeCandidate;
191 | break;
192 | default:
193 | typeCandidate = "NM" + typeCandidate;
194 | break;
195 | }
196 |
197 | result = NoteType is NoteType.TTP
198 | ? typeCandidate + "\t" +
199 | Bar + "\t" +
200 | Tick + "\t" +
201 | KeyNum + "\t" +
202 | KeyGroup + "\t" +
203 | (SpecialEffect ? 1 : 0) + "\t" +
204 | TouchSize
205 | : typeCandidate + "\t" + Bar + "\t" + Tick + "\t" + Key;
206 | break;
207 | }
208 |
209 | return result;
210 | }
211 |
212 | public override Note NewInstance()
213 | {
214 | Note result = new Tap(this);
215 | return result;
216 | }
217 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/BPM.cs:
--------------------------------------------------------------------------------
1 | using static MaiLib.ChartEnum;
2 |
3 | namespace MaiLib;
4 |
5 | public class BPM : ICodeBlock
6 | {
7 | public double Speed { get; private set; }
8 |
9 | public BPM(double bpm)
10 | {
11 | Speed = bpm;
12 | }
13 |
14 | public BPM(string bpm)
15 | {
16 | Speed = double.Parse(bpm);
17 | }
18 |
19 | public string Compose(ChartVersion chartVersion)
20 | {
21 | switch (chartVersion)
22 | {
23 | case ChartVersion.Simai:
24 | case ChartVersion.SimaiFes:
25 | default:
26 | return $"({Math.Round(Speed, 4)})";
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/BeginSeq.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using static MaiLib.ChartEnum;
3 |
4 | namespace MaiLib;
5 |
6 | public class BeginSeq : ICodeBlock
7 | {
8 | public BPM BPM { get; private set; }
9 | public Measure Measure { get; private set; }
10 | public NoteSeq NoteSeq { get; protected set; }
11 |
12 | public BeginSeq()
13 | {
14 | BPM = new BPM(120);
15 | Measure = new Measure(4);
16 | NoteSeq = new NoteSeq();
17 | }
18 |
19 | public BeginSeq(BPMChange bpm, MeasureChange measure, NoteSeq noteSeq)
20 | {
21 | BPM = new BPM(120);
22 | Measure = new Measure(4);
23 | NoteSeq = new NoteSeq();
24 | }
25 |
26 | public string Compose(ChartVersion chartVersion)
27 | {
28 | StringBuilder result = new StringBuilder();
29 | result.Append(BPM.Compose(chartVersion));
30 | result.Append(Measure.Compose(chartVersion));
31 | result.Append(NoteSeq.Compose(chartVersion));
32 | result.Append("E\n");
33 | return result.ToString();
34 | }
35 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/ChartDef.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using static MaiLib.ChartEnum;
3 | using static MaiLib.ICodeBlock;
4 |
5 | namespace MaiLib;
6 |
7 | public class ChartDef : ICodeBlock
8 | {
9 | public BPM? BPM { get; private set; }
10 | public Measure? Measure { get; private set; }
11 |
12 | public ChartDef(BPM bpm)
13 | {
14 | BPM = bpm;
15 | }
16 |
17 | public ChartDef(Measure measure)
18 | {
19 | Measure = measure;
20 | }
21 |
22 | public ChartDef(BPM bpm, Measure measure)
23 | {
24 | BPM = bpm;
25 | Measure = measure;
26 | }
27 |
28 | public string Compose(ChartVersion chartVersion)
29 | {
30 | if (BPM is null && Measure is null) throw new ComponentMissingException("CHART-DEF", "BPM, MEASURE");
31 | StringBuilder builder = new();
32 | if (BPM is not null) builder.Append(BPM.Compose(chartVersion));
33 | if (Measure is not null) builder.Append(Measure.Compose(chartVersion));
34 | return builder.ToString();
35 | }
36 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/HoldComp.cs:
--------------------------------------------------------------------------------
1 | using static MaiLib.ChartEnum;
2 |
3 | namespace MaiLib;
4 |
5 | public class HoldComp : ICodeBlock
6 | {
7 | public TapComp TapComp { get; private set; }
8 | public HoldDuration HoldDuration { get; set; }
9 |
10 | public HoldComp(TapComp tapComp, HoldDuration holdDuration)
11 | {
12 | TapComp = tapComp;
13 | HoldDuration = holdDuration;
14 | }
15 |
16 | public string Compose(ChartVersion chartVersion)
17 | {
18 | return TapComp.Compose(chartVersion) + "h" + HoldDuration.Compose(chartVersion);
19 | }
20 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/HoldDuration.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | public class HoldDuration : ICodeBlock
4 | {
5 | public MeasureDuration? MeasureDuration { get; private set; }
6 | public TimeDuration? TimeDuration { get; private set; }
7 |
8 | public HoldDuration(MeasureDuration measureDuration)
9 | {
10 | MeasureDuration = measureDuration;
11 | }
12 |
13 | public HoldDuration(TimeDuration timeDuration)
14 | {
15 | TimeDuration = timeDuration;
16 | }
17 |
18 | public string Compose(ChartEnum.ChartVersion chartVersion)
19 | {
20 | if (MeasureDuration is not null)
21 | {
22 | return "[" + MeasureDuration.Compose(chartVersion) + "]";
23 | }
24 | else if (TimeDuration is not null)
25 | {
26 | return "[" + TimeDuration.Compose(chartVersion) + "]";
27 | }
28 | else throw new ICodeBlock.ComponentMissingException("HOLD-DURATION", "MEASURE-DURATION OR TIME-DURATION");
29 | }
30 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/ICodeBlock.cs:
--------------------------------------------------------------------------------
1 | using static MaiLib.ChartEnum;
2 |
3 | namespace MaiLib;
4 |
5 | public interface ICodeBlock
6 | {
7 | public string Compose(ChartVersion chartVersion);
8 |
9 | public class ComponentMissingException : Exception
10 | {
11 | public ComponentMissingException(string codeBlock, string missedComponents) : base(
12 | String.Format("CODE BLOCK {0} MISSED FOLLOWING COMPONENTS: {1}", codeBlock, missedComponents))
13 | {
14 | }
15 | }
16 |
17 | public class ExcessiveComponentsException : Exception
18 | {
19 | public ExcessiveComponentsException(string codeBlock, string unexpectedComponents) : base(
20 | String.Format("CODE BLOCK {0} WAS PROVIDED WITH FOLLOWING COMPONENTS MORE THAN EXPECTED: {1}", codeBlock,
21 | unexpectedComponents))
22 | {
23 | }
24 | }
25 |
26 | public class UnexpectedStringSuppliedException : Exception
27 | {
28 | public UnexpectedStringSuppliedException(string codeBlock, string expectedString, string actualString) : base(
29 | String.Format("CODE BLOCK {0} WAS SUPPLIED WITH UNEXPECTED STRING. EXPECTED: {1}; ACTUAL: {2}", codeBlock,
30 | expectedString, actualString))
31 | {
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/Key.cs:
--------------------------------------------------------------------------------
1 | using static MaiLib.ChartEnum;
2 |
3 | namespace MaiLib;
4 |
5 | public class Key : ICodeBlock
6 | {
7 | public int Button { get; private set; }
8 |
9 | public Key(int key)
10 | {
11 | if (key is >= 1 and <= 8)
12 | {
13 | Button = key;
14 | }
15 | else throw new ICodeBlock.UnexpectedStringSuppliedException("KEY", "NUM 1-8", key.ToString());
16 | }
17 |
18 | public Key(string keyInString)
19 | {
20 | int key = int.Parse(keyInString);
21 | if (key is >= 1 and <= 8)
22 | {
23 | Button = key;
24 | }
25 | else throw new ICodeBlock.UnexpectedStringSuppliedException("KEY", "NUM 1-8", key.ToString());
26 | }
27 |
28 | public string Compose(ChartVersion chartVersion)
29 | {
30 | switch (chartVersion)
31 | {
32 | case ChartVersion.Simai:
33 | case ChartVersion.SimaiFes:
34 | default:
35 | return Button.ToString();
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/KeyComp.cs:
--------------------------------------------------------------------------------
1 | using static MaiLib.ChartEnum;
2 |
3 | namespace MaiLib;
4 |
5 | public class KeyComp : ICodeBlock
6 | {
7 | public Key Key1 { get; private set; }
8 | public Key Key2 { get; private set; }
9 |
10 | public KeyComp(Key key1, Key key2)
11 | {
12 | Key1 = key1;
13 | Key2 = key2;
14 | }
15 |
16 | public string Compose(ChartVersion chartVersion)
17 | {
18 | return Key1.Compose(chartVersion) + Key2.Compose(chartVersion);
19 | }
20 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/Measure.cs:
--------------------------------------------------------------------------------
1 | using static MaiLib.ChartEnum;
2 |
3 | namespace MaiLib;
4 |
5 | public class Measure : ICodeBlock
6 | {
7 | public int Quaver { get; private set; }
8 |
9 | public Measure(int quaver)
10 | {
11 | Quaver = quaver;
12 | }
13 |
14 | public Measure(string quaver)
15 | {
16 | Quaver = int.Parse(quaver);
17 | }
18 |
19 | public string Compose(ChartVersion chartVersion)
20 | {
21 | switch (chartVersion)
22 | {
23 | case ChartVersion.Simai:
24 | case ChartVersion.SimaiFes:
25 | default:
26 | return $"{{{Quaver}}}";
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/MeasureDuration.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | public class MeasureDuration : ICodeBlock
4 | {
5 | public int Quaver { get; private set; }
6 | public int Multiple { get; private set; }
7 |
8 | public MeasureDuration(int quaver, int multiple)
9 | {
10 | Quaver = quaver;
11 | Multiple = multiple;
12 | }
13 |
14 | public string Compose(ChartEnum.ChartVersion chartVersion) => $"[{Quaver}:{Multiple}]";
15 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/NormalNote.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using static MaiLib.ChartEnum;
3 |
4 | namespace MaiLib;
5 |
6 | public class NormalNote : ICodeBlock
7 | {
8 | public TapComp? TapComp { get; private set; }
9 | public HoldComp? HoldComp { get; private set; }
10 | public SlideGroupComp? SlideGroupComp { get; private set; }
11 |
12 | public NormalNote(TapComp tapComp)
13 | {
14 | TapComp = tapComp;
15 | }
16 |
17 | public NormalNote(HoldComp holdComp)
18 | {
19 | HoldComp = holdComp;
20 | }
21 |
22 | public NormalNote(SlideGroupComp slidGroupComp)
23 | {
24 | SlideGroupComp = slidGroupComp;
25 | }
26 |
27 | public string Compose(ChartVersion chartVersion)
28 | {
29 | StringBuilder builder = new();
30 | if (TapComp is not null)
31 | {
32 | builder.Append(TapComp.Compose(chartVersion));
33 | }
34 | else if (HoldComp is not null)
35 | {
36 | builder.Append(HoldComp.Compose(chartVersion));
37 | }
38 | else if (SlideGroupComp is not null)
39 | {
40 | builder.Append(SlideGroupComp.Compose(chartVersion));
41 | }
42 | else throw new ICodeBlock.ComponentMissingException("NORMAL-NOTE", "TAP-COMP OR HOLD-COMP OR SLIDE-GROUP-COMP");
43 |
44 | return builder.ToString();
45 | }
46 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/NoteComp.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using static MaiLib.ChartEnum;
3 |
4 | namespace MaiLib;
5 |
6 | public class NoteComp : ICodeBlock
7 | {
8 | public SingleNote? SingleNote { get; private set; }
9 | public NoteComp? InnerNoteComp { get; private set; }
10 | public KeyComp? KeyComp { get; private set; }
11 |
12 | public NoteComp(SingleNote singleNote)
13 | {
14 | SingleNote = singleNote;
15 | }
16 |
17 | public NoteComp(SingleNote singleNote, NoteComp noteComp)
18 | {
19 | SingleNote = singleNote;
20 | InnerNoteComp = noteComp;
21 | }
22 |
23 | public NoteComp(KeyComp keyComp)
24 | {
25 | KeyComp = keyComp;
26 | }
27 |
28 | public string Compose(ChartVersion chartVersion)
29 | {
30 | StringBuilder builder = new();
31 | if (SingleNote is not null)
32 | {
33 | builder.Append(SingleNote.Compose(chartVersion));
34 | if (InnerNoteComp is not null)
35 | {
36 | builder.Append('/');
37 | builder.Append(InnerNoteComp.Compose(chartVersion));
38 | }
39 | }
40 | else if (KeyComp is not null)
41 | {
42 | builder.Append(KeyComp.Compose(chartVersion));
43 | }
44 | else if (InnerNoteComp is not null)
45 | {
46 | throw new ICodeBlock.ComponentMissingException("NOTE-COMP", "SINGLE-NOTE");
47 | }
48 | else throw new ICodeBlock.ComponentMissingException("NOTE-COMP", "SINGLE NOTE, (NOTE-COMP), KEY-COMP");
49 |
50 | return builder.ToString();
51 | }
52 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/NoteSeq.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using static MaiLib.ChartEnum;
3 |
4 | namespace MaiLib;
5 |
6 | public class NoteSeq : ICodeBlock
7 | {
8 | public ChartDef? ChartDef { get; private set; }
9 | public NoteComp? NoteComp { get; private set; }
10 | public NoteSeq? InnerNoteSeq { get; private set; }
11 |
12 | public bool IsSingleComma { get; private set; }
13 |
14 | public NoteSeq(ChartDef chartDef, NoteSeq noteSeq)
15 | {
16 | ChartDef = chartDef;
17 | InnerNoteSeq = noteSeq;
18 | IsSingleComma = false;
19 | }
20 |
21 | public NoteSeq(NoteComp noteComp, NoteSeq noteSeq)
22 | {
23 | NoteComp = noteComp;
24 | InnerNoteSeq = noteSeq;
25 | IsSingleComma = false;
26 | }
27 |
28 | public NoteSeq(NoteComp noteComp)
29 | {
30 | NoteComp = noteComp;
31 | IsSingleComma = false;
32 | }
33 |
34 | public NoteSeq(bool isSingleComma)
35 | {
36 | IsSingleComma = isSingleComma;
37 | }
38 |
39 | public NoteSeq()
40 | {
41 | IsSingleComma = false;
42 | }
43 |
44 | public string Compose(ChartVersion chartVersion)
45 | {
46 | StringBuilder builder = new();
47 | if (ChartDef is not null)
48 | {
49 | if (InnerNoteSeq is not null)
50 | {
51 | builder.Append(ChartDef.Compose(chartVersion));
52 | builder.Append(InnerNoteSeq.Compose(chartVersion));
53 | builder.Append('\n');
54 | }
55 | else throw new ICodeBlock.ComponentMissingException("NOTE-SEQ", "CHART-DEF");
56 | }
57 | else if (NoteComp is not null)
58 | {
59 | builder.Append(NoteComp.Compose(chartVersion));
60 | builder.Append(',');
61 | if (InnerNoteSeq is not null)
62 | {
63 | builder.Append(InnerNoteSeq.Compose(chartVersion));
64 | }
65 | }
66 | else if (IsSingleComma)
67 | {
68 | builder.Append(',');
69 | }
70 |
71 | return builder.ToString();
72 | }
73 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/Sensor.cs:
--------------------------------------------------------------------------------
1 | using static MaiLib.ChartEnum;
2 |
3 | namespace MaiLib;
4 |
5 | public class Sensor : ICodeBlock
6 | {
7 | public string SensorArea { get; private set; }
8 | private readonly string[] _allowedStrings = ["A", "B", "C", "D", "E", "F"];
9 |
10 | public string ExpectedStrings => String.Join(", ", _allowedStrings);
11 |
12 | public Sensor(string suppliedString)
13 | {
14 | if (_allowedStrings.Any(suppliedString.Equals))
15 | {
16 | SensorArea = suppliedString;
17 | }
18 | else throw new ICodeBlock.UnexpectedStringSuppliedException("SENSOR", ExpectedStrings, suppliedString);
19 | }
20 |
21 | public string Compose(ChartVersion chartVersion) => SensorArea;
22 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/SingleNote.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using static MaiLib.ChartEnum;
3 |
4 | namespace MaiLib;
5 |
6 | public class SingleNote : ICodeBlock
7 | {
8 | public TouchNote? TouchNote { get; private set; }
9 | public NormalNote? NormalNote { get; private set; }
10 |
11 | public SingleNote(TouchNote touchNote)
12 | {
13 | TouchNote = touchNote;
14 | }
15 |
16 | public SingleNote(NormalNote normalNote)
17 | {
18 | NormalNote = normalNote;
19 | }
20 |
21 | public string Compose(ChartVersion chartVersion)
22 | {
23 | if (TouchNote is not null)
24 | {
25 | return TouchNote.Compose(chartVersion);
26 | }
27 | else if (NormalNote is not null)
28 | {
29 | return NormalNote.Compose(chartVersion);
30 | }
31 | else throw new ICodeBlock.ComponentMissingException("SINGLE-NOTE", "TOUCH-NOTE OR NORMAL-NOTE");
32 | }
33 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/SlideComp.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using static MaiLib.ChartEnum;
3 |
4 | namespace MaiLib;
5 |
6 | public class SlideComp : ICodeBlock
7 | {
8 | public SlideType SlideType { get; private set; }
9 | public Key Key { get; private set; }
10 | public SlideDuration SlideDuration { get; private set; }
11 |
12 | public SlideComp(SlideType slideType, Key key, SlideDuration slideDuration)
13 | {
14 | SlideType = slideType;
15 | Key = key;
16 | SlideDuration = slideDuration;
17 | }
18 |
19 | public string Compose(ChartVersion chartVersion)
20 | {
21 | StringBuilder builder = new();
22 | builder.Append(SlideType.Compose(chartVersion));
23 | builder.Append(Key.Compose(chartVersion));
24 | builder.Append(SlideDuration.Compose(chartVersion));
25 | return builder.ToString();
26 | }
27 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/SlideConnectedComp.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using static MaiLib.ChartEnum;
3 |
4 | namespace MaiLib;
5 |
6 | public class SlideConnectedComp : ICodeBlock
7 | {
8 | public SlideConnectedSeq? SlideConnectedSeq { get; private set; }
9 | public SlideDuration? SlideDuration { get; private set; }
10 | public SlideConnectedMeasuredSeq? SlideConnectedMeasuredSeq { get; private set; }
11 | public bool IsBreak { get; private set; }
12 |
13 | public SlideConnectedComp(SlideConnectedSeq slideConnectedSeq, bool isBreak)
14 | {
15 | SlideConnectedSeq = slideConnectedSeq;
16 | IsBreak = isBreak;
17 | }
18 |
19 | public SlideConnectedComp(SlideConnectedMeasuredSeq slideConnectedMeasuredSeq, SlideDuration slideDuration,
20 | bool isBreak)
21 | {
22 | SlideConnectedMeasuredSeq = slideConnectedMeasuredSeq;
23 | SlideDuration = slideDuration;
24 | IsBreak = isBreak;
25 | }
26 |
27 | public string Compose(ChartVersion chartVersion)
28 | {
29 | StringBuilder builder = new();
30 | if (SlideConnectedSeq is not null)
31 | {
32 | builder.Append(SlideConnectedSeq.Compose(chartVersion));
33 | if (IsBreak) builder.Append('b');
34 | }
35 | else if (SlideConnectedMeasuredSeq is not null)
36 | {
37 | builder.Append(SlideConnectedMeasuredSeq.Compose(chartVersion));
38 | if (SlideDuration is not null)
39 | {
40 | builder.Append(SlideDuration.Compose(chartVersion));
41 | if (IsBreak) builder.Append('b');
42 | }
43 | else throw new ICodeBlock.ComponentMissingException("SLIDE-CONNECTED", "SLIDE-DURATION");
44 | }
45 | else
46 | throw new ICodeBlock.ComponentMissingException("SLIDE-CONNECTED",
47 | "SLIDE-CONNECTED-SEQ OR SLIDE-CONNECTED-MEASURED-SEQ");
48 |
49 | return builder.ToString();
50 | }
51 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/SlideConnectedMeasuredSeq.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using static MaiLib.ChartEnum;
3 |
4 | namespace MaiLib;
5 |
6 | public class SlideConnectedMeasuredSeq : ICodeBlock
7 | {
8 | public SlideType SlideType { get; private set; }
9 | public Key Key { get; private set; }
10 | public SlideConnectedMeasuredSeq? InnerSlideConnectedSeq { get; private set; }
11 |
12 | public SlideConnectedMeasuredSeq(SlideType slideType, Key key, SlideConnectedMeasuredSeq slideConnectedSeq)
13 | {
14 | SlideType = slideType;
15 | Key = key;
16 | InnerSlideConnectedSeq = slideConnectedSeq;
17 | }
18 |
19 | public SlideConnectedMeasuredSeq(SlideType slideType, Key key)
20 | {
21 | SlideType = slideType;
22 | Key = key;
23 | }
24 |
25 | public string Compose(ChartVersion chartVersion)
26 | {
27 | StringBuilder builder = new StringBuilder();
28 | builder.Append(SlideType.Compose(chartVersion));
29 | builder.Append(Key.Compose(chartVersion));
30 | if (InnerSlideConnectedSeq is not null) builder.Append(InnerSlideConnectedSeq.Compose(chartVersion));
31 | return builder.ToString();
32 | }
33 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/SlideConnectedSeq.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using static MaiLib.ChartEnum;
3 |
4 | namespace MaiLib;
5 |
6 | public class SlideConnectedSeq : ICodeBlock
7 | {
8 | public SlideType SlideType { get; private set; }
9 | public Key Key { get; private set; }
10 | public SlideDuration SlideDuration { get; private set; }
11 | public SlideConnectedSeq? InnerSlideConnectedSeq { get; private set; }
12 |
13 | public SlideConnectedSeq(SlideType slideType, Key key, SlideDuration slideDuration,
14 | SlideConnectedSeq slideConnectedSeq)
15 | {
16 | SlideType = slideType;
17 | Key = key;
18 | SlideDuration = slideDuration;
19 | InnerSlideConnectedSeq = slideConnectedSeq;
20 | }
21 |
22 | public SlideConnectedSeq(SlideType slideType, Key key, SlideDuration slideDuration)
23 | {
24 | SlideType = slideType;
25 | Key = key;
26 | SlideDuration = slideDuration;
27 | }
28 |
29 | public string Compose(ChartVersion chartVersion)
30 | {
31 | StringBuilder builder = new StringBuilder();
32 | builder.Append(SlideType.Compose(chartVersion));
33 | builder.Append(Key.Compose(chartVersion));
34 | builder.Append(SlideDuration.Compose(chartVersion));
35 | if (InnerSlideConnectedSeq is not null) builder.Append(InnerSlideConnectedSeq.Compose(chartVersion));
36 | return builder.ToString();
37 | }
38 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/SlideDuration.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | public class SlideDuration : ICodeBlock
4 | {
5 | public MeasureDuration? MeasureDuration { get; private set; }
6 | public SlideTimeDuration? SlideTimeDuration { get; private set; }
7 |
8 | public SlideDuration(MeasureDuration measureDuration)
9 | {
10 | MeasureDuration = measureDuration;
11 | }
12 |
13 | public SlideDuration(SlideTimeDuration slideTimeDuration)
14 | {
15 | SlideTimeDuration = slideTimeDuration;
16 | }
17 |
18 | public string Compose(ChartEnum.ChartVersion chartVersion)
19 | {
20 | if (MeasureDuration is not null)
21 | {
22 | return "[" + MeasureDuration.Compose(chartVersion) + "]";
23 | }
24 | else if (SlideTimeDuration is not null)
25 | {
26 | return "[" + SlideTimeDuration.Compose(chartVersion) + "]";
27 | }
28 | else
29 | throw new ICodeBlock.ComponentMissingException("SLIDE-DURATION", "MEASURE-DURATION OR SLIDE-TIME-DURATION");
30 | }
31 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/SlideGroupComp.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using static MaiLib.ChartEnum;
3 |
4 | namespace MaiLib;
5 |
6 | public class SlideGroupComp : ICodeBlock
7 | {
8 | public TapComp TapComp { get; private set; }
9 | public SlideSeq SlideSeq { get; private set; }
10 |
11 | public SlideGroupComp(TapComp tapComp, SlideSeq slideSeq)
12 | {
13 | TapComp = tapComp;
14 | SlideSeq = slideSeq;
15 | }
16 |
17 | public string Compose(ChartVersion chartVersion)
18 | {
19 | StringBuilder builder = new();
20 | builder.Append(TapComp.Compose(chartVersion));
21 | builder.Append(SlideSeq.Compose(chartVersion));
22 | return builder.ToString();
23 | }
24 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/SlideSeq.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using static MaiLib.ChartEnum;
3 |
4 | namespace MaiLib;
5 |
6 | public class SlideSeq : ICodeBlock
7 | {
8 | public SlideSet SlideSet { get; private set; }
9 | public SlideSeq? InnerSlideSeq { get; private set; }
10 |
11 | public SlideSeq(SlideSet slideSet)
12 | {
13 | SlideSet = slideSet;
14 | }
15 |
16 | public SlideSeq(SlideSet slideSet, SlideSeq slideSeq)
17 | {
18 | SlideSet = slideSet;
19 | InnerSlideSeq = slideSeq;
20 | }
21 |
22 | public string Compose(ChartVersion chartVersion)
23 | {
24 | StringBuilder builder = new();
25 | builder.Append(SlideSet.Compose(chartVersion));
26 | if (InnerSlideSeq is not null) builder.Append(InnerSlideSeq.Compose(chartVersion));
27 | return builder.ToString();
28 | }
29 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/SlideSet.cs:
--------------------------------------------------------------------------------
1 | using static MaiLib.ChartEnum;
2 |
3 | namespace MaiLib;
4 |
5 | public class SlideSet : ICodeBlock
6 | {
7 | public SlideComp? SlideComp { get; private set; }
8 | public SlideConnectedComp? SlideConnectedComp { get; private set; }
9 |
10 | public SlideSet(SlideComp slideComp)
11 | {
12 | SlideComp = slideComp;
13 | }
14 |
15 | public SlideSet(SlideConnectedComp slideConnectedComp)
16 | {
17 | SlideConnectedComp = slideConnectedComp;
18 | }
19 |
20 | public string Compose(ChartVersion chartVersion)
21 | {
22 | if (SlideComp is not null)
23 | {
24 | return SlideComp.Compose(chartVersion);
25 | }
26 | else if (SlideConnectedComp is not null)
27 | {
28 | return SlideConnectedComp.Compose(chartVersion);
29 | }
30 | else throw new ICodeBlock.ComponentMissingException("SLIDE-SET", "SLIDE-COMP OR SLIDE-CONNECTED-COMP");
31 | }
32 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/SlideTimeDuration.cs:
--------------------------------------------------------------------------------
1 | using System.Data;
2 | using static MaiLib.ChartEnum;
3 |
4 | namespace MaiLib;
5 |
6 | public class SlideTimeDuration : ICodeBlock
7 | {
8 | public double SecondsOfWait { get; private set; }
9 | public double? SecondsOfDuration { get; private set; }
10 | public double? BPM { get; private set; }
11 | public int? Quaver { get; private set; }
12 | public int? Multiple { get; private set; }
13 |
14 | public SlideTimeDuration(double secondsOfWait, double secondsOfDuration)
15 | {
16 | SecondsOfWait = secondsOfWait;
17 | SecondsOfDuration = secondsOfDuration;
18 | }
19 |
20 | public SlideTimeDuration(double secondsOfWait, double bpm, int quaver, int multiple)
21 | {
22 | SecondsOfWait = secondsOfWait;
23 | BPM = bpm;
24 | Quaver = quaver;
25 | Multiple = multiple;
26 | }
27 |
28 |
29 | public string Compose(ChartVersion chartVersion)
30 | {
31 | switch (chartVersion)
32 | {
33 | case ChartVersion.Simai:
34 | case ChartVersion.SimaiFes:
35 | default:
36 | if (SecondsOfDuration is not null) return $"[{SecondsOfWait}##{SecondsOfDuration}]";
37 | else if (BPM is not null)
38 | {
39 | if (Quaver is null) throw new ICodeBlock.ComponentMissingException("SLIDE-TIME-DURATION", "QUAVER");
40 | else if (Multiple is null)
41 | throw new ICodeBlock.ComponentMissingException("SLIDE-TIME-DURATION", "MULTIPLE");
42 | else return $"[{SecondsOfWait}##{BPM}#{Quaver}:{Multiple}]";
43 | }
44 | else
45 | throw new ICodeBlock.ComponentMissingException("SLIDE-TIME-DURATION", "SECONDS-OF-DURATION OR BPM");
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/SlideType.cs:
--------------------------------------------------------------------------------
1 | using static MaiLib.ChartEnum;
2 |
3 | namespace MaiLib;
4 |
5 | public class SlideType : ICodeBlock
6 | {
7 | public string NoteType { get; private set; }
8 | public Key? InflectionKey { get; private set; }
9 | private readonly string[] _allowedStrings = ["-", ">", "<", "^", "v", "p", "q", "s", "z", "pp", "qq", "w"];
10 |
11 | public string ExpectedStrings => $"{String.Join(", ", _allowedStrings)}, V";
12 |
13 | public SlideType(string suppliedString)
14 | {
15 | if (suppliedString.Contains('V'))
16 | {
17 | if (suppliedString.Length != 2)
18 | throw new ICodeBlock.UnexpectedStringSuppliedException("SLIDE-TYPE", "V", suppliedString);
19 | NoteType = "V";
20 | InflectionKey = new Key(suppliedString[1]);
21 | }
22 | else if (_allowedStrings.Any(suppliedString.Equals))
23 | {
24 | NoteType = suppliedString;
25 | }
26 | else throw new ICodeBlock.UnexpectedStringSuppliedException("SLIDE-TYPE", ExpectedStrings, suppliedString);
27 | }
28 |
29 | public string Compose(ChartVersion chartVersion)
30 | {
31 | return InflectionKey is null ? NoteType : NoteType + InflectionKey.Compose(chartVersion);
32 | }
33 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/StartPostfix.cs:
--------------------------------------------------------------------------------
1 | using static MaiLib.ChartEnum;
2 |
3 | namespace MaiLib;
4 |
5 | public class StartPostfix : ICodeBlock
6 | {
7 | public string? Postfix { get; private set; }
8 | private readonly string[] _allowedStrings = ["$", "!", "@"];
9 |
10 | public string ExpectedStrings => String.Join(", ", _allowedStrings);
11 |
12 | public StartPostfix()
13 | {
14 | }
15 |
16 | public StartPostfix(string suppliedString)
17 | {
18 | if (_allowedStrings.Any(suppliedString.Equals))
19 | {
20 | Postfix = suppliedString;
21 | }
22 | else throw new ICodeBlock.UnexpectedStringSuppliedException("SENSOR", ExpectedStrings, suppliedString);
23 | }
24 |
25 | public string Compose(ChartVersion chartVersion) => Postfix ?? "";
26 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/TapComp.cs:
--------------------------------------------------------------------------------
1 | using static MaiLib.ChartEnum;
2 |
3 | namespace MaiLib;
4 |
5 | public class TapComp : ICodeBlock
6 | {
7 | public Key Key { get; private set; }
8 | public StartPostfix? StartPostfix { get; private set; }
9 | public bool IsBreak { get; private set; }
10 | public bool IsEx { get; private set; }
11 |
12 | public TapComp(Key key, bool isBreak, bool isEx)
13 | {
14 | Key = key;
15 | IsBreak = isBreak;
16 | IsEx = isEx;
17 | }
18 |
19 | public string Compose(ChartVersion chartVersion)
20 | {
21 | string result = Key.Compose(chartVersion);
22 | if (IsBreak) result += "b";
23 | if (IsEx) result += "x";
24 | return result;
25 | }
26 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/TimeDuration.cs:
--------------------------------------------------------------------------------
1 | using System.Data;
2 | using static MaiLib.ChartEnum;
3 |
4 | namespace MaiLib;
5 |
6 | public class TimeDuration : ICodeBlock
7 | {
8 | public double? SecondsOfDuration { get; private set; }
9 | public double? BPM { get; private set; }
10 | public int? Quaver { get; private set; }
11 | public int? Multiple { get; private set; }
12 |
13 | public TimeDuration(double secondsOfDuration)
14 | {
15 | SecondsOfDuration = secondsOfDuration;
16 | }
17 |
18 | public TimeDuration(double bpm, int quaver, int multiple)
19 | {
20 | BPM = bpm;
21 | Quaver = quaver;
22 | Multiple = multiple;
23 | }
24 |
25 |
26 | public string Compose(ChartVersion chartVersion)
27 | {
28 | switch (chartVersion)
29 | {
30 | case ChartVersion.Simai:
31 | case ChartVersion.SimaiFes:
32 | default:
33 | if (SecondsOfDuration is not null) return $"[#{SecondsOfDuration}]";
34 | else if (BPM is not null)
35 | {
36 | if (Quaver is null) throw new ICodeBlock.ComponentMissingException("TIME-DURATION", "QUAVER");
37 | else if (Multiple is null)
38 | throw new ICodeBlock.ComponentMissingException("TIME-DURATION", "MULTIPLE");
39 | else return $"[{BPM}#{Quaver}:{Multiple}]";
40 | }
41 | else throw new ICodeBlock.ComponentMissingException("TIME-DURATION", "SECONDS-OF-DURATION OR BPM");
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/Parser/CodeBlocks/TouchNote.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using static MaiLib.ChartEnum;
3 |
4 | namespace MaiLib;
5 |
6 | public class TouchNote : ICodeBlock
7 | {
8 | public Sensor Sensor { get; private set; }
9 | public TapComp TapComp { get; private set; }
10 | public bool IsFirework { get; private set; }
11 | public HoldDuration? HoldDuration { get; private set; }
12 |
13 | public TouchNote(Sensor sensor, TapComp tapComp, bool isFirework)
14 | {
15 | Sensor = sensor;
16 | TapComp = tapComp;
17 | IsFirework = isFirework;
18 | }
19 |
20 | public TouchNote(Sensor sensor, TapComp tapComp, bool isFirework, HoldDuration holdDuration)
21 | {
22 | Sensor = sensor;
23 | TapComp = tapComp;
24 | IsFirework = isFirework;
25 | HoldDuration = holdDuration;
26 | }
27 |
28 | public string Compose(ChartVersion chartVersion)
29 | {
30 | StringBuilder builder = new();
31 | builder.Append(Sensor.Compose(chartVersion));
32 | builder.Append(TapComp.Compose(chartVersion));
33 | if (IsFirework) builder.Append(('f'));
34 | if (HoldDuration is not null) builder.Append(HoldDuration.Compose(chartVersion));
35 | return builder.ToString();
36 | }
37 | }
--------------------------------------------------------------------------------
/Parser/IParser.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | ///
4 | /// Provide interface for parsers
5 | ///
6 | public interface IParser
7 | {
8 | ///
9 | /// Return correct GoodBrother of given Token.
10 | ///
11 | /// Token to intake
12 | /// Corresponding GoodBrother
13 | public Chart ChartOfToken(string[] token);
14 |
15 | ///
16 | /// Return correct BPMChanges of given Token.
17 | ///
18 | /// Token to intake
19 | /// Corresponding BPMChanges
20 | public BPMChanges BPMChangesOfToken(string token);
21 |
22 | ///
23 | /// Return corresponding MeasureChanges
24 | ///
25 | /// Intake token
26 | /// Corresponding measure change
27 | public MeasureChanges MeasureChangesOfToken(string token);
28 |
29 | ///
30 | /// Return a specific note of given Token.
31 | ///
32 | /// Token to take in
33 | /// Specific Note
34 | public Note NoteOfToken(string token);
35 |
36 | ///
37 | /// Return a specific note of given Token.
38 | ///
39 | /// Token to take in
40 | ///
41 | ///
42 | ///
43 | /// Specific Note
44 | public Note NoteOfToken(string token, int bar, int tick, double bpm);
45 |
46 | ///
47 | /// Return correct Tap note.
48 | ///
49 | /// Token to take in
50 | /// Bar of this note
51 | /// Tick of this note
52 | /// BPM of this note
53 | /// Specific Tap
54 | public Tap TapOfToken(string token, int bar, int tick, double bpm);
55 |
56 | ///
57 | /// Return correct Tap note.
58 | ///
59 | /// Token to take in
60 | /// Specific Tap
61 | public Tap TapOfToken(string token);
62 |
63 |
64 | ///
65 | /// Return correct Hold note.
66 | ///
67 | /// Token to take in
68 | /// Bar of this note
69 | /// Tick of this note
70 | /// BPM of this note
71 | /// Specific Hold Note
72 | public Hold HoldOfToken(string token, int bar, int tick, double bpm);
73 |
74 | ///
75 | /// Return correct Hold note.
76 | ///
77 | /// Token to take in
78 | /// Specific Hold Note
79 | public Hold HoldOfToken(string token);
80 |
81 |
82 | ///
83 | /// Return correct Slide note.
84 | ///
85 | /// Token to take in
86 | /// Bar of this note
87 | /// Tick of this note
88 | /// The start note of this slide
89 | /// BPM of this note
90 | /// Specific Slide Note
91 | public Slide SlideOfToken(string token, int bar, int tick, Note slideStart, double bpm);
92 |
93 | ///
94 | /// Return correct Slide note.
95 | ///
96 | /// Token to take in
97 | /// Specific Slide Note
98 | public Slide SlideOfToken(string token);
99 | }
--------------------------------------------------------------------------------
/Parser/Ma2parser.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | using static MaiLib.NoteEnum;
4 |
5 | ///
6 | /// Give enums of parameters of Standard Keys
7 | ///
8 | public enum StdParam
9 | {
10 | Type,
11 | Bar,
12 | Tick,
13 | Key,
14 | WaitTime,
15 | LastTime,
16 | EndKey
17 | }
18 |
19 | ///
20 | /// Give enums of parameters of Deluxe Tap/Slide Keys
21 | ///
22 | public enum DxTapParam
23 | {
24 | Type,
25 | Bar,
26 | Tick,
27 | Key,
28 | KeyGroup,
29 | SpecialEffect,
30 | NoteSize
31 | }
32 |
33 | ///
34 | /// Give enums of parameters of Deluxe Hold Keys
35 | ///
36 | public enum DxHoldParam
37 | {
38 | Type,
39 | Bar,
40 | Tick,
41 | Key,
42 | LastTime,
43 | KeyGroup,
44 | SpecialEffect,
45 | NoteSize
46 | }
47 |
48 | ///
49 | /// Parses ma2 file into Ma2 chart format
50 | ///
51 | public class Ma2Parser : IParser
52 | {
53 | private Tap PreviousSlideStart;
54 | private static int _maximumDefinition = 384;
55 |
56 | ///
57 | /// Empty constructor
58 | ///
59 | public Ma2Parser()
60 | {
61 | PreviousSlideStart = new Tap();
62 | }
63 |
64 | public Chart ChartOfToken(string[] token)
65 | {
66 | BPMChanges? bpmChanges = new BPMChanges();
67 | MeasureChanges? measureChanges = new MeasureChanges();
68 | List? notes = [];
69 | if (token != null)
70 | foreach (string? x in token)
71 | {
72 | string? typeCandidate = x.Split('\t')[(int)StdParam.Type];
73 | bool isBPM_DEF = typeCandidate.Equals("BPM_DEF");
74 | bool isMET_DEF = typeCandidate.Equals("MET_DEF");
75 | bool isBPM = typeCandidate.Equals("BPM");
76 | bool isMET = typeCandidate.Equals("MET");
77 | bool isNOTE = typeCandidate.Equals("TAP")
78 | || typeCandidate.Equals("STR")
79 | || typeCandidate.Equals("TTP")
80 | || typeCandidate.Equals("XTP")
81 | || typeCandidate.Equals("XST")
82 | || typeCandidate.Equals("BRK")
83 | || typeCandidate.Equals("BST")
84 | || typeCandidate.Equals("HLD")
85 | || typeCandidate.Equals("XHO")
86 | || typeCandidate.Equals("THO")
87 | || typeCandidate.Equals("SI_")
88 | || typeCandidate.Equals("SV_")
89 | || typeCandidate.Equals("SF_")
90 | || typeCandidate.Equals("SCL")
91 | || typeCandidate.Equals("SCR")
92 | || typeCandidate.Equals("SUL")
93 | || typeCandidate.Equals("SUR")
94 | || typeCandidate.Equals("SLL")
95 | || typeCandidate.Equals("SLR")
96 | || typeCandidate.Equals("SXL")
97 | || typeCandidate.Equals("SXR")
98 | || typeCandidate.Equals("SSL")
99 | || typeCandidate.Equals("SSR")
100 | || (typeCandidate.Contains("NM") && typeCandidate.Length == 5)
101 | || (typeCandidate.Contains("CN") && typeCandidate.Length == 5)
102 | || (typeCandidate.Contains("EX") && typeCandidate.Length == 5)
103 | || (typeCandidate.Contains("BR") && typeCandidate.Length == 5)
104 | || (typeCandidate.Contains("BX") && typeCandidate.Length == 5);
105 |
106 | if (isBPM_DEF)
107 | {
108 | bpmChanges = BPMChangesOfToken(x);
109 | }
110 | else if (isMET_DEF)
111 | {
112 | measureChanges = MeasureChangesOfToken(x);
113 | }
114 | else if (isBPM)
115 | {
116 | string[]? bpmCandidate = x.Split('\t');
117 | BPMChange? candidate = new BPMChange(int.Parse(bpmCandidate[1]),
118 | int.Parse(bpmCandidate[2]),
119 | double.Parse(bpmCandidate[3]));
120 | bpmChanges.Add(candidate);
121 | bpmChanges.Update();
122 | }
123 | else if (isMET)
124 | {
125 | string[]? measureCandidate = x.Split('\t');
126 | measureChanges.Add(int.Parse(measureCandidate[(int)StdParam.Bar]),
127 | int.Parse(measureCandidate[(int)StdParam.Tick]),
128 | int.Parse(measureCandidate[(int)StdParam.Key]),
129 | int.Parse(measureCandidate[(int)StdParam.WaitTime]));
130 | }
131 | else if (isNOTE)
132 | {
133 | Note? candidate = NoteOfToken(x);
134 | notes.Add(candidate);
135 | }
136 | }
137 |
138 | foreach (Note? note in notes)
139 | {
140 | note.BPMChangeNotes = bpmChanges.ChangeNotes;
141 | if (bpmChanges.ChangeNotes.Count > 0 && note.BPMChangeNotes.Count == 0)
142 | throw new IndexOutOfRangeException("BPM COUNT DISAGREE");
143 | if (bpmChanges.ChangeNotes.Count == 0) throw new IndexOutOfRangeException("BPM CHANGE COUNT DISAGREE");
144 | }
145 |
146 | Chart result = new Ma2(notes, bpmChanges, measureChanges);
147 | return result;
148 | }
149 |
150 | public BPMChanges BPMChangesOfToken(string token)
151 | {
152 | return new BPMChanges();
153 | }
154 |
155 | public MeasureChanges MeasureChangesOfToken(string token)
156 | {
157 | return new MeasureChanges(int.Parse(token.Split('\t')[1]), int.Parse(token.Split('\t')[2]));
158 | }
159 |
160 | public Note NoteOfToken(string token)
161 | {
162 | string[]? candidate = token.Split('\t');
163 | int bar = int.Parse(candidate[(int)StdParam.Bar]);
164 | int tick = int.Parse(candidate[(int)StdParam.Tick]);
165 | return NoteOfToken(token, bar, tick, 0.0);
166 | }
167 |
168 | public Note NoteOfToken(string token, int bar, int tick, double bpm)
169 | {
170 | Note result = new Rest();
171 | string noteTypeCandidate = token.Split('\t')[(int)StdParam.Type];
172 | string fesNoteState = "";
173 | if (noteTypeCandidate.Length > 3)
174 | {
175 | fesNoteState = noteTypeCandidate.Substring(0, 2); // First 2 characters are note state
176 | noteTypeCandidate = noteTypeCandidate.Substring(2);
177 | token = token.Substring(2);
178 | }
179 |
180 | bool isTap = noteTypeCandidate.Contains("TAP")
181 | || noteTypeCandidate.Contains("STR")
182 | || noteTypeCandidate.Contains("TTP")
183 | || noteTypeCandidate.Equals("XTP")
184 | || noteTypeCandidate.Equals("XST")
185 | || noteTypeCandidate.Equals("BRK")
186 | || noteTypeCandidate.Equals("BST");
187 | bool isHold = noteTypeCandidate.Contains("HLD")
188 | || noteTypeCandidate.Equals("XHO")
189 | || noteTypeCandidate.Contains("THO");
190 | bool isSlide = noteTypeCandidate.Contains("SI_")
191 | || noteTypeCandidate.Contains("SV_")
192 | || noteTypeCandidate.Contains("SF_")
193 | || noteTypeCandidate.Contains("SCL")
194 | || noteTypeCandidate.Contains("SCR")
195 | || noteTypeCandidate.Contains("SUL")
196 | || noteTypeCandidate.Contains("SUR")
197 | || noteTypeCandidate.Contains("SLL")
198 | || noteTypeCandidate.Contains("SLR")
199 | || noteTypeCandidate.Contains("SXL")
200 | || noteTypeCandidate.Contains("SXR")
201 | || noteTypeCandidate.Contains("SSL")
202 | || noteTypeCandidate.Contains("SSR");
203 | if (isTap)
204 | result = TapOfToken(token);
205 | else if (isHold)
206 | result = HoldOfToken(token);
207 | else if (isSlide) result = SlideOfToken(token);
208 | // result.SlideStart = PreviousSlideStart;
209 | if (result.Tick == _maximumDefinition)
210 | {
211 | result.Tick = 0;
212 | result.Bar++;
213 | }
214 |
215 | if (!fesNoteState.Equals(""))
216 | {
217 | switch (fesNoteState)
218 | {
219 | case "BR":
220 | result.NoteSpecialState = SpecialState.Break;
221 | break;
222 | case "EX":
223 | result.NoteSpecialState = SpecialState.EX;
224 | break;
225 | case "BX":
226 | result.NoteSpecialState = SpecialState.BreakEX;
227 | break;
228 | case "CN":
229 | result.NoteSpecialState = SpecialState.ConnectingSlide;
230 | break;
231 | //NM does not need extra case
232 | }
233 | }
234 |
235 | if (bpm > 0.0) result.BPM = bpm;
236 | if (result.NoteSpecificGenre is NoteSpecificGenre.SLIDE_START) PreviousSlideStart = (Tap)result;
237 | return result;
238 | }
239 |
240 | public Hold HoldOfToken(string token, int bar, int tick, double bpm)
241 | {
242 | Note result = new Rest();
243 | string[]? candidate = token.Split('\t');
244 | SpecialState specialState = SpecialState.Normal;
245 | switch (candidate[(int)DxTapParam.Type])
246 | {
247 | case "XHO":
248 | candidate[(int)DxTapParam.Type] = "HLD";
249 | specialState = SpecialState.EX;
250 | break;
251 | }
252 |
253 | bool noteTypeIsValid = Enum.TryParse(candidate[(int)DxTapParam.Type], out NoteType typeCandidate);
254 | if (!noteTypeIsValid)
255 | throw new Exception("The given note type is invalid. Type provided: " + candidate[(int)DxTapParam.Type]);
256 | if (candidate[(int)DxTapParam.Type].Contains("THO")) //Basically all THO falls in this line
257 | {
258 | string? noteSize = candidate.Count() > 7 ? candidate[(int)DxHoldParam.NoteSize] : "M1";
259 | bool specialEffect = int.Parse(candidate[(int)DxHoldParam.SpecialEffect]) == 1;
260 | result = new Hold(typeCandidate,
261 | bar,
262 | tick,
263 | candidate[(int)DxHoldParam.Key] + candidate[(int)DxHoldParam.KeyGroup],
264 | int.Parse(candidate[(int)DxHoldParam.LastTime]),
265 | specialEffect,
266 | noteSize);
267 | }
268 | else
269 | {
270 | result = new Hold(typeCandidate,
271 | int.Parse(candidate[(int)StdParam.Bar]),
272 | int.Parse(candidate[(int)StdParam.Tick]),
273 | candidate[(int)StdParam.Key],
274 | int.Parse(candidate[(int)StdParam.WaitTime]));
275 | }
276 |
277 | if (bpm > 0.0) result.BPM = bpm;
278 | result.NoteSpecialState = specialState;
279 | return (Hold)result;
280 | }
281 |
282 | public Hold HoldOfToken(string token)
283 | {
284 | string[]? candidate = token.Split('\t');
285 | int bar = int.Parse(candidate[(int)StdParam.Bar]);
286 | int tick = int.Parse(candidate[(int)StdParam.Tick]);
287 | return HoldOfToken(token, bar, tick, 0.0);
288 | }
289 |
290 | public Slide SlideOfToken(string token, int bar, int tick, Note slideStart, double bpm)
291 | {
292 | string[]? candidate = token.Split('\t');
293 | bool noteTypeIsValid = Enum.TryParse(candidate[(int)DxTapParam.Type], out NoteType typeCandidate);
294 | if (!noteTypeIsValid)
295 | throw new Exception("Given Note Type is not valid. Given: " + candidate[(int)DxTapParam.Type]);
296 | if (!slideStart.Key.Equals(candidate[(int)StdParam.Key]) || slideStart.Bar != bar || slideStart.Tick != tick)
297 | PreviousSlideStart = new Tap(NoteType.NST, bar, tick, candidate[(int)StdParam.Key]);
298 | Slide? result = new Slide(typeCandidate,
299 | bar,
300 | tick,
301 | slideStart.Key,
302 | int.Parse(candidate[(int)StdParam.WaitTime]),
303 | int.Parse(candidate[(int)StdParam.LastTime]),
304 | candidate[(int)StdParam.EndKey]);
305 | if (bpm > 0.0) result.BPM = bpm;
306 | return result;
307 | }
308 |
309 | public Slide SlideOfToken(string token)
310 | {
311 | string[]? candidate = token.Split('\t');
312 | int bar = int.Parse(candidate[(int)StdParam.Bar]);
313 | int tick = int.Parse(candidate[(int)StdParam.Tick]);
314 | if (!PreviousSlideStart.Key.Equals(candidate[(int)StdParam.Key]) || PreviousSlideStart.Bar != bar ||
315 | PreviousSlideStart.Tick != tick)
316 | PreviousSlideStart = new Tap(NoteType.NST, bar, tick, candidate[(int)StdParam.Key]);
317 | return SlideOfToken(token, bar, tick, PreviousSlideStart, 0.0);
318 | }
319 |
320 |
321 | public Tap TapOfToken(string token, int bar, int tick, double bpm)
322 | {
323 | Note result = new Rest();
324 | string[]? candidate = token.Split('\t');
325 | // Resolves 1.03 to 1.04 issue
326 | SpecialState specialState = SpecialState.Normal;
327 | switch (candidate[(int)DxTapParam.Type])
328 | {
329 | case "XST":
330 | candidate[(int)DxTapParam.Type] = "STR";
331 | specialState = SpecialState.EX;
332 | break;
333 | case "XTP":
334 | candidate[(int)DxTapParam.Type] = "TAP";
335 | specialState = SpecialState.EX;
336 | break;
337 | case "BST":
338 | candidate[(int)DxTapParam.Type] = "STR";
339 | specialState = SpecialState.Break;
340 | break;
341 | case "BRK":
342 | candidate[(int)DxTapParam.Type] = "TAP";
343 | specialState = SpecialState.Break;
344 | break;
345 | }
346 |
347 | bool noteTypeIsValid = Enum.TryParse(candidate[(int)DxTapParam.Type], out NoteType typeCandidate);
348 | if (!noteTypeIsValid)
349 | throw new Exception("Given Note Type is not valid. Given: " + candidate[(int)DxTapParam.Type]);
350 | if (candidate[(int)StdParam.Type].Contains("TTP"))
351 | {
352 | string? noteSize = candidate.Length > 7 ? candidate[7] : "M1";
353 | bool specialEffect = int.Parse(candidate[(int)DxTapParam.SpecialEffect]) == 1;
354 | result = new Tap(typeCandidate,
355 | bar,
356 | tick,
357 | candidate[(int)DxTapParam.Key] + candidate[(int)DxTapParam.KeyGroup],
358 | specialEffect,
359 | noteSize);
360 | }
361 | else
362 | {
363 | result = new Tap(typeCandidate,
364 | int.Parse(candidate[(int)StdParam.Bar]),
365 | int.Parse(candidate[(int)StdParam.Tick]),
366 | candidate[(int)StdParam.Key]);
367 | }
368 |
369 | result.NoteSpecialState = specialState;
370 | return (Tap)result;
371 | }
372 |
373 | public Tap TapOfToken(string token)
374 | {
375 | string[]? candidate = token.Split('\t');
376 | int bar = int.Parse(candidate[(int)StdParam.Bar]);
377 | int tick = int.Parse(candidate[(int)StdParam.Tick]);
378 | return TapOfToken(token, bar, tick, 0.0);
379 | }
380 | }
--------------------------------------------------------------------------------
/Parser/SimaiParserR.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | using static MaiLib.TokenEnum;
4 | using static MaiLib.NoteEnum;
5 |
6 | public class SimaiParserR
7 | {
8 | public int Resolution { get; private set; }
9 | public int Tick { get; private set; }
10 | public SimaiScanner Scanner { get; private set; }
11 |
12 | public SimaiParserR()
13 | {
14 | Scanner = new SimaiScanner();
15 | Resolution = 384;
16 | }
17 |
18 | public SimaiParserR(SimaiScanner scanner)
19 | {
20 | Scanner = scanner;
21 | Resolution = 384;
22 | }
23 |
24 | public Chart Parse()
25 | {
26 | Chart candidate = new Simai();
27 | List notes = [];
28 | // NoteType noteType = NoteType.RST;
29 | while (Scanner.CurrentToken is not TokenType.EOS)
30 | {
31 | // if (notes.Count == 0 && Scanner.CurrentToken is not Token)
32 | }
33 |
34 | return candidate;
35 | }
36 | }
--------------------------------------------------------------------------------
/Parser/SimaiScanner.cs:
--------------------------------------------------------------------------------
1 | using static MaiLib.NoteEnum;
2 | using static MaiLib.TokenEnum;
3 |
4 | namespace MaiLib
5 | {
6 | public class SimaiScanner
7 | {
8 | public string[]? IncomingChart { get; private set; }
9 | public int LineNum { get; private set; }
10 | public int CharNum { get; private set; }
11 | public TokenType CurrentToken { get; private set; }
12 |
13 | public char? CurrentChar { get; private set; }
14 |
15 | public char NextChar
16 | {
17 | get => IncomingChart is null || LineNum >= IncomingChart.Length || CharNum >= IncomingChart[LineNum].Length
18 | ? ' '
19 | : IncomingChart[LineNum][CharNum];
20 | }
21 |
22 | public SimaiScanner()
23 | {
24 | LineNum = 0;
25 | CharNum = 0;
26 | CurrentToken = TokenType.EOS;
27 | }
28 |
29 | public SimaiScanner(string chart)
30 | {
31 | Load(chart);
32 | try
33 | {
34 | ScanAndAdvance();
35 | }
36 | catch (Exception ex)
37 | {
38 | Console.WriteLine("An exception was raised during scanning: {0}", ex.Message);
39 | CurrentToken = TokenType.EOS;
40 | // throw ex;
41 | }
42 | }
43 |
44 | public void Load(string chart)
45 | {
46 | IncomingChart = chart.Split(Environment.NewLine);
47 | LineNum = 0;
48 | CharNum = 0;
49 | }
50 |
51 | public void ScanAndAdvance()
52 | {
53 | if (IncomingChart is null)
54 | throw new NullReferenceException("The scanner is not provided with valid chart to scan.");
55 | if (LineNum < IncomingChart.Length)
56 | {
57 | if (IncomingChart[LineNum].Length == 0) CurrentToken = TokenType.BLANK;
58 | else if (CharNum < IncomingChart[LineNum].Length)
59 | {
60 | switch (NextChar)
61 | {
62 | case '(':
63 | CurrentToken = TokenType.LPAREN;
64 | break;
65 | case ')':
66 | CurrentToken = TokenType.RPAREN;
67 | break;
68 | case '{':
69 | CurrentToken = TokenType.LBRACE;
70 | break;
71 | case '}':
72 | CurrentToken = TokenType.RBRACE;
73 | break;
74 | case '[':
75 | CurrentToken = TokenType.LBRACKET;
76 | break;
77 | case ']':
78 | CurrentToken = TokenType.RBRACKET;
79 | break;
80 | case ',':
81 | CurrentToken = TokenType.COMMA;
82 | break;
83 | case '.':
84 | CurrentToken = TokenType.DOT;
85 | break;
86 | case '#':
87 | CurrentToken = TokenType.SHARP;
88 | break;
89 | case ':':
90 | CurrentToken = TokenType.COLON;
91 | break;
92 | case '$':
93 | CurrentToken = TokenType.DOLLAR;
94 | break;
95 | case '/':
96 | CurrentToken = TokenType.SLASH;
97 | break;
98 | case '<':
99 | CurrentToken = TokenType.LANGLE;
100 | break;
101 | case '>':
102 | CurrentToken = TokenType.RANGLE;
103 | break;
104 | case '-':
105 | CurrentToken = TokenType.DASH;
106 | break;
107 | case '*':
108 | CurrentToken = TokenType.ASTERISK;
109 | break;
110 | case 'A':
111 | CurrentToken = TokenType.A;
112 | break;
113 | case 'B':
114 | CurrentToken = TokenType.B;
115 | break;
116 | case 'C':
117 | CurrentToken = TokenType.C;
118 | break;
119 | case 'D':
120 | CurrentToken = TokenType.D;
121 | break;
122 | case 'E':
123 | CurrentToken = TokenType.E;
124 | break;
125 | case 'F':
126 | CurrentToken = TokenType.F;
127 | break;
128 | case 'v':
129 | CurrentToken = TokenType.SV;
130 | break;
131 | case 'V':
132 | CurrentToken = TokenType.LV;
133 | break;
134 | case 'p':
135 | CurrentToken = TokenType.P;
136 | break;
137 | case 'q':
138 | CurrentToken = TokenType.Q;
139 | break;
140 | case 's':
141 | CurrentToken = TokenType.S;
142 | break;
143 | case 'z':
144 | CurrentToken = TokenType.Z;
145 | break;
146 | case '0':
147 | CurrentToken = TokenType.NUM0;
148 | break;
149 | case '1':
150 | CurrentToken = TokenType.NUM1;
151 | break;
152 | case '2':
153 | CurrentToken = TokenType.NUM2;
154 | break;
155 | case '3':
156 | CurrentToken = TokenType.NUM3;
157 | break;
158 | case '4':
159 | CurrentToken = TokenType.NUM4;
160 | break;
161 | case '5':
162 | CurrentToken = TokenType.NUM5;
163 | break;
164 | case '6':
165 | CurrentToken = TokenType.NUM6;
166 | break;
167 | case '7':
168 | CurrentToken = TokenType.NUM7;
169 | break;
170 | case '8':
171 | CurrentToken = TokenType.NUM8;
172 | break;
173 | case '9':
174 | CurrentToken = TokenType.NUM9;
175 | break;
176 | case 'b':
177 | CurrentToken = TokenType.BREAK;
178 | break;
179 | case 'x':
180 | CurrentToken = TokenType.EX;
181 | break;
182 | case 'h':
183 | CurrentToken = TokenType.HOLD;
184 | break;
185 | case 'f':
186 | CurrentToken = TokenType.FIREWORK;
187 | break;
188 | case ' ':
189 | CurrentToken = TokenType.BLANK;
190 | break;
191 | default:
192 | throw new UnexpectedCharacterException(LineNum, CharNum, IncomingChart[LineNum][CharNum]);
193 | }
194 |
195 | CurrentChar = IncomingChart[LineNum][CharNum];
196 | }
197 |
198 | if (CharNum < IncomingChart[LineNum].Length - 1)
199 | {
200 | CharNum++;
201 | }
202 | else
203 | {
204 | CharNum = 0;
205 | LineNum++;
206 | }
207 | }
208 | else CurrentToken = TokenType.EOS;
209 | }
210 | }
211 |
212 | public class UnexpectedCharacterException : Exception
213 | {
214 | public UnexpectedCharacterException(int lineNum, int charNum, char token) :
215 | base($"At Line {lineNum}: Unexpected char {token} at Line {lineNum} Char {charNum}.")
216 | {
217 | }
218 | }
219 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MaiLib
2 |
3 | ## A library for processing maimai charts
4 | This is a parser for rhythm game `maimai` chart interpretation and manipulation. While this parser is primarily made for `maimai`, the program structure keeps the extensibility for other games to use.
5 |
6 | `MaiLib` currently supports 2 formats of charts:
7 | 1. `MA2`: a machine-readable pseudocode used by official `maimai` game. It records each note in chronological order relative to bar and tick, and has a statistics section recording maximum object number and scores, etc. The game used `Ver. 1.03` of this format before maimai DX FESTiVAL, and shifted to `Ver 1.04` after adding new `Slide` notes. This format itself does not contain metadata of the music - stored externally in `music.xml`. This format is more preferable for this parser.
8 | 2. `Simai`: a human-readable pseudocode used by most chart player programs. It lines `Tap` and `Hold` notes in punch-hole like arrays with fixed separations (`n-th` notes), and marks `Slide` note as a group binding with the starting `Tap` note. The detailed specification can be found at https://w.atwiki.jp/simai/pages/1003.html.
9 |
10 | > One example implementation of this library is MaichartConverter, which converts between Simai and Ma2. Please
11 | > see [MaichartConverter](https://github.com/Neskol/MaichartConverter) for more information.
12 |
13 | ### Build
14 | > While you can simply build this library, but mostly you want to add this repo as a submodule.
15 |
16 | git clone https://github.com/Neskol/MaiLib
17 | dotnet build
18 |
19 | ### Add as a submodule
20 | git submodule add https://github.com/Neskol/MaiLib
21 | git submodule update --init --recursive
22 | //Your other command continues
23 |
24 | ### Usage
25 |
26 | - The most basic part of this is `Chart`. You can use this base class to construct different charts.
27 | - This library follows the Tokenizer-Parser-Compiler format to process files, and uses an Abstract Syntax Tree for
28 | grammar
29 | decomposition when implementing Simai and Ma2 formats. If you wish to add additional formats, please make sure you
30 | also process files in the same fashion as this library, and ensure that all methods within
31 | the `ITokenizer`, `IParser`,
32 | and `ICompiler` interfaces are properly implemented.
33 | - The `Chart` base class is an intermediary in order to achieve inter-format compatability. If you are adding a new
34 | format,
35 | please be sure to also create a class implementing this base class. The `Chart` class has already implemented the
36 | functions
37 | you need for a maimai chart, and you only need to implement several abstract methods in that class to fit your
38 | format.
39 | - Then, you may create a maimai chart instance in your code. For example, creating a Ma2 chart can be done
40 | via `Chart ma2Chart = new Ma2();`.
41 |
42 | ## Additional notice for Simai compatability
43 |
44 | - As always, I think Simai is a language more focused on charting rather than interpreting. I still have no idea why
45 | there isn't a UI-based charting tool. Instead we have to learn this unsecure and unintuitive language, especially
46 | after
47 | Festival added new features. This makes interpreting Simai a huge PAIN since the way it converts between ticks and
48 | times is vague and honestly unreasonable.
49 | - For example, it defines a Slide note as having a wait time of 1 beat or one 1/4 note (or a crotchet for those in the
50 | music community)
51 | after its start tap. If your Slide note starts longer or shorter than 1 quaver of the current BPM, you will have to:
52 | a) change
53 | the BPM for that specific Slide or b) define the time by [wait time##last time] (and calculating that is extremely
54 | time-consuming).
55 | - I hope someone develops a language better than Simai to use as a intermediate language between coding and charting.
56 | Thank you.
57 |
58 | ### Parameters notice
59 |
60 | - music files should be named `musicxxxxxx.mp3`, where 'xxxxxx' matches the music ID specified in `music.xml` in each
61 | a000
62 | folder. Please make sure to add 0s at the front of this so that it contains 6 digits
63 | - bga files should be named `xxxxxx.mp4` which matches the music ID specified in `music.xml` in each a000 folder. Please
64 | make sure to add 0s at the front of this so that it contains 6 digits
65 | - image folders should be structured in `image/Texture2D/` and the files should start with 'UI_Jacket_xxxxxx.jpg', where
66 | 'xxxxxx' matches the music id specified in `music.xml` in each a000 folder. Please make sure to add 0s at the front of
67 | this so that it contains 6 digits
68 | - The difficulty parameter is listed 0-4, from Basic to Re:Master. In MaiLib, I specified rules for Easy and Utage, but
69 | it
70 | takes time for me to figure it out, or you could implement it on your own, referring MaiLib code
71 | - All paths, reguardless of operating system, should end with a path separator like "/" or "\". Do not include the
72 | quotation marks in the path.
73 | - If you have difficulty using the commands, please refer to `VSCode launch.json` where I included several examples.
74 | - The whole program was initially planned to convert from ma2 to simai, and all other features were developed after
75 | that,
76 | so there is a HUGE amount of compromises in the code's design which makes it hard to read (but works so far). It would
77 | be
78 | most kind of you if you could help me in fixing that.
79 |
80 | ### Disclaimer
81 |
82 | - Copyright of all works within this repository belongs to their respective rights holders. This tool is made solely for
83 | personal use. Please use any resources not found within this repository at your own risk.
84 | - If you would like to use this parser in your project, please refer to [MaiLib](https://github.com/Neskol/MaiLib) and
85 | hopefully that helps!
86 |
--------------------------------------------------------------------------------
/Tokenizer/ITokenizer.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | ///
4 | /// Intake files and tokenize.
5 | ///
6 | internal interface ITokenizer
7 | {
8 | ///
9 | /// Intake files and return tokens.
10 | ///
11 | ///
12 | /// Tokens from file specified
13 | string[] Tokens(string location);
14 |
15 | ///
16 | /// Intake files and return tokens.
17 | ///
18 | ///
19 | /// Tokens from text specified
20 | string[] TokensFromText(string text);
21 | }
--------------------------------------------------------------------------------
/Tokenizer/Ma2Tokenizer.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | ///
4 | /// Tokenizer of ma2 file
5 | ///
6 | public class Ma2Tokenizer : ITokenizer
7 | {
8 | ///
9 | /// Empty Constructor
10 | ///
11 | public Ma2Tokenizer()
12 | {
13 | }
14 |
15 | public string[] Tokens(string location)
16 | {
17 | string[]? result = File.ReadAllLines(location);
18 | return result;
19 | }
20 |
21 | public string[] TokensFromText(string text)
22 | {
23 | string[]? result = text.Split("\n");
24 | return result;
25 | }
26 | }
--------------------------------------------------------------------------------
/Tokenizer/SimaiTokenizer.cs:
--------------------------------------------------------------------------------
1 | namespace MaiLib;
2 |
3 | ///
4 | /// Tokenize input file into tokens that parser can read
5 | ///
6 | public class SimaiTokenizer : ITokenizer
7 | {
8 | ///
9 | /// Stores the candidates of charts
10 | ///
11 | private readonly Dictionary chartCandidates;
12 |
13 | ///
14 | /// Stores the information to read
15 | ///
16 | private readonly TrackInformation simaiTrackInformation;
17 |
18 | ///
19 | /// Constructs a tokenizer
20 | ///
21 | public SimaiTokenizer()
22 | {
23 | simaiTrackInformation = new XmlInformation();
24 | chartCandidates = [];
25 | }
26 |
27 | ///
28 | /// Access the chart candidates
29 | ///
30 | public Dictionary ChartCandidates => chartCandidates;
31 |
32 | ///
33 | /// Access the chart information
34 | ///
35 | public TrackInformation SimaiTrackInformation => simaiTrackInformation;
36 |
37 | public string[] Tokens(string location)
38 | {
39 | string[]? takeIn = File.ReadAllLines(location);
40 | string? storage = "";
41 | foreach (string? line in takeIn) storage += line;
42 | return TokensFromText(storage);
43 | }
44 |
45 | public string[] TokensFromText(string text)
46 | {
47 | string? storage = text;
48 | string[]? result = new String(text.ToCharArray().Where(c => !Char.IsWhiteSpace(c)).ToArray()).Split(',');
49 | return result;
50 | }
51 |
52 |
53 | ///
54 | /// Update candidates from texts specified
55 | ///
56 | /// Text to be tokenized
57 | public void UpdateFromText(string input)
58 | {
59 | string? storage = input;
60 | string[]? result = storage.Split("&");
61 | string? titleCandidate = "";
62 | string? bpmCandidate = "";
63 | string? artistCandidate = "";
64 | string? chartDesigner = "";
65 | string? shortIdCandidate = "";
66 | string? genreCandidate = "";
67 | string? versionCandidate = "";
68 |
69 | foreach (string? item in result)
70 | if (item.Contains("title"))
71 | {
72 | titleCandidate = item.Replace("title=", "").Replace("[SD]", "").Replace("[DX]", "");
73 | simaiTrackInformation.InformationDict["Name"] = titleCandidate;
74 | }
75 | else if (item.Contains("wholebpm"))
76 | {
77 | bpmCandidate = item.Replace("wholebpm=", "");
78 | simaiTrackInformation.InformationDict["BPM"] = bpmCandidate;
79 | }
80 | else if (item.Contains("artist"))
81 | {
82 | artistCandidate = item.Replace("artist=", "");
83 | simaiTrackInformation.InformationDict["Composer"] = artistCandidate;
84 | }
85 | else if (item.Contains("des="))
86 | {
87 | chartDesigner = item.Replace("des=", "");
88 | }
89 | else if (item.Contains("shortid"))
90 | {
91 | shortIdCandidate = item.Replace("shortid=", "");
92 | simaiTrackInformation.InformationDict["Music ID"] = shortIdCandidate;
93 | if (shortIdCandidate.Length <= 6 && int.TryParse(shortIdCandidate, out int id))
94 | {
95 | if (shortIdCandidate.Length > 4)
96 | simaiTrackInformation.InformationDict["SDDX Suffix"] = "DX";
97 | else simaiTrackInformation.InformationDict["SDDX Suffix"] = "SD";
98 | }
99 | }
100 | else if (item.Contains("genre"))
101 | {
102 | genreCandidate = item.Replace("genre=", "");
103 | simaiTrackInformation.InformationDict["Genre"] = genreCandidate;
104 | }
105 | else if (item.Contains("version"))
106 | {
107 | versionCandidate = item.Replace("version=", "");
108 | simaiTrackInformation.InformationDict["Version"] = versionCandidate;
109 | }
110 | else if (item.Contains("lv_1"))
111 | {
112 | string? easyCandidate = item.Replace("lv_1=", "");
113 | simaiTrackInformation.InformationDict["Easy"] = easyCandidate;
114 | }
115 | else if (item.Contains("des_1"))
116 | {
117 | string? easyChartCandidate = item.Replace("des_1=", "");
118 | simaiTrackInformation.InformationDict["Easy Chart Maker"] = easyChartCandidate;
119 | }
120 | else if (item.Contains("lv_2"))
121 | {
122 | string? basicCandidate = item.Replace("lv_2=", "");
123 | simaiTrackInformation.InformationDict["Basic"] = basicCandidate;
124 | }
125 | else if (item.Contains("des_2"))
126 | {
127 | string? basicChartCandidate = item.Replace("des_2=", "");
128 | simaiTrackInformation.InformationDict["Basic Chart Maker"] = basicChartCandidate;
129 | }
130 | else if (item.Contains("lv_3"))
131 | {
132 | string? advancedCandidate = item.Replace("lv_3=", "");
133 | simaiTrackInformation.InformationDict["Advanced"] = advancedCandidate;
134 | }
135 | else if (item.Contains("des_3"))
136 | {
137 | string? advancedChartCandidate = item.Replace("des_3=", "");
138 | simaiTrackInformation.InformationDict["Advanced Chart Maker"] = advancedChartCandidate;
139 | }
140 | else if (item.Contains("lv_4"))
141 | {
142 | string? expertCandidate = item.Replace("lv_4=", "");
143 | simaiTrackInformation.InformationDict["Expert"] = expertCandidate;
144 | }
145 | else if (item.Contains("des_4"))
146 | {
147 | string? expertChartCandidate = item.Replace("des_4=", "");
148 | simaiTrackInformation.InformationDict["Expert Chart Maker"] = expertChartCandidate;
149 | }
150 | else if (item.Contains("lv_5"))
151 | {
152 | string? masterCandidate = item.Replace("lv_5=", "");
153 | simaiTrackInformation.InformationDict["Master"] = masterCandidate;
154 | }
155 | else if (item.Contains("des_5"))
156 | {
157 | string? masterChartCandidate = item.Replace("des_5=", "");
158 | simaiTrackInformation.InformationDict["Master Chart Maker"] = masterChartCandidate;
159 | }
160 | else if (item.Contains("lv_6"))
161 | {
162 | string? remasterCandidate = item.Replace("lv_6=", "");
163 | simaiTrackInformation.InformationDict["Remaster"] = remasterCandidate;
164 | }
165 | else if (item.Contains("des_6"))
166 | {
167 | string? remasterChartCandidate = item.Replace("des_6=", "");
168 | simaiTrackInformation.InformationDict["Remaster Chart Maker"] = remasterChartCandidate;
169 | }
170 | else if (item.Contains("lv_7"))
171 | {
172 | string? utageCandidate = item.Replace("lv_7=", "");
173 | simaiTrackInformation.InformationDict["Utage"] = utageCandidate;
174 | }
175 | else if (item.Contains("des_7"))
176 | {
177 | string? utageChartCandidate = item.Replace("des_7=", "");
178 | simaiTrackInformation.InformationDict["Utage Chart Maker"] = utageChartCandidate;
179 | }
180 | else if (item.Contains("inote_2"))
181 | {
182 | string? noteCandidate = item.Replace("inote_2=", "");
183 | chartCandidates.Add("2", TokensFromText(noteCandidate));
184 | }
185 | else if (item.Contains("inote_3"))
186 | {
187 | string? noteCandidate = item.Replace("inote_3=", "");
188 | chartCandidates.Add("3", TokensFromText(noteCandidate));
189 | }
190 | else if (item.Contains("inote_4"))
191 | {
192 | string? noteCandidate = item.Replace("inote_4=", "");
193 | chartCandidates.Add("4", TokensFromText(noteCandidate));
194 | }
195 | else if (item.Contains("inote_5"))
196 | {
197 | string? noteCandidate = item.Replace("inote_5=", "");
198 | chartCandidates.Add("5", TokensFromText(noteCandidate));
199 | }
200 | else if (item.Contains("inote_6"))
201 | {
202 | string? noteCandidate = item.Replace("inote_6=", "");
203 | chartCandidates.Add("6", TokensFromText(noteCandidate));
204 | }
205 | else if (item.Contains("inote_7"))
206 | {
207 | string? noteCandidate = item.Replace("inote_7=", "");
208 | chartCandidates.Add("7", TokensFromText(noteCandidate));
209 | }
210 | // TODO: Fix this when note >= 8
211 | else if (item.Contains("inote_"))
212 | {
213 | string? noteCandidate = item.Replace("inote_=", "");
214 | chartCandidates.Add("Default", TokensFromText(noteCandidate));
215 | }
216 | }
217 |
218 | ///
219 | /// Update candidates from texts specified
220 | ///
221 | /// Location of text to be tokenized
222 | public void UpdateFromPath(string path)
223 | {
224 | string[]? takeIn = File.ReadAllLines(path);
225 | string? storage = "";
226 | foreach (string? line in takeIn) storage += line;
227 | UpdateFromText(storage);
228 | }
229 | }
--------------------------------------------------------------------------------
/XMaiLTemplete.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------