├── .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 | --------------------------------------------------------------------------------