├── .gitignore ├── Aff2Preview.sln ├── Aff2Preview ├── AffTools.csproj ├── AffTools │ ├── Aff2Preview │ │ └── AffRenderer.cs │ ├── AffAnalyzer │ │ ├── Analyzer.cs │ │ └── Note.cs │ └── AffReader │ │ ├── AffReader.cs │ │ ├── AffStringParser.cs │ │ ├── ArcAlgorithm.cs │ │ └── ArcaeaFileFormat.cs ├── MyGraphics │ ├── GdiPlusAdapter.cs │ ├── GraphicsAdapter.cs │ └── SkiaAdapter.cs └── Program.cs ├── LICENSE ├── README.md ├── assets ├── 2.aff ├── arc_body.png ├── base.jpg ├── note.png └── note_hold.png └── output.jpg /.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 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /Aff2Preview.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.32319.34 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AffTools", "Aff2Preview\AffTools.csproj", "{EDFDBF2E-7FDB-4A72-941B-4951E71CF069}" 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 | {EDFDBF2E-7FDB-4A72-941B-4951E71CF069}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {EDFDBF2E-7FDB-4A72-941B-4951E71CF069}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {EDFDBF2E-7FDB-4A72-941B-4951E71CF069}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {EDFDBF2E-7FDB-4A72-941B-4951E71CF069}.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 = {13BA9946-ABA4-4971-8825-F0B7E6DB8709} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Aff2Preview/AffTools.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | False 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Aff2Preview/AffTools/Aff2Preview/AffRenderer.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using System.Text.RegularExpressions; 3 | 4 | using AffTools.AffReader; 5 | using AffTools.MyGraphics; 6 | 7 | namespace AffTools.Aff2Preview; 8 | 9 | internal class AffRenderer 10 | { 11 | private abstract class DrawObjectBase 12 | { 13 | public Vector3 Location { get; init; } 14 | public string? Property { get; init; } 15 | public bool IsEnwiden { get; init; } 16 | public abstract void Draw(GraphicsAdapter g); 17 | } 18 | 19 | private class ArcSegment : DrawObjectBase 20 | { 21 | public Vector3 End { get; init; } 22 | public int ColorId { get; init; } 23 | public bool IsVoid { get; init; } 24 | public override void Draw(GraphicsAdapter g) 25 | { 26 | float drawX = IsEnwiden ? Location.X * 4 / 6 + 0.25f * Config.DrawingTrackWidth * 4 / 6 : Location.X; 27 | float endX = IsEnwiden ? End.X * 4 / 6 + 0.25f * Config.DrawingTrackWidth * 4 / 6 : End.X; 28 | ColorDesc color = new(); 29 | float w; 30 | if (IsVoid) 31 | { 32 | w = Config.ArcVoidWidth; 33 | color.SetColor(Config.GetArcVoidColor()); 34 | } 35 | else 36 | { 37 | w = Config.ArcWidth; 38 | color.SetColor(Config.GetArcColor(ColorId)); 39 | } 40 | color.SetColorA((byte)Math.Clamp((int)(47 + (230 - 47) * Location.Y), 0, 255)); 41 | g.SetColor(color); 42 | g.DrawLine(w, drawX, Config.TimingToY(Location.Z), endX, Config.TimingToY(End.Z)); 43 | } 44 | } 45 | 46 | private class ArcTap : DrawObjectBase 47 | { 48 | public override void Draw(GraphicsAdapter g) 49 | { 50 | var airTap = Property switch 51 | { 52 | "voice_wav" => Config.Side == 1 ? Config.SfxDTap : Config.SfxLTap, 53 | "glass_wav" => Config.Side == 1 ? Config.SfxDTap : Config.SfxLTap, 54 | _ => Config.AirTap 55 | }; 56 | int drawX = IsEnwiden ? (int)(Location.X * 4 / 6 + Config.EnwidenTrackWidth / 2 + 3) : 57 | (int)(Location.X - Config.SingleTrackWidth / 2 + 1); 58 | 59 | g.DrawImageScaled( 60 | airTap, 61 | drawX, 62 | (int)(Config.TimingToY(Location.Z) - Config.SkyNoteHeight / 4) - (IsEnwiden ? 1 : 0), 63 | IsEnwiden ? Config.EnwidenTrackWidth - 2 : Config.SingleTrackWidth - 2, 64 | Config.SkyNoteHeight / 4, 65 | Math.Clamp(0.3f + Location.Y * 0.7f, 0, 1) 66 | ); 67 | } 68 | } 69 | 70 | private class ConnectLine : DrawObjectBase 71 | { 72 | public Vector3 End { get; init; } 73 | public override void Draw(GraphicsAdapter g) 74 | { 75 | g.DrawLine( 76 | ColorDesc.FromArgb(Config.GetConnectLineColor()), 77 | 2f, 78 | Location.X, Config.TimingToY(Location.Z), 79 | End.X, Config.TimingToY(Location.Z)); 80 | } 81 | } 82 | 83 | private class TextObject : DrawObjectBase 84 | { 85 | public string Text { get; set; } = ""; 86 | public FontDesc Font { get; set; } 87 | public ColorDesc Color { get; set; } 88 | public override void Draw(GraphicsAdapter g) 89 | { 90 | g.DrawString(Text, Color, Font, Location.X, Config.TimingToY(Location.Z)); 91 | } 92 | } 93 | 94 | internal class ChartConfig 95 | { 96 | public ImageDesc Background = new GdiImage(); 97 | public ImageDesc Cover = new GdiImage(); 98 | 99 | public ImageDesc Tap = new GdiImage(); 100 | public ImageDesc Hold = new GdiImage(); 101 | public ImageDesc AirTap = new GdiImage(); 102 | 103 | public readonly ImageDesc SfxDTap = new GdiImage(); 104 | public readonly ImageDesc SfxLTap = new GdiImage(); 105 | 106 | public int NoteHeight { get; set; } = 64; 107 | public int SkyNoteHeight { get; set; } = 61; 108 | public float ArcWidth { get; set; } = 20f; 109 | public float ArcVoidWidth { get; set; } = 3f; 110 | 111 | public int TotalTrackWidth { get; set; } = 248; 112 | public int DrawingTrackWidth => TotalTrackWidth - 4; 113 | public int SingleTrackWidth => DrawingTrackWidth / 4; 114 | public int EnwidenTrackWidth => DrawingTrackWidth / 6; 115 | 116 | public int TimingScale { get; set; } = 5; 117 | 118 | public int Cols { get; set; } = 1; 119 | public int ColWidth { get; set; } = 0; 120 | public int Rows { get; set; } = 1; 121 | public float SegmentLengthInBaseBpm { get; set; } = 0; 122 | 123 | /// 124 | /// 0:hikari 1:conflict 2:finale 125 | /// 126 | public int Side { get; set; } = 0; 127 | 128 | public uint TrackColor 129 | => Side switch 130 | { 131 | 0 => 0xffffffff, 132 | 1 => 0xff382a47, 133 | 2 => 0xffffffff, 134 | _ => 0 135 | }; 136 | 137 | public uint TrackLineColor 138 | => Side switch 139 | { 140 | 0 => 0xffd3d3d3, 141 | 1 => 0xff2e1f3c, 142 | 2 => 0xffd3d3d3, 143 | _ => 0 144 | }; 145 | 146 | public uint TrackSegmentLineColor 147 | => Side switch 148 | { 149 | 0 => 0xa0d3d3d3, 150 | 1 => 0xc02e1f3c, 151 | 2 => 0xa0d3d3d3, 152 | _ => 0 153 | }; 154 | 155 | public uint TrackStripColor 156 | => Side switch 157 | { 158 | 0 => 0x0f808080, 159 | 1 => 0x08f0f8ff, 160 | 2 => 0x0f808080, 161 | _ => 0 162 | }; 163 | 164 | public uint GetArcVoidColor() 165 | => Side switch 166 | { 167 | 0 => 0xffd3d3d3, 168 | 1 => 0xffa9a9a9, 169 | 2 => 0xffd3d3d3, 170 | _ => 0, 171 | }; 172 | 173 | public uint GetArcColor(int type) 174 | => Side switch 175 | { 176 | 0 => type switch 177 | { 178 | 0 => 0xff31dae7, 179 | 1 => 0xffff69b4, 180 | _ => 0, 181 | }, 182 | 1 => type switch 183 | { 184 | 0 => 0xff00ced1, 185 | 1 => 0xffff1493, 186 | _ => 0, 187 | }, 188 | 2 => type switch 189 | { 190 | 0 => 0xff31dae7, 191 | 1 => 0xffff69b4, 192 | _ => 0, 193 | }, 194 | _ => 0xff31dae7, 195 | }; 196 | 197 | public uint GetConnectLineColor() 198 | => Side switch 199 | { 200 | 0 => 0xdc90ee90, 201 | 1 => 0xdcff1493, 202 | 2 => 0xdc90ee90, 203 | _ => 0, 204 | }; 205 | 206 | public int TotalTrackLength { get; set; } = 0; 207 | 208 | public float TimingToY(float timing) 209 | => (TotalTrackLength - timing) / TimingScale - 3; 210 | } 211 | 212 | private readonly ArcaeaAffReader _affReader = new(); 213 | 214 | public static ChartConfig Config = new(); 215 | 216 | private AffAnalyzer.Analyzer _affAnalyzer; 217 | 218 | public AffRenderer(string affFile) 219 | { 220 | AffFile = affFile; 221 | } 222 | 223 | public string AffFile { get; set; } = ""; 224 | public string Title { get; set; } = ""; 225 | public float Rating { get; set; } = 0f; 226 | public int Notes { get; set; } = 0; 227 | public int Side { get; set; } = 0; 228 | public float ChartBpm { get; set; } = 0f; 229 | public string Artist { get; set; } = ""; 230 | public string Charter { get; set; } = ""; 231 | public int Difficulty { get; set; } = 0; 232 | public bool IsMirror { get; set; } = false; 233 | private List<(int, int)> Interval4K { get; set; } = new(); 234 | public string DiffStr => Difficulty switch 235 | { 236 | 0 => "Past", 237 | 1 => "Present", 238 | 2 => "Future", 239 | 3 => "Beyond", 240 | _ => "" 241 | }; 242 | 243 | public void LoadResource(string tap, string hold, string airTap, string bg, string cover) 244 | { 245 | Config.Background.FromFile(bg); 246 | 247 | Config.Cover.FromFile(cover); 248 | 249 | Config.Tap.FromFile(tap); 250 | Config.Hold.FromFile(hold); 251 | Config.AirTap.FromFile(airTap); 252 | } 253 | 254 | private void MirrorAff() 255 | { 256 | foreach (var affEvent in _affReader.Events) 257 | { 258 | switch (affEvent) 259 | { 260 | case ArcaeaAffTap tap: 261 | tap.Track = 5 - tap.Track; 262 | break; 263 | case ArcaeaAffHold hold: 264 | hold.Track = 5 - hold.Track; 265 | break; 266 | case ArcaeaAffArc arc: 267 | arc.XStart = 1f - arc.XStart; 268 | arc.XEnd = 1f - arc.XEnd; 269 | arc.Color = 1 - arc.Color; 270 | break; 271 | } 272 | } 273 | } 274 | 275 | private void LoadAff() 276 | { 277 | _affReader.Parse(AffFile); 278 | 279 | _affAnalyzer = new(_affReader); 280 | _affAnalyzer.AnalyzeSegments(); 281 | _affAnalyzer.AnalyzeNotes(); 282 | 283 | Config.TotalTrackLength = (int)_affAnalyzer.totalTime; 284 | } 285 | 286 | public ImageDesc Draw() 287 | { 288 | Config.Side = Side; 289 | 290 | LoadAff(); 291 | if (IsMirror) MirrorAff(); 292 | 293 | _affAnalyzer.CalcNotes(); 294 | Interval4K = _affAnalyzer.Get4LaneInterval(Config.TotalTrackLength); 295 | 296 | var trackImg = DrawTrackObjects(); 297 | 298 | float segmentLengthInBaseBpm = _affAnalyzer.baseTimePerSegment / Config.TimingScale; 299 | int rows = _affAnalyzer.segmentCountInBaseBpm; 300 | int cols = 1; 301 | int colWidth = Config.TotalTrackWidth + 75; 302 | 303 | for (; rows > 0; rows--) 304 | { 305 | cols = _affAnalyzer.segmentCountInBaseBpm / rows + (_affAnalyzer.segmentCountInBaseBpm % rows == 0 ? 0 : 1); 306 | double w = cols * colWidth; 307 | double h = rows * segmentLengthInBaseBpm; 308 | if (w / h >= 4f / 3f) 309 | break; 310 | } 311 | 312 | Config.Cols = cols; 313 | Config.Rows = rows; 314 | Config.ColWidth = colWidth; 315 | Config.SegmentLengthInBaseBpm = segmentLengthInBaseBpm; 316 | 317 | int outputWidth = Config.Cols * colWidth + 100; 318 | int outputHeight = Config.Rows * (int)segmentLengthInBaseBpm + 200 + 25 + 25; 319 | 320 | ColorDesc? rectColor = ColorDesc.FromArgb(Config.Side switch 321 | { 322 | 0 => 0xc8f0f0f0, 323 | 1 => 0xc8202020, 324 | 2 => 0xc8f0f0f0, 325 | }); 326 | 327 | GraphicsAdapter g = new GdiPlusAdapter(); 328 | g.BeginContext(outputWidth, outputHeight); 329 | g.Fill(rectColor); 330 | 331 | int bw = Config.Background.GetWidth(); 332 | int bh = Config.Background.GetHeight(); 333 | if ((float)outputWidth / bw > (float)outputHeight / bh) 334 | { 335 | int dw = outputWidth; 336 | float rate = (float)dw / bw; 337 | float dh = bh * rate; 338 | g.DrawImageScaled(Config.Background, 0, -Math.Abs(outputHeight - dh) / 2, dw, dh); 339 | } 340 | else 341 | { 342 | int dh = outputHeight; 343 | float rate = (float)dh / bh; 344 | float dw = bw * rate; 345 | g.DrawImageScaled(Config.Background, -Math.Abs(outputWidth - dw) / 2, 0, dw, dh); 346 | } 347 | 348 | g.FillRectangle(rectColor, 349 | 25, 25, Config.Cols * Config.ColWidth + 50, Config.Rows * (int)Config.SegmentLengthInBaseBpm + 50 + 25); 350 | 351 | for (int x = 0; x < Config.Cols; x++) 352 | { 353 | double y = trackImg.GetHeight() - (x + 1) * Config.Rows * Config.SegmentLengthInBaseBpm; 354 | g.DrawImageCliped(trackImg, x * colWidth + colWidth - Config.TotalTrackWidth + 50, 50, 355 | 0, (int)y, Config.TotalTrackWidth, (int)(Config.Rows * Config.SegmentLengthInBaseBpm)); 356 | } 357 | 358 | DrawComboNumber(g); 359 | //DrawSegmentNumber(g); 360 | DrawSegmentBpm(g); 361 | DrawNoteLength(g); 362 | 363 | DrawFooter(g); 364 | 365 | return g.EndContext(); 366 | } 367 | 368 | public void DrawTrack(GraphicsAdapter g) 369 | { 370 | foreach (var (initial, end) in Interval4K) 371 | { 372 | for (int i = 0; i < 5; i++) 373 | { 374 | g.DrawLine(ColorDesc.FromArgb(Config.TrackLineColor), i is > 0 and < 4 ? 2f : 4f, 375 | Config.DrawingTrackWidth / 4 * i + 2, (Config.TotalTrackLength - initial) / Config.TimingScale, 376 | Config.DrawingTrackWidth / 4 * i + 2, (Config.TotalTrackLength - end) / Config.TimingScale); 377 | } 378 | } 379 | 380 | var widen = _affAnalyzer.GetPairEnwidenLanes(); 381 | foreach (var (initial, end) in widen) 382 | { 383 | for (int i = 0; i < 7; i++) 384 | { 385 | g.DrawLine(ColorDesc.FromArgb(Config.TrackLineColor), i is > 0 and < 6 ? 2f : 4f, 386 | Config.DrawingTrackWidth * i / 6 + 2, (Config.TotalTrackLength - initial.Timing) / Config.TimingScale, 387 | Config.DrawingTrackWidth * i / 6 + 2, (Config.TotalTrackLength - end.Timing) / Config.TimingScale); 388 | } 389 | } 390 | 391 | g.SetColor(ColorDesc.FromArgb(Config.TrackSegmentLineColor)); 392 | foreach (float t in _affAnalyzer.SegmentTimings) 393 | { 394 | if (t >= 0) 395 | g.DrawLine(3f, 396 | 0, Config.TimingToY(t), 397 | Config.TotalTrackWidth, Config.TimingToY(t)); 398 | } 399 | 400 | g.SetColor(ColorDesc.FromArgb(Config.TrackStripColor)); 401 | for (int i = 0; i < Config.TotalTrackLength; i += 45) 402 | { 403 | g.DrawLine(57f, -20, i, 300, i - 200); 404 | } 405 | 406 | foreach (var note in _affAnalyzer.Notes) 407 | { 408 | if (note.TimePoint > Config.TotalTrackLength) 409 | break; 410 | 411 | float density = note.InvDuration - 6; 412 | 413 | if (density < 0) continue; 414 | 415 | density = density * density * 0.5f; 416 | 417 | g.SetColor(ColorDesc.FromArgb((byte)density, 255, 0, 0)); 418 | g.FillRectangle(ColorDesc.FromArgb((byte)density, 255, 0, 0), 419 | 0, Config.TimingToY(note.TimePoint + note.Duration) - 1, 420 | Config.TotalTrackWidth, (note.Duration + 3) / Config.TimingScale); 421 | } 422 | } 423 | 424 | public ImageDesc DrawTrackObjects() 425 | { 426 | GraphicsAdapter g = new GdiPlusAdapter(); 427 | g.BeginContext(Config.TotalTrackWidth, Config.TotalTrackLength / Config.TimingScale); 428 | 429 | g.Fill(ColorDesc.FromArgb(Config.TrackColor)); 430 | 431 | DrawTrack(g); 432 | DrawFloorNotes(g); 433 | DrawAirObjects(g); 434 | 435 | return g.EndContext(); 436 | } 437 | 438 | void DrawFloorNotes(GraphicsAdapter g) 439 | { 440 | foreach (var ev in _affReader.Events) 441 | { 442 | if (ev is ArcaeaAffTap tap) 443 | { 444 | if (Interval4K.Any(x => tap.Timing > x.Item1 && tap.Timing < x.Item2)) 445 | { 446 | float x = Config.DrawingTrackWidth * (tap.Track - 1) / 4; 447 | float y = Config.TimingToY(tap.Timing); 448 | g.DrawImageScaled(Config.Tap, 449 | x + 3, y - Config.NoteHeight / 4, 450 | Config.SingleTrackWidth - 2, Config.NoteHeight / 4, 0); 451 | } 452 | else 453 | { 454 | float x = Config.DrawingTrackWidth * (tap.Track) / 6; 455 | float y = Config.TimingToY(tap.Timing); 456 | g.DrawImageScaled(Config.Tap, 457 | x + 3, y - Config.NoteHeight / 4, 458 | Config.EnwidenTrackWidth - 2, Config.NoteHeight / 4, 0); 459 | } 460 | } 461 | else if (ev is ArcaeaAffHold hold) 462 | { 463 | if (Interval4K.Any(x => hold.Timing > x.Item1 && hold.Timing < x.Item2)) 464 | { 465 | float x = Config.DrawingTrackWidth * (hold.Track - 1) / 4; 466 | float ys = Config.TimingToY(hold.Timing); 467 | float ye = Config.TimingToY(hold.EndTiming); 468 | g.DrawImageScaled(Config.Hold, x + 3, ye, Config.SingleTrackWidth - 2, ys - ye, 0); 469 | } 470 | else 471 | { 472 | float x = Config.DrawingTrackWidth * (hold.Track) / 6; 473 | float ys = Config.TimingToY(hold.Timing); 474 | float ye = Config.TimingToY(hold.EndTiming); 475 | g.DrawImageScaled(Config.Hold, x + 3, ye, Config.EnwidenTrackWidth - 2, ys - ye, 0); 476 | } 477 | } 478 | } 479 | } 480 | 481 | public void DrawAirObjects(GraphicsAdapter g) 482 | { 483 | List airObjects = new(); 484 | List airTaps = new(); 485 | 486 | var AddDoubleTip = (float x, float z, bool isEnwiden) => 487 | { 488 | int sw = isEnwiden ? Config.EnwidenTrackWidth : Config.SingleTrackWidth; 489 | float sx = x <= Config.DrawingTrackWidth / 2 ? x + sw / 2 + 5 : x - sw / 2 - 20; 490 | airObjects.Add(new TextObject() 491 | { 492 | Text = "x2", 493 | Location = new Vector3(sx, 1.5f, z + 80), 494 | Color = Config.Side == 1 ? ColorDesc.FromArgb(0xddffffff) : ColorDesc.FromArgb(0xff000000), 495 | Font = new FontDesc("exo", 10f, FontDescStyle.Bold) 496 | }); 497 | }; 498 | 499 | foreach (var ev in _affReader.Events) 500 | { 501 | if (ev is not ArcaeaAffArc t) continue; 502 | 503 | int duration = t.EndTiming - t.Timing; 504 | bool isEnwiden = !Interval4K.Any(inv => t.Timing > inv.Item1 && t.Timing < inv.Item2); 505 | 506 | int segSize = duration / (duration < 1000 ? 14 : 7); 507 | int segmentCount = (segSize == 0 ? 0 : duration / segSize) + 1; 508 | 509 | List segments = new(); 510 | 511 | Vector3 start = new(); 512 | Vector3 end = new((t.XStart + 0.5f) * Config.DrawingTrackWidth / 2 + 3, t.YStart, t.Timing); 513 | segments.Add(end); 514 | 515 | for (int i = 0; i < segmentCount - 1; i++) 516 | { 517 | start = end; 518 | float x = ArcAlgorithm.X(t.XStart, t.XEnd, (i + 1f) * segSize / duration, ArcaeaAffArc.ToArcLineType(t.LineType)); 519 | float y = ArcAlgorithm.Y(t.YStart, t.YEnd, (i + 1f) * segSize / duration, ArcaeaAffArc.ToArcLineType(t.LineType)); 520 | end = new Vector3((x + 0.5f) * Config.DrawingTrackWidth / 2 + 3, 521 | y, 522 | t.Timing + segSize * (i + 1)); 523 | segments.Add(end); 524 | } 525 | 526 | // last segment 527 | { 528 | start = end; 529 | end = new Vector3((t.XEnd + 0.5f) * Config.DrawingTrackWidth / 2 + 3, 530 | t.YEnd, 531 | t.EndTiming); 532 | segments.Add(end); 533 | } 534 | 535 | for (int i = 0; i < segments.Count - 1; i++) 536 | { 537 | var st = segments[i]; 538 | var ed = segments[i + 1]; 539 | 540 | airObjects.Add(new ArcSegment() 541 | { 542 | IsVoid = t.IsVoid, 543 | ColorId = t.Color, 544 | Location = st, 545 | End = ed, 546 | IsEnwiden = isEnwiden, 547 | }); 548 | } 549 | 550 | if (t.ArcTaps is null) 551 | continue; 552 | 553 | foreach (int airTapTiming in t.ArcTaps) 554 | { 555 | float tm = airTapTiming - t.Timing; 556 | float x = ArcAlgorithm.X(t.XStart, t.XEnd, tm / duration, ArcaeaAffArc.ToArcLineType(t.LineType)); 557 | float y = ArcAlgorithm.Y(t.YStart, t.YEnd, tm / duration, ArcaeaAffArc.ToArcLineType(t.LineType)); 558 | 559 | bool isInEnwiden = !Interval4K.Any(inv => airTapTiming > inv.Item1 && airTapTiming < inv.Item2); 560 | 561 | x = (x + 0.5f) * Config.DrawingTrackWidth / 2; 562 | 563 | airTaps.Add(new ArcTap() 564 | { 565 | Location = new Vector3(x + 2, y, airTapTiming), 566 | Property = (ev as ArcaeaAffArc)?.Fx, 567 | IsEnwiden = isInEnwiden, 568 | }); 569 | 570 | if (isEnwiden) 571 | x = x * 4 / 6 + Config.EnwidenTrackWidth + 3; 572 | 573 | // detect underneath notes 574 | foreach (var evOther in _affReader.Events) 575 | { 576 | if (evOther is ArcaeaAffTap evAt) 577 | { 578 | if (Math.Abs(evAt.Timing - airTapTiming) > 3) continue; 579 | 580 | float x_t = Config.DrawingTrackWidth * (evAt.Track - 1) / 4 + Config.SingleTrackWidth / 2; 581 | 582 | if (!Interval4K.Any(inv => evAt.Timing > inv.Item1 && evAt.Timing < inv.Item2)) 583 | x_t = Config.DrawingTrackWidth * evAt.Track / 6 + Config.EnwidenTrackWidth / 2; 584 | 585 | float y_t = Config.TimingToY(evAt.Timing); 586 | 587 | airObjects.Add(new ConnectLine() 588 | { 589 | Location = new Vector3(x_t + 3, y, airTapTiming + 6), 590 | End = new Vector3(x + 3, 0, airTapTiming + 6) 591 | }); 592 | 593 | if (Math.Abs(x - x_t) <= 5) 594 | AddDoubleTip(x, airTapTiming, isEnwiden); 595 | } 596 | else if (!t.Equals(evOther) && evOther is ArcaeaAffArc evArc) 597 | { 598 | if (evArc.ArcTaps is null) 599 | continue; 600 | 601 | foreach (int arcT in evArc.ArcTaps) 602 | { 603 | if (Math.Abs(arcT - airTapTiming) > 3) continue; 604 | 605 | float arc_t_x = ArcAlgorithm.X(evArc.XStart, evArc.XEnd, evArc.Timing / duration, ArcaeaAffArc.ToArcLineType(evArc.LineType)); 606 | float arc_t_y = ArcAlgorithm.Y(evArc.YStart, evArc.YEnd, evArc.Timing / duration, ArcaeaAffArc.ToArcLineType(evArc.LineType)); 607 | arc_t_x = (arc_t_x + 0.5f) * Config.DrawingTrackWidth / 2; 608 | if (!Interval4K.Any(inv => arcT > inv.Item1 && arcT < inv.Item2)) 609 | arc_t_x = arc_t_x * 4 / 6 + Config.EnwidenTrackWidth + 3; 610 | 611 | if (Math.Abs(arc_t_x - x) <= 5 && arc_t_y < y) 612 | AddDoubleTip(x, airTapTiming, isEnwiden); 613 | } 614 | 615 | } 616 | } 617 | 618 | } 619 | } 620 | 621 | airObjects.OrderBy(x => x.Location.Y).ToList().ForEach(x => x.Draw(g)); 622 | airTaps.ForEach(x => x.Draw(g)); 623 | } 624 | 625 | public void DrawSegmentNumber(GraphicsAdapter g) 626 | { 627 | ColorDesc c = ColorDesc.FromArgb(Config.Side == 1 ? 0xffffffff : 0xff000000); 628 | c.SetColorA(240); 629 | g.SetColor(c); 630 | g.SetFont("exo", 10f, FontDescStyle.Bold); 631 | 632 | for (int i = 0, t = 1; i < _affAnalyzer.SegmentTimings.Count; i++) 633 | { 634 | int segmentTiming = (int)_affAnalyzer.SegmentTimings[i]; 635 | 636 | if (segmentTiming > _affAnalyzer.totalTime) 637 | break; 638 | 639 | if (segmentTiming < 0) 640 | continue; 641 | 642 | int next = i + 1; 643 | if (next < _affAnalyzer.SegmentTimings.Count) 644 | { 645 | int segmentTiming2 = (int)_affAnalyzer.SegmentTimings[next]; 646 | if (segmentTiming2 - segmentTiming < 100) 647 | continue; 648 | } 649 | 650 | float colHeight = Config.Rows * Config.SegmentLengthInBaseBpm; 651 | int sy = segmentTiming / Config.TimingScale; 652 | 653 | float y = colHeight - sy % colHeight; 654 | float x = Config.ColWidth * (sy / (int)colHeight); 655 | if (y <= 5) 656 | y = Config.Rows * Config.SegmentLengthInBaseBpm; 657 | 658 | g.DrawString(t.ToString(), x + Config.ColWidth - 220 - 25, y + 37); 659 | t++; 660 | } 661 | } 662 | 663 | public void DrawComboNumber(GraphicsAdapter g) 664 | { 665 | ColorDesc c = ColorDesc.FromArgb(Config.Side == 1 ? 0xffffffff : 0xff000000); 666 | c.SetColorA(240); 667 | g.SetColor(c); 668 | g.SetFont("exo", 10f, FontDescStyle.Bold); 669 | 670 | int prevCombo = -1; 671 | float endTime = _affAnalyzer.realTotalTime; 672 | int fullCombo = _affAnalyzer.Total; 673 | float colHeight = Config.Rows * Config.SegmentLengthInBaseBpm; 674 | 675 | for (int i = 0; i < _affAnalyzer.SegmentTimings.Count; i++) 676 | { 677 | int segmentTiming = (int)_affAnalyzer.SegmentTimings[i]; 678 | 679 | if (segmentTiming > endTime - 10) 680 | break; 681 | 682 | if (segmentTiming < 0) 683 | continue; 684 | 685 | int next = i + 1; 686 | if (next < _affAnalyzer.SegmentTimings.Count) 687 | { 688 | int segmentTimingNext = (int)_affAnalyzer.SegmentTimings[next]; 689 | if (segmentTimingNext - segmentTiming < 100) 690 | continue; 691 | } 692 | 693 | int combo = _affAnalyzer.GetCombo(segmentTiming); 694 | if (combo == prevCombo) 695 | continue; 696 | 697 | prevCombo = combo; 698 | 699 | int sy = segmentTiming / Config.TimingScale; 700 | 701 | float y = colHeight - sy % colHeight; 702 | float x = Config.ColWidth * (sy / (int)colHeight); 703 | if (y <= 5) 704 | y = Config.Rows * Config.SegmentLengthInBaseBpm; 705 | 706 | g.DrawString(combo.ToString(), x + Config.ColWidth - 220 - 29, y + 37); 707 | } 708 | { 709 | int sy = (int)endTime / Config.TimingScale; 710 | 711 | float y = colHeight - sy % colHeight; 712 | float x = Config.ColWidth * (sy / (int)colHeight); 713 | if (y <= 5) 714 | y = Config.Rows * Config.SegmentLengthInBaseBpm; 715 | 716 | g.DrawString(fullCombo.ToString(), x + Config.ColWidth - 220 - 29, y + 37); 717 | } 718 | } 719 | 720 | public void DrawSegmentBpm(GraphicsAdapter g) 721 | { 722 | ColorDesc c = ColorDesc.FromArgb(0xffff7f50); 723 | 724 | g.SetColor(c); 725 | g.SetFont("exo", 10f, FontDescStyle.Bold); 726 | 727 | float lastBpl = 0; 728 | float lastBpm = 0; 729 | 730 | foreach (var ev in _affReader.Events) 731 | { 732 | if (ev is not ArcaeaAffTiming t) continue; 733 | 734 | if (t.Timing > Config.TotalTrackLength) 735 | break; 736 | 737 | if (t.Timing == 0 && t.TimingGroup != 0) 738 | continue; 739 | 740 | if (t.Bpm is 0 or > 1000) 741 | continue; 742 | 743 | float colHeight = Config.Rows * Config.SegmentLengthInBaseBpm; 744 | float rate = t.Bpm / _affAnalyzer.baseBpm; 745 | float y = colHeight - t.Timing / Config.TimingScale % colHeight; 746 | float x = Config.ColWidth * (t.Timing / Config.TimingScale / (int)colHeight); 747 | if (y <= 5) 748 | y = (int)colHeight; 749 | 750 | if (lastBpm != t.Bpm) 751 | { 752 | g.DrawStringLayoutLTRB($"{(int)t.Bpm}", 753 | x + Config.ColWidth - 220 - 20 - 60, y + 37, 754 | x + Config.ColWidth - 220 - 20 - 6, y + 60, 755 | StringAdapterAlignment.Far); 756 | 757 | lastBpm = t.Bpm; 758 | } 759 | 760 | if (lastBpl == t.BeatsPerLine) continue; 761 | float bpl = t.BeatsPerLine; 762 | string? bplText = ""; 763 | 764 | if ((int)(bpl * 100) % 100 == 0) 765 | bplText = $"{(int)bpl}/4"; 766 | else if ((int)(bpl * 200) % 100 == 0) 767 | { 768 | bplText = $"{(int)(bpl * 2)}/8"; 769 | } 770 | else if ((int)(bpl * 400) % 100 == 0) 771 | { 772 | bplText = $"{(int)(bpl * 2)}/16"; 773 | } 774 | if (t.Bpm > 0) 775 | g.DrawStringLayoutLTRB($"{bplText}", 776 | x + Config.ColWidth - 220 - 20 - 60, y + 52, 777 | x + Config.ColWidth - 220 - 20 - 6, y + 80, 778 | StringAdapterAlignment.Far); 779 | 780 | lastBpl = t.BeatsPerLine; 781 | } 782 | } 783 | 784 | public void DrawNoteLength(GraphicsAdapter g) 785 | { 786 | ColorDesc c = ColorDesc.FromArgb(0xff008b8b); 787 | 788 | g.SetColor(c); 789 | g.SetFont("exo", 10f, FontDescStyle.Bold); 790 | 791 | foreach (var note in _affAnalyzer.Notes) 792 | { 793 | if (note.TimePoint > Config.TotalTrackLength) 794 | break; 795 | 796 | float colHeight = Config.Rows * Config.SegmentLengthInBaseBpm; 797 | float y = colHeight - note.TimePoint / Config.TimingScale % colHeight; 798 | float x = Config.ColWidth * (note.TimePoint / Config.TimingScale / (int)colHeight); 799 | if (y <= 1) 800 | y = (int)colHeight; 801 | 802 | string? s = 803 | (note.Divide > 0 ? note.Divide.ToString() : "-") + 804 | (note.hasDot ? "." : "") + 805 | (note.beyondFull ? "-" : ""); 806 | 807 | g.DrawString(s, x + Config.ColWidth - 218, y + 37); 808 | } 809 | } 810 | 811 | public void DrawFooter(GraphicsAdapter g) 812 | { 813 | int footerX = 25; 814 | int footerY = Config.Rows * (int)Config.SegmentLengthInBaseBpm + 200 - 100 + 25; 815 | int footerW = Config.Cols * Config.ColWidth + 50; 816 | int footerH = 100; 817 | 818 | ColorDesc? rectColor = ColorDesc.FromArgb(Config.Side switch 819 | { 820 | 0 => 0xc8f0f0f0, 821 | 1 => 0xc8202020, 822 | 2 => 0xc8f0f0f0, 823 | _ => 0, 824 | }); 825 | 826 | g.FillRectangle(rectColor, footerX, footerY, footerW, footerH); 827 | 828 | g.SetColor(ColorDesc.FromArgb(Config.Side == 1 ? 0xffffffff : 0xff000000)); 829 | 830 | bool hasCover = Config.Cover.InnerImage is not null; 831 | if (hasCover) 832 | g.DrawImageScaled(Config.Cover, footerX, footerY, 100, 100); 833 | 834 | Regex enRegex = new(@"^[A-Za-z\d_\s/\(\)\+\=\-\.\[\]:\(\)&']+$"); 835 | 836 | string? title = Title + 837 | $" [ {DiffStr} {Rating:F1} ]" + 838 | $" Tap{_affAnalyzer.Tap} " + 839 | $"Hold{_affAnalyzer.Hold} " + 840 | $"Arc{_affAnalyzer.Arc[0] + _affAnalyzer.Arc[1]} " + 841 | $"[ Blue{_affAnalyzer.Arc[0]} Red{_affAnalyzer.Arc[1]} ] " + 842 | $"ArcTap{_affAnalyzer.ArcTap} " + 843 | $"Total{_affAnalyzer.Total}"; 844 | 845 | string? secondLine = (ChartBpm > 0 ? $"Bpm {ChartBpm} " : "") + $"{Artist} / {Charter}"; 846 | 847 | g.SetColor(ColorDesc.FromArgb(Config.Side == 1 ? 0xffffffff : 0xff000000)); 848 | //g.SetFont(enRegex.IsMatch(title) || enRegex.IsMatch(secondLine) ? "GeosansLight" : "Kazesawa Regular", 26f, FontDescStyle.Regular); 849 | g.SetFont("GeosansLight", 26f, FontDescStyle.Regular); 850 | g.DrawString(title, footerX + 15 + (hasCover ? 100 : 0), footerY + 10); 851 | 852 | g.DrawString(secondLine, footerX + 15 + (hasCover ? 100 : 0), footerY + 50); 853 | 854 | g.SetColor(ColorDesc.FromArgb(0xffff7f50)); 855 | g.SetFont("exo", 18f, FontDescStyle.Bold); 856 | 857 | g.DrawStringLayout("Generate by AffTools.Aff2Preview 2.1 ", 858 | footerX + footerW - 500, footerY + footerH - 30, 859 | 500, 30, 860 | StringAdapterAlignment.Far); 861 | } 862 | 863 | } 864 | -------------------------------------------------------------------------------- /Aff2Preview/AffTools/AffAnalyzer/Analyzer.cs: -------------------------------------------------------------------------------- 1 | using AffTools.AffReader; 2 | 3 | namespace AffTools.AffAnalyzer; 4 | 5 | internal class Analyzer 6 | { 7 | private List _noteRaws = new(); 8 | 9 | public List Notes { get; private set; } = new(); 10 | 11 | private readonly ArcaeaAffReader _affReader; 12 | 13 | public readonly List SegmentTimings = new(); 14 | 15 | public float totalTime; 16 | public float realTotalTime; 17 | public float baseBpm; 18 | public float baseBpl; 19 | public float baseTimePerSegment; 20 | public int segmentCountInBaseBpm; 21 | 22 | public readonly Dictionary timingCombos = new(); 23 | public readonly Dictionary timingTaps = new(); 24 | 25 | public int Tap = 0; 26 | public int Hold = 0; 27 | public readonly List Arc = new() { 0, 0, 0, 0 }; 28 | public int ArcTap = 0; 29 | public int Total = 0; 30 | public int TapTotal => Tap + ArcTap; 31 | 32 | public Analyzer(ArcaeaAffReader affReader) 33 | { 34 | _affReader = affReader; 35 | 36 | var globalTimingGroup = affReader.Events[0] as ArcaeaAffTiming; 37 | if (globalTimingGroup is not null) 38 | { 39 | baseBpm = globalTimingGroup.Bpm; 40 | baseBpl = globalTimingGroup.BeatsPerLine; 41 | baseTimePerSegment = 60 * 1000 * (int)baseBpl / baseBpm; 42 | } 43 | 44 | foreach (var ev in affReader.Events) 45 | { 46 | if (IsGroupNoInput(ev.TimingGroup)) 47 | continue; 48 | 49 | totalTime = ev switch 50 | { 51 | ArcaeaAffTap => MathF.Max(totalTime, ev.Timing), 52 | ArcaeaAffArc arc => MathF.Max(totalTime, arc.EndTiming), 53 | ArcaeaAffHold hd => MathF.Max(totalTime, hd.EndTiming), 54 | ArcaeaAffTiming tm => MathF.Max(totalTime, tm.Timing), 55 | _ => totalTime 56 | }; 57 | } 58 | 59 | realTotalTime = totalTime; 60 | totalTime += baseTimePerSegment / 4; 61 | 62 | for (double i = 0; i < totalTime; i += baseTimePerSegment) 63 | { 64 | segmentCountInBaseBpm++; 65 | } 66 | } 67 | 68 | /// 69 | /// To pair the scenecontrol statement 70 | /// 71 | /// 72 | public List<(ArcaeaAffSceneControl, ArcaeaAffSceneControl)> GetPairEnwidenLanes() 73 | { 74 | var list = new List(); 75 | var result = new List<(ArcaeaAffSceneControl, ArcaeaAffSceneControl)>(); 76 | 77 | foreach (var affEvent in _affReader.Events) 78 | { 79 | if (affEvent.Type != EventType.SceneControl) continue; 80 | if ((affEvent as ArcaeaAffSceneControl)?.SceneControlTypeName != "enwidenlanes") continue; 81 | 82 | list.Add((ArcaeaAffSceneControl)affEvent); 83 | } 84 | 85 | if (list.Count % 2 != 0) // Pair the enwidenlane 86 | { 87 | var end = new ArcaeaAffSceneControl 88 | { 89 | Timing = _affReader.Events.Last().Timing, 90 | Type = EventType.SceneControl, 91 | Parameters = new List() { 0, 0 }, 92 | SceneControlTypeName = "enwidenlanes" 93 | }; 94 | list.Add(end); 95 | } 96 | 97 | for (int i = 0; i < list.Count - 1; i++) 98 | { 99 | var statement = list[i]; 100 | if (Convert.ToInt32(statement.Parameters[1]) != 1) continue; 101 | if (Convert.ToInt32(list[i + 1].Parameters[1]) == 0) result.Add((statement, list[i + 1])); 102 | } 103 | return result; 104 | } 105 | 106 | public List<(int, int)> Get4LaneInterval(int? end) 107 | { 108 | var list = new List(); 109 | var result = new List<(int, int)>(); 110 | 111 | foreach (var affEvent in _affReader.Events) 112 | { 113 | if (affEvent.Type != EventType.SceneControl) continue; 114 | if ((affEvent as ArcaeaAffSceneControl)?.SceneControlTypeName != "enwidenlanes") continue; 115 | 116 | list.Add((ArcaeaAffSceneControl)affEvent); 117 | } 118 | 119 | 120 | if (list.Count == 0) 121 | { 122 | result.Add((0, end ?? _affReader.Events.Last().Timing)); 123 | return result; 124 | } 125 | 126 | var startSegment = 0; 127 | var pair = GetPairEnwidenLanes(); 128 | foreach (var statement in list) 129 | { 130 | if (pair.TrueForAll(x => x.Item1.Timing != startSegment && x.Item2.Timing != statement.Timing)) 131 | result.Add((startSegment, statement.Timing)); 132 | startSegment = statement.Timing; 133 | } 134 | 135 | if (Convert.ToInt32(list.Last().Parameters[1]) == 0) 136 | { 137 | result.Add((list.Last().Timing, end ?? _affReader.Events.Last().Timing)); 138 | } 139 | 140 | return result; 141 | } 142 | 143 | public void AnalyzeNotes() 144 | { 145 | _noteRaws.Clear(); 146 | 147 | Dictionary> arcColors = new(); 148 | arcColors.Add(0, new()); 149 | arcColors.Add(1, new()); 150 | arcColors.Add(2, new()); 151 | arcColors.Add(3, new()); 152 | 153 | foreach (var ev in _affReader.Events) 154 | { 155 | if (_affReader.TimingGroupProperties[ev.TimingGroup].NoInput) 156 | continue; 157 | 158 | switch (ev) 159 | { 160 | case ArcaeaAffTap evTap: 161 | _noteRaws.Add(new(ev.Timing, 0)); 162 | break; 163 | case ArcaeaAffHold evHold: 164 | _noteRaws.Add(new(ev.Timing, evHold.EndTiming - evHold.Timing)); 165 | break; 166 | case ArcaeaAffArc evArc: 167 | { 168 | if (!evArc.IsVoid) 169 | arcColors[evArc.Color].Add(evArc); 170 | 171 | if (evArc.ArcTaps is not null) 172 | { 173 | foreach (var at in evArc.ArcTaps) 174 | { 175 | _noteRaws.Add(new(at, 0)); 176 | } 177 | } 178 | 179 | break; 180 | } 181 | } 182 | } 183 | 184 | foreach (var (_, arcList) in arcColors) 185 | { 186 | for (var i = arcList.Count - 1; i > 0; i--) 187 | { 188 | var arc = arcList[i]; 189 | var prev = arcList[i - 1]; 190 | if (arc.Timing != prev.EndTiming) 191 | { 192 | _noteRaws.Add(new(arc.Timing, 0)); 193 | } 194 | } 195 | if (arcList.Any()) 196 | _noteRaws.Add(new(arcList[0].Timing, 0)); 197 | } 198 | 199 | _noteRaws = _noteRaws.OrderBy(x => x.TimePoint).ToList(); 200 | 201 | Notes.Clear(); 202 | for (var i = 0; i < _noteRaws.Count - 1; i++) 203 | { 204 | var dt = _noteRaws[i + 1].TimePoint - _noteRaws[i].TimePoint; 205 | if (dt <= 3) 206 | continue; 207 | 208 | var currBpm = GetCurrentTiming(_noteRaws[i].TimePoint, 0).Bpm; 209 | Note n = new(); 210 | if (!n.Analyze(_noteRaws[i].TimePoint, dt, currBpm)) 211 | n.Analyze(_noteRaws[i].TimePoint, dt, baseBpm); 212 | Notes.Add(n); 213 | } 214 | 215 | } 216 | 217 | private ArcaeaAffTiming GetCurrentTiming(int timing, int timingGroup) 218 | { 219 | var timings = _affReader.Events 220 | .Where(ev => ev is ArcaeaAffTiming && ev.TimingGroup == timingGroup) 221 | .Select(x => x as ArcaeaAffTiming); 222 | 223 | return timings.Last(x => x.Timing <= timing); 224 | } 225 | 226 | public bool IsGroupNoInput(int timingGroup) 227 | { 228 | return _affReader.TimingGroupProperties[timingGroup].NoInput; 229 | } 230 | 231 | public int CalcSingleHold(int start, int end, bool hasHead, float bpm, float tpdf) 232 | { 233 | if (start >= end) return 0; 234 | 235 | // Do NOT check "Code Optimization" in the Project Properties!!! 236 | // I HATE FLOATING POINT ERROR... 237 | float d = end - start; 238 | float unit = bpm >= 256 ? 60000 : 30000; 239 | unit /= bpm; 240 | unit /= tpdf; 241 | float cf = d / unit; 242 | var ci = (int)cf; 243 | return ci <= 1 ? 1 : hasHead ? ci - 1 : ci; 244 | } 245 | 246 | public int GetCombo(int timing) 247 | { 248 | if (timingCombos.ContainsKey(timing)) 249 | return timingCombos[timing]; 250 | 251 | try 252 | { 253 | var t = timingCombos.Last(x => x.Key <= timing); 254 | return t.Value; 255 | } 256 | catch 257 | { 258 | return 0; 259 | } 260 | } 261 | 262 | public float GetTapDensity(int timing, int threshold) 263 | { 264 | try 265 | { 266 | var t = timingTaps.Where(x => (timing - threshold <= x.Key && x.Key < timing + threshold)).ToList(); 267 | if (!t.Any()) 268 | return 0; 269 | float density = (float)t.Sum(x => x.Value) * 1000 / (threshold * 2); 270 | return density; 271 | } 272 | catch 273 | { 274 | return 0; 275 | } 276 | } 277 | 278 | public void CalcNotes() 279 | { 280 | List arcs = 281 | _affReader.Events.Where(x => x is ArcaeaAffArc a && !a.IsVoid && !IsGroupNoInput(x.TimingGroup)) 282 | .Select(x => x as ArcaeaAffArc).ToList(); 283 | 284 | arcs.Sort((a, b) => a.Timing.CompareTo(b.Timing)); 285 | ArcaeaAffArc[] scra = arcs.ToArray(); 286 | Array.Sort(scra, (a, b) => a.EndTiming.CompareTo(b.EndTiming)); 287 | int m = scra.Length; 288 | int i = 0; 289 | 290 | List timingNotePoints = new(); 291 | 292 | foreach (ArcaeaAffArc evArc in arcs) 293 | { 294 | for (var j = i; j < m; ++j) 295 | { 296 | ArcaeaAffArc prev = scra[j]; 297 | if (prev.EndTiming <= evArc.Timing - 10) 298 | { 299 | i = j; 300 | } 301 | else if (prev.EndTiming >= evArc.Timing + 10) 302 | { 303 | break; 304 | } 305 | else if (evArc != prev && evArc.YStart == prev.YEnd && Math.Abs(evArc.XStart - prev.XEnd) < 0.1) 306 | { 307 | evArc.HasHead = false; 308 | } 309 | } 310 | } 311 | 312 | List tapTimings = new(); 313 | 314 | foreach (var ev in _affReader.Events) 315 | { 316 | if (IsGroupNoInput(ev.TimingGroup)) 317 | continue; 318 | 319 | switch (ev) 320 | { 321 | case ArcaeaAffTap: 322 | timingNotePoints.Add(ev.Timing); 323 | Tap++; 324 | Total++; 325 | tapTimings.Add(ev.Timing); 326 | break; 327 | case ArcaeaAffHold evHold: 328 | { 329 | var timing = GetCurrentTiming(evHold.Timing, evHold.TimingGroup); 330 | var t = CalcSingleHold(evHold.Timing, evHold.EndTiming, true, timing.Bpm, _affReader.TimingPointDensityFactor); 331 | for (var tx = 0; tx < t; tx++) 332 | { 333 | timingNotePoints.Add(evHold.Timing + (evHold.EndTiming - evHold.Timing) * tx / t); 334 | } 335 | Hold += t; 336 | Total += t; 337 | break; 338 | } 339 | case ArcaeaAffArc evArc: 340 | { 341 | ArcTap += evArc.ArcTaps?.Count ?? 0; 342 | 343 | for (var x = 0; x < evArc.ArcTaps?.Count; x++) 344 | { 345 | timingNotePoints.Add(evArc.ArcTaps[x]); 346 | Total++; 347 | tapTimings.Add(evArc.ArcTaps[x]); 348 | } 349 | 350 | if (evArc.IsVoid) 351 | continue; 352 | 353 | var timing = GetCurrentTiming(evArc.Timing, evArc.TimingGroup); 354 | var t = CalcSingleHold(evArc.Timing, evArc.EndTiming, evArc.HasHead, timing.Bpm, _affReader.TimingPointDensityFactor); 355 | for (var tx = 0; tx < t; tx++) 356 | { 357 | timingNotePoints.Add(evArc.Timing + (evArc.EndTiming - evArc.Timing) * tx / t); 358 | } 359 | Arc[evArc.Color] += t; 360 | Total += t; 361 | break; 362 | } 363 | } 364 | } 365 | 366 | tapTimings = tapTimings.OrderBy(x => x).ToList(); 367 | 368 | foreach (var timing in tapTimings) 369 | { 370 | if (timingTaps.ContainsKey(timing)) 371 | timingTaps[timing]++; 372 | 373 | else 374 | timingTaps[timing] = 1; 375 | } 376 | 377 | timingNotePoints = timingNotePoints.OrderBy(x => x).ToList(); 378 | int total = 0; 379 | foreach (var timing in timingNotePoints) 380 | { 381 | if (timingCombos.ContainsKey(timing)) 382 | { 383 | total++; 384 | timingCombos[timing]++; 385 | } 386 | else 387 | { 388 | total++; 389 | timingCombos[timing] = total; 390 | } 391 | } 392 | 393 | Console.WriteLine($"F{Tap} L{Hold} A{Arc[0] + Arc[1]} (blue{Arc[0]} red{Arc[1]}) S{ArcTap} t:{Tap + Hold + Arc[0] + Arc[1] + ArcTap}"); 394 | } 395 | 396 | public void AnalyzeSegments() 397 | { 398 | List Timings = _affReader.Events 399 | .Where(ev => ev is ArcaeaAffTiming && ev.TimingGroup == 0) 400 | .Select(x => x as ArcaeaAffTiming).ToList(); 401 | 402 | for (int i = 0; i < Timings.Count - 1; ++i) 403 | { 404 | float segment = Timings[i].Bpm == 0 ? 405 | Timings[i + 1].Timing - Timings[i].Timing : 406 | 60000 / Math.Abs(Timings[i].Bpm) * Timings[i].BeatsPerLine; 407 | if (segment == 0) continue; 408 | int n = 0; 409 | while (true) 410 | { 411 | float j = Timings[i].Timing + n++ * segment; 412 | if (j >= Timings[i + 1].Timing) 413 | break; 414 | SegmentTimings.Add(j); 415 | } 416 | } 417 | 418 | if (Timings.Count >= 1) 419 | { 420 | float segmentRemain = Timings[^1].Bpm == 0 ? totalTime - Timings[^1].Timing 421 | : 60000 / Math.Abs(Timings[^1].Bpm) * Timings[^1].BeatsPerLine; 422 | if (segmentRemain != 0) 423 | { 424 | int n = 0; 425 | float j = Timings[^1].Timing; 426 | while (j < totalTime) 427 | { 428 | j = Timings[^1].Timing + n++ * segmentRemain; 429 | SegmentTimings.Add(j); 430 | } 431 | } 432 | } 433 | 434 | if (Timings.Count >= 1 && Timings[0].Bpm != 0 && Timings[0].BeatsPerLine != 0) 435 | { 436 | float t = 0; 437 | float delta = 60000 / Math.Abs(Timings[0].Bpm) * Timings[0].BeatsPerLine; 438 | int n = 0; 439 | if (delta != 0) 440 | { 441 | while (t >= -3000) 442 | { 443 | n++; 444 | t = -n * delta; 445 | SegmentTimings.Insert(0, t); 446 | } 447 | } 448 | } 449 | } 450 | 451 | public Dictionary GetChartQuality(int threshold) 452 | { 453 | List<(int, ArcaeaAffEvent)> timingList = new(); 454 | 455 | foreach (var ev in _affReader.Events) 456 | { 457 | if (IsGroupNoInput(ev.TimingGroup)) 458 | continue; 459 | 460 | switch (ev) 461 | { 462 | case ArcaeaAffTap evTap: 463 | timingList.Add((evTap.Timing, ev)); 464 | break; 465 | case ArcaeaAffHold evHold: 466 | timingList.Add((evHold.Timing, ev)); 467 | break; 468 | case ArcaeaAffArc evArc: 469 | { 470 | if (evArc.ArcTaps is not null) 471 | { 472 | timingList.AddRange(evArc.ArcTaps.Select(v => (v, ev))); 473 | } 474 | 475 | break; 476 | } 477 | 478 | } 479 | } 480 | 481 | Dictionary msec = new(); 482 | for (int i = -threshold; i <= threshold; i++) 483 | msec.Add(i, 0); 484 | 485 | for (int i = 0; i < timingList.Count; i++) 486 | { 487 | var (timing, ev) = timingList[i]; 488 | 489 | for (int t = 0; t < timingList.Count; t++) 490 | { 491 | var (timingt, evt) = timingList[t]; 492 | if (evt == ev) 493 | continue; 494 | 495 | var dt = timingt - timing; 496 | if (Math.Abs(dt) <= threshold) 497 | msec[dt]++; 498 | } 499 | } 500 | 501 | return msec; 502 | } 503 | 504 | /// 505 | /// Analyze charts for double-tap alignment problem 506 | /// 507 | /// 508 | public static void OutputAllChartDoubleTapAnalyze(string affFolder) 509 | { 510 | var d = new DirectoryInfo(affFolder); 511 | int total = 0; 512 | int totalTwin = 0; 513 | int ptotal = 0; 514 | int ptotalTwin = 0; 515 | int threshold = 5; 516 | 517 | Dictionary dtList = new(); 518 | for (int i = 0; i <= threshold; i++) 519 | dtList.Add(i, 0); 520 | 521 | foreach (var f in d.GetDirectories()) 522 | { 523 | foreach (var f2 in f.GetFiles("*.aff")) 524 | { 525 | ArcaeaAffReader affReader = new(); 526 | affReader.Parse(f2.FullName); 527 | 528 | Analyzer analyzer = new(affReader); 529 | var result = analyzer.GetChartQuality(threshold); 530 | 531 | totalTwin += result[0]; 532 | 533 | if (result[1] > 0) 534 | { 535 | ptotal++; 536 | for (int i = 1; i <= threshold; i++) 537 | ptotalTwin += result[i]; 538 | 539 | for (int i = 0; i <= threshold; i++) 540 | dtList[i] += result[i]; 541 | 542 | Console.WriteLine(f2.FullName); 543 | Console.WriteLine($"dt: " + 544 | string.Join(" ", 545 | result.Where(k => k.Key >= 0 && k.Value > 0). 546 | Select(k => $"[{k.Key},{k.Value}]") 547 | )); 548 | } 549 | 550 | total++; 551 | } 552 | } 553 | 554 | Console.WriteLine($"total problem charts: {ptotal}/{total}"); 555 | Console.WriteLine($"total problem doubles: {ptotalTwin}/{totalTwin + ptotalTwin}"); 556 | for (int i = 1; i <= threshold; i++) 557 | Console.WriteLine($"total {i}ms: {dtList[i]}"); 558 | } 559 | 560 | } 561 | -------------------------------------------------------------------------------- /Aff2Preview/AffTools/AffAnalyzer/Note.cs: -------------------------------------------------------------------------------- 1 | namespace AffTools.AffAnalyzer; 2 | 3 | internal struct NoteRaw 4 | { 5 | public int TimePoint = 0; 6 | public int Duration = 0; 7 | 8 | public NoteRaw(int timePoint, int duration) 9 | { 10 | TimePoint = timePoint; 11 | Duration = duration; 12 | } 13 | } 14 | 15 | internal class Note 16 | { 17 | public int TimePoint { get; set; } = 0; 18 | public int Duration { get; set; } = 0; 19 | public int Divide { get; set; } = -1; 20 | 21 | public bool beyondFull = false; 22 | public bool hasDot = false; 23 | public bool isTriplet = false; 24 | 25 | public float InvDuration 26 | { 27 | get 28 | { 29 | if (Duration == 0) return 0; 30 | return 1000 / Duration; 31 | } 32 | } 33 | 34 | private static bool isDoubleEqual(double a, double b, double e) => Math.Abs(a - b) <= e; 35 | 36 | public Note(int timeing, int length, double bpm) 37 | { 38 | Analyze(timeing, length, bpm); 39 | } 40 | 41 | public Note() 42 | { 43 | } 44 | 45 | public bool Analyze(int timing, int length, double bpm) 46 | { 47 | var threshold = 3.5; 48 | 49 | Duration = length; 50 | TimePoint = timing; 51 | var time_full_note = 60 * 1000 * 4 / bpm; 52 | if (length > time_full_note) 53 | { 54 | beyondFull = true; 55 | return true; 56 | } 57 | 58 | if (isDoubleEqual(length, time_full_note, threshold)) 59 | { 60 | Divide = 1; 61 | return true; 62 | } 63 | 64 | for (var i = 2; i <= 64;) 65 | { 66 | var t_len = time_full_note / i; 67 | var t_dot_len = t_len * 1.5; 68 | 69 | if (isDoubleEqual(length, t_len, threshold)) 70 | { 71 | Divide = i; 72 | return true; 73 | } 74 | 75 | if (isDoubleEqual(length, t_dot_len, threshold)) 76 | { 77 | Divide = i; 78 | hasDot = true; 79 | return true; 80 | } 81 | 82 | i += i switch 83 | { 84 | < 4 => 1, 85 | < 28 => 2, 86 | < 32 => 4, 87 | <= 64 => 8, 88 | _ => 1 89 | }; 90 | } 91 | 92 | return false; 93 | } 94 | } -------------------------------------------------------------------------------- /Aff2Preview/AffTools/AffReader/AffReader.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | namespace AffTools.AffReader; 4 | 5 | public class ArcaeaAffReader 6 | { 7 | public int TotalTimingGroup = 1; 8 | 9 | public int CurrentTimingGroup; 10 | 11 | public int AudioOffset; 12 | public float TimingPointDensityFactor = 1; 13 | 14 | public List Events = new List(); 15 | 16 | public List TimingGroupProperties = new List(); 17 | 18 | private static EventType DetermineType(string line) 19 | { 20 | if (line.StartsWith("(")) 21 | return EventType.Tap; 22 | if (line.StartsWith("timing(")) 23 | return EventType.Timing; 24 | if (line.StartsWith("hold(")) 25 | return EventType.Hold; 26 | if (line.StartsWith("arc(")) 27 | return EventType.Arc; 28 | if (line.StartsWith("camera(")) 29 | return EventType.Camera; 30 | if (line.StartsWith("scenecontrol(")) 31 | return EventType.SceneControl; 32 | if (line.StartsWith("timinggroup(")) 33 | return EventType.TimingGroup; 34 | if (line.StartsWith("};")) 35 | return EventType.TimingGroupEnd; 36 | return EventType.Unknown; 37 | } 38 | 39 | private void ParseTiming(string line) 40 | { 41 | try 42 | { 43 | var stringParser = new AffStringParser(line); 44 | stringParser.Skip(7); 45 | var num = stringParser.ReadInt(","); 46 | var num2 = stringParser.ReadFloat(","); 47 | var num3 = stringParser.ReadFloat(")"); 48 | Events.Add(new ArcaeaAffTiming 49 | { 50 | Timing = num, 51 | BeatsPerLine = num3, 52 | Bpm = num2, 53 | Type = EventType.Timing, 54 | TimingGroup = CurrentTimingGroup 55 | }); 56 | if (num3 < 0f) 57 | throw new ArcaeaAffFormatException("节拍线密度小于0"); 58 | if (num == 0 && num2 == 0f) 59 | throw new ArcaeaAffFormatException("基准BPM为0(Timing为0的timing事件BPM不能为0)"); 60 | } 61 | catch (ArcaeaAffFormatException) 62 | { 63 | throw; 64 | } 65 | catch (Exception) 66 | { 67 | throw new ArcaeaAffFormatException("符号错误"); 68 | } 69 | } 70 | 71 | private void ParseTap(string line, bool noInput) 72 | { 73 | try 74 | { 75 | var stringParser = new AffStringParser(line); 76 | stringParser.Skip(1); 77 | var timing = stringParser.ReadInt(","); 78 | var num = stringParser.ReadInt(")"); 79 | if (!noInput) Events.Add(new ArcaeaAffTap 80 | { 81 | Timing = timing, 82 | Track = num, 83 | Type = EventType.Tap, 84 | TimingGroup = CurrentTimingGroup, 85 | NoInput = noInput 86 | }); 87 | if (num is < 0 or > 5) 88 | throw new ArcaeaAffFormatException("轨道错误"); 89 | } 90 | catch (ArcaeaAffFormatException) 91 | { 92 | throw; 93 | } 94 | catch (Exception) 95 | { 96 | throw new ArcaeaAffFormatException("符号错误"); 97 | } 98 | } 99 | 100 | private void ParseHold(string line, bool noInput) 101 | { 102 | try 103 | { 104 | var stringParser = new AffStringParser(line); 105 | stringParser.Skip(5); 106 | var num = stringParser.ReadInt(","); 107 | var num2 = stringParser.ReadInt(","); 108 | var num3 = stringParser.ReadInt(")"); 109 | if (!noInput) Events.Add(new ArcaeaAffHold 110 | { 111 | Timing = num, 112 | EndTiming = num2, 113 | Track = num3, 114 | Type = EventType.Hold, 115 | TimingGroup = CurrentTimingGroup, 116 | NoInput = noInput 117 | }); 118 | if (num3 is < 0 or > 5) 119 | throw new ArcaeaAffFormatException("轨道错误"); 120 | if (num2 < num) 121 | throw new ArcaeaAffFormatException("持续时间小于0"); 122 | } 123 | catch (ArcaeaAffFormatException) 124 | { 125 | throw; 126 | } 127 | catch (Exception) 128 | { 129 | throw new ArcaeaAffFormatException("符号错误"); 130 | } 131 | } 132 | 133 | private void ParseArc(string line, bool noInput) 134 | { 135 | try 136 | { 137 | var stringParser = new AffStringParser(line); 138 | stringParser.Skip(4); 139 | var num = stringParser.ReadInt(","); 140 | var num2 = stringParser.ReadInt(","); 141 | var xStart = stringParser.ReadFloat(","); 142 | var xEnd = stringParser.ReadFloat(","); 143 | var lineType = stringParser.ReadString(","); 144 | var yStart = stringParser.ReadFloat(","); 145 | var yEnd = stringParser.ReadFloat(","); 146 | var color = stringParser.ReadInt(","); 147 | var fx = stringParser.ReadString(","); 148 | var isVoid = stringParser.ReadBool(")"); 149 | List? list = null; 150 | if (stringParser.Current != ";") 151 | { 152 | list = new List(); 153 | isVoid = true; 154 | do 155 | { 156 | stringParser.Skip(8); 157 | list.Add(stringParser.ReadInt(")")); 158 | } 159 | while (!(stringParser.Current != ",")); 160 | } 161 | if (!noInput) Events.Add(new ArcaeaAffArc 162 | { 163 | Timing = num, 164 | EndTiming = num2, 165 | XStart = xStart, 166 | XEnd = xEnd, 167 | LineType = lineType, 168 | YStart = yStart, 169 | YEnd = yEnd, 170 | Color = color, 171 | Fx = fx, 172 | IsVoid = isVoid, 173 | Type = EventType.Arc, 174 | ArcTaps = list, 175 | TimingGroup = CurrentTimingGroup, 176 | NoInput = noInput 177 | }); 178 | if (num2 < num) 179 | throw new ArcaeaAffFormatException("持续时间小于0"); 180 | } 181 | catch (ArcaeaAffFormatException) 182 | { 183 | throw; 184 | } 185 | catch (Exception) 186 | { 187 | throw new ArcaeaAffFormatException("符号错误"); 188 | } 189 | } 190 | 191 | private void ParseCamera(string line) 192 | { 193 | try 194 | { 195 | var stringParser = new AffStringParser(line); 196 | stringParser.Skip(7); 197 | var timing = stringParser.ReadInt(","); 198 | var move = new Vector3(stringParser.ReadFloat(","), stringParser.ReadFloat(","), stringParser.ReadFloat(",")); 199 | var rotate = new Vector3(stringParser.ReadFloat(","), stringParser.ReadFloat(","), stringParser.ReadFloat(",")); 200 | var cameraType = stringParser.ReadString(","); 201 | var num = stringParser.ReadInt(")"); 202 | Events.Add(new ArcaeaAffCamera 203 | { 204 | Timing = timing, 205 | Duration = num, 206 | Move = move, 207 | Rotate = rotate, 208 | CameraType = cameraType, 209 | Type = EventType.Camera 210 | }); 211 | if (num < 0) 212 | throw new ArcaeaAffFormatException("持续时间小于0"); 213 | } 214 | catch (ArcaeaAffFormatException) 215 | { 216 | throw; 217 | } 218 | catch (Exception) 219 | { 220 | throw new ArcaeaAffFormatException("符号错误"); 221 | } 222 | } 223 | 224 | private void ParseSceneControl(string line) 225 | { 226 | try 227 | { 228 | try 229 | { 230 | var stringParser = new AffStringParser(line); 231 | stringParser.Skip(13); 232 | var timing = stringParser.ReadInt(","); 233 | var sceneControlTypeName = stringParser.ReadString(",").Trim(); 234 | var text = stringParser.ReadString(); 235 | text = text.Substring(0, text.LastIndexOf(')')); 236 | var array = text.Split(','); 237 | var list = new List(); 238 | var text2 = ""; 239 | var array2 = array; 240 | foreach (var text3 in array2) 241 | { 242 | var num = text3[0] == '\''; 243 | var flag = text2 != ""; 244 | var flag2 = text3[^1] == '\''; 245 | if (num || flag || flag2) 246 | text2 += text3; 247 | if (flag2) 248 | { 249 | list.Add(text2.Substring(1, text2.Length - 2)); 250 | text2 = ""; 251 | } 252 | if (!num && !flag && !flag2) 253 | list.Add(float.Parse(text3)); 254 | } 255 | Events.Add(new ArcaeaAffSceneControl 256 | { 257 | Timing = timing, 258 | Type = EventType.SceneControl, 259 | Parameters = list, 260 | SceneControlTypeName = sceneControlTypeName, 261 | TimingGroup = CurrentTimingGroup 262 | }); 263 | } 264 | catch (Exception) 265 | { 266 | var stringParser2 = new AffStringParser(line); 267 | stringParser2.Skip(13); 268 | var timing2 = stringParser2.ReadInt(","); 269 | var sceneControlTypeName2 = stringParser2.ReadString(")"); 270 | var parameters = new List(); 271 | Events.Add(new ArcaeaAffSceneControl 272 | { 273 | Timing = timing2, 274 | Type = EventType.SceneControl, 275 | Parameters = parameters, 276 | SceneControlTypeName = sceneControlTypeName2, 277 | TimingGroup = CurrentTimingGroup 278 | }); 279 | } 280 | } 281 | catch (ArcaeaAffFormatException) 282 | { 283 | throw; 284 | } 285 | catch (Exception) 286 | { 287 | throw new ArcaeaAffFormatException("符号错误"); 288 | } 289 | } 290 | 291 | private static bool NoInputGroup(string line) 292 | { 293 | try 294 | { 295 | var stringParser = new AffStringParser(line); 296 | stringParser.Skip(12); 297 | return stringParser.ReadString(")") == "noinput"; 298 | } 299 | catch (ArcaeaAffFormatException) 300 | { 301 | return false; 302 | } 303 | catch (Exception) 304 | { 305 | return false; 306 | } 307 | } 308 | 309 | public void Parse(string path) 310 | { 311 | TotalTimingGroup = 1; 312 | CurrentTimingGroup = 0; 313 | TimingGroupProperties.Add(new TimingGroupProperties()); 314 | var noInput = false; 315 | var array = File.ReadAllLines(path); 316 | try 317 | { 318 | AudioOffset = int.Parse(array[0].Replace("AudioOffset:", "")); 319 | } 320 | catch (Exception) 321 | { 322 | throw new ArcaeaAffFormatException(array[0], 1); 323 | } 324 | int i; 325 | if (array[1].Contains("TimingPointDensityFactor")) 326 | { 327 | try 328 | { 329 | TimingPointDensityFactor = float.Parse(array[1].Replace("TimingPointDensityFactor:", "")); 330 | } 331 | catch (Exception) 332 | { 333 | throw new ArcaeaAffFormatException(array[1], 2); 334 | } 335 | try 336 | { 337 | ParseTiming(array[3]); 338 | } 339 | catch (Exception) 340 | { 341 | throw new ArcaeaAffFormatException(EventType.Timing, array[3], 4); 342 | } 343 | i = 4; 344 | } 345 | else 346 | { 347 | try 348 | { 349 | ParseTiming(array[2]); 350 | } 351 | catch (Exception) 352 | { 353 | throw new ArcaeaAffFormatException(EventType.Timing, array[2], 3); 354 | } 355 | i = 3; 356 | } 357 | 358 | for (; i < array.Length; i++) 359 | { 360 | var text = array[i].Trim(); 361 | var eventType = DetermineType(text); 362 | try 363 | { 364 | switch (eventType) 365 | { 366 | case EventType.Timing: 367 | ParseTiming(text); 368 | break; 369 | case EventType.Tap: 370 | ParseTap(text, noInput); 371 | break; 372 | case EventType.Hold: 373 | ParseHold(text, noInput); 374 | break; 375 | case EventType.Arc: 376 | ParseArc(text, noInput); 377 | break; 378 | case EventType.Camera: 379 | ParseCamera(text); 380 | break; 381 | case EventType.SceneControl: 382 | ParseSceneControl(text); 383 | break; 384 | case EventType.TimingGroup: 385 | TotalTimingGroup++; 386 | CurrentTimingGroup = TotalTimingGroup - 1; 387 | noInput = NoInputGroup(text); 388 | while (TimingGroupProperties.Count <= CurrentTimingGroup) 389 | { 390 | TimingGroupProperties.Add(new TimingGroupProperties()); 391 | } 392 | TimingGroupProperties[CurrentTimingGroup] = new TimingGroupProperties 393 | { 394 | NoInput = noInput, 395 | }; 396 | break; 397 | case EventType.TimingGroupEnd: 398 | CurrentTimingGroup = 0; 399 | noInput = false; 400 | break; 401 | case EventType.Unknown: 402 | break; 403 | } 404 | } 405 | catch (ArcaeaAffFormatException ex3) 406 | { 407 | throw new ArcaeaAffFormatException(eventType, text, i + 1, ex3.Reason); 408 | } 409 | catch (Exception) 410 | { 411 | throw new ArcaeaAffFormatException(eventType, text, i + 1); 412 | } 413 | } 414 | Events.Sort((a, b) => a.Timing.CompareTo(b.Timing)); 415 | if (CurrentTimingGroup == 0) return; 416 | throw new ArcaeaAffFormatException("Timing Group { 与 } 数量不匹配,请确保\ntiminggroup(){\n与\n};\n各占一行"); 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /Aff2Preview/AffTools/AffReader/AffStringParser.cs: -------------------------------------------------------------------------------- 1 | namespace AffTools.AffReader; 2 | 3 | public class AffStringParser 4 | { 5 | private int pos; 6 | private string str; 7 | 8 | public AffStringParser(string str) 9 | { 10 | this.str = str; 11 | } 12 | 13 | public void Skip(int length) 14 | { 15 | pos += length; 16 | } 17 | 18 | public float ReadFloat(string? terminator = null) 19 | { 20 | int end = terminator != null ? str.IndexOf(terminator, pos) : str.Length - 1; 21 | float value = float.Parse(str.Substring(pos, end - pos)); 22 | pos += end - pos + 1; 23 | return value; 24 | } 25 | 26 | public int ReadInt(string? terminator = null) 27 | { 28 | int end = terminator != null ? str.IndexOf(terminator, pos) : str.Length - 1; 29 | int value = int.Parse(str.Substring(pos, end - pos)); 30 | pos += end - pos + 1; 31 | return value; 32 | } 33 | 34 | public bool ReadBool(string? terminator = null) 35 | { 36 | int end = terminator != null ? str.IndexOf(terminator, pos) : str.Length - 1; 37 | bool value = bool.Parse(str.Substring(pos, end - pos)); 38 | pos += end - pos + 1; 39 | return value; 40 | } 41 | 42 | public string ReadString(string? terminator = null) 43 | { 44 | int end = terminator != null ? str.IndexOf(terminator, pos) : str.Length - 1; 45 | string value = str.Substring(pos, end - pos); 46 | pos += end - pos + 1; 47 | return value; 48 | } 49 | 50 | public string Current 51 | { 52 | get 53 | { 54 | return str[pos].ToString(); 55 | } 56 | } 57 | 58 | public string Peek(int count) 59 | { 60 | return str.Substring(pos, count); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Aff2Preview/AffTools/AffReader/ArcAlgorithm.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace AffTools.AffReader; 3 | 4 | public static class ArcAlgorithm 5 | { 6 | public static float ArcXToWorld(float x) 7 | { 8 | return -8.5f * x + 4.25f; 9 | } 10 | public static float ArcYToWorld(float y) 11 | { 12 | return 1 + 4.5f * y; 13 | } 14 | public static float WorldXToArc(float x) 15 | { 16 | return (x - 4.25f) / -8.5f; 17 | } 18 | public static float WorldYToArc(float y) 19 | { 20 | return (y - 1) / 4.5f; 21 | } 22 | public static float S(float start, float end, float t) 23 | { 24 | return (1 - t) * start + end * t; 25 | } 26 | public static float O(float start, float end, float t) 27 | { 28 | return start + (end - start) * (1 - MathF.Cos(1.5707963f * t)); 29 | } 30 | public static float I(float start, float end, float t) 31 | { 32 | return start + (end - start) * MathF.Sin(1.5707963f * t); 33 | } 34 | public static float B(float start, float end, float t) 35 | { 36 | float o = 1 - t; 37 | return MathF.Pow(o, 3) * start + 3 * MathF.Pow(o, 2) * t * start + 3 * o * MathF.Pow(t, 2) * end + MathF.Pow(t, 3) * end; 38 | } 39 | 40 | public static float X(float start, float end, float t, ArcLineType type) 41 | { 42 | switch (type) 43 | { 44 | default: 45 | case ArcLineType.S: 46 | return S(start, end, t); 47 | case ArcLineType.B: 48 | return B(start, end, t); 49 | case ArcLineType.Si: 50 | case ArcLineType.SiSi: 51 | case ArcLineType.SiSo: 52 | return I(start, end, t); 53 | case ArcLineType.So: 54 | case ArcLineType.SoSi: 55 | case ArcLineType.SoSo: 56 | return O(start, end, t); 57 | } 58 | } 59 | public static float Y(float start, float end, float t, ArcLineType type) 60 | { 61 | switch (type) 62 | { 63 | default: 64 | case ArcLineType.S: 65 | case ArcLineType.Si: 66 | case ArcLineType.So: 67 | return S(start, end, t); 68 | case ArcLineType.B: 69 | return B(start, end, t); 70 | case ArcLineType.SiSi: 71 | case ArcLineType.SoSi: 72 | return I(start, end, t); 73 | case ArcLineType.SiSo: 74 | case ArcLineType.SoSo: 75 | return O(start, end, t); 76 | } 77 | } 78 | 79 | public static float Qi(float value) 80 | { 81 | return value * value * value; 82 | } 83 | public static float Qo(float value) 84 | { 85 | return (value = value - 1) * value * value + 1; 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /Aff2Preview/AffTools/AffReader/ArcaeaFileFormat.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | namespace AffTools.AffReader 4 | { 5 | public enum ArcLineType 6 | { 7 | B, 8 | S, 9 | Si, 10 | So, 11 | SiSi, 12 | SiSo, 13 | SoSi, 14 | SoSo 15 | } 16 | 17 | namespace Advanced 18 | { 19 | public enum SpecialType 20 | { 21 | Unknown, 22 | TextArea, 23 | Fade 24 | } 25 | 26 | public class ArcadeAffSpecial : ArcaeaAffEvent 27 | { 28 | public SpecialType SpecialType; 29 | public string param1; 30 | public string param2; 31 | public string param3; 32 | } 33 | } 34 | 35 | public class ArcaeaAffFormatException : Exception 36 | { 37 | public string? Reason; 38 | 39 | public ArcaeaAffFormatException(string? reason) : base(reason) 40 | { 41 | Reason = reason; 42 | } 43 | 44 | public ArcaeaAffFormatException(string content, int line) 45 | { 46 | Console.WriteLine($"在第 {line} 行发生读取错误。\n{content}"); 47 | } 48 | 49 | public ArcaeaAffFormatException(EventType type, string content, int line) 50 | { 51 | Console.WriteLine($"在第 {line} 行处的 {type} 型事件中发生读取错误。\n{content}"); 52 | } 53 | 54 | public ArcaeaAffFormatException(EventType type, string content, int line, string? reason) 55 | { 56 | Console.WriteLine($"在第 {line} 行处的 {type} 型事件中发生读取错误。\n{content}\n{reason}"); 57 | } 58 | } 59 | 60 | public class ArcaeaAffEvent 61 | { 62 | public int Timing; 63 | 64 | public EventType Type; 65 | 66 | public int TimingGroup; 67 | } 68 | 69 | public class ArcaeaAffArc : ArcaeaAffEvent 70 | { 71 | public int EndTiming; 72 | 73 | public float XStart; 74 | 75 | public float XEnd; 76 | 77 | public string LineType = null!; 78 | 79 | public float YStart; 80 | 81 | public float YEnd; 82 | 83 | public int Color; 84 | 85 | public string Fx = null!; 86 | 87 | public bool IsVoid; 88 | 89 | public List? ArcTaps; 90 | 91 | public bool NoInput; 92 | 93 | public bool HasHead = true; 94 | 95 | public static ArcLineType ToArcLineType(string type) 96 | { 97 | return type switch 98 | { 99 | "b" => ArcLineType.B, 100 | "s" => ArcLineType.S, 101 | "si" => ArcLineType.Si, 102 | "so" => ArcLineType.So, 103 | "sisi" => ArcLineType.SiSi, 104 | "siso" => ArcLineType.SiSo, 105 | "sosi" => ArcLineType.SoSi, 106 | "soso" => ArcLineType.SoSo, 107 | _ => ArcLineType.S 108 | }; 109 | } 110 | } 111 | 112 | public class ArcaeaAffCamera : ArcaeaAffEvent 113 | { 114 | public Vector3 Move; 115 | 116 | public Vector3 Rotate; 117 | 118 | public string CameraType = null!; 119 | 120 | public int Duration; 121 | } 122 | 123 | public class ArcaeaAffHold : ArcaeaAffEvent 124 | { 125 | public int EndTiming; 126 | 127 | public int Track; 128 | 129 | public bool NoInput; 130 | } 131 | 132 | public class ArcaeaAffSceneControl : ArcaeaAffEvent 133 | { 134 | public string SceneControlTypeName = null!; 135 | 136 | public List Parameters = null!; 137 | } 138 | 139 | public class ArcaeaAffTap : ArcaeaAffEvent 140 | { 141 | public int Track; 142 | 143 | public bool NoInput; 144 | } 145 | 146 | public class ArcaeaAffTiming : ArcaeaAffEvent 147 | { 148 | public float Bpm; 149 | 150 | public float BeatsPerLine; 151 | } 152 | 153 | public enum EventType 154 | { 155 | Timing, 156 | Tap, 157 | Hold, 158 | Arc, 159 | Camera, 160 | Unknown, 161 | SceneControl, 162 | TimingGroup, 163 | TimingGroupEnd 164 | } 165 | 166 | public class TimingGroupProperties 167 | { 168 | public bool NoInput; 169 | } 170 | } -------------------------------------------------------------------------------- /Aff2Preview/MyGraphics/GdiPlusAdapter.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Drawing.Drawing2D; 3 | using System.Drawing.Imaging; 4 | 5 | namespace AffTools.MyGraphics; 6 | 7 | #pragma warning disable CA1416 8 | 9 | public class GdiImage : ImageDesc 10 | { 11 | public override void FromFile(string filePath) 12 | { 13 | if (filePath != "") 14 | InnerImage = Image.FromFile(filePath); 15 | } 16 | 17 | public override int GetHeight() => ((InnerImage as Image)?.Height) ?? 0; 18 | public override int GetWidth() => ((InnerImage as Image)?.Width) ?? 0; 19 | public override void SaveToPng(string filePath) 20 | { 21 | if (InnerImage is Image im) 22 | { 23 | im.Save(filePath); 24 | } 25 | } 26 | } 27 | 28 | public class GdiPlusAdapter : GraphicsAdapter 29 | { 30 | private Graphics g; 31 | private Image img; 32 | private Font font; 33 | private SolidBrush brush = new(Color.White); 34 | private Pen pen = new(Color.White); 35 | 36 | public static FontStyle ConvertStyle(FontDescStyle s) 37 | => (FontStyle)s; 38 | 39 | public static StringAlignment ConvertAlign(StringAdapterAlignment s) 40 | => (StringAlignment)s; 41 | 42 | public override void BeginContext(int width, int height) 43 | { 44 | img = new Bitmap(width, height); 45 | g = Graphics.FromImage(img); 46 | g.SmoothingMode = SmoothingMode.HighQuality; 47 | g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; 48 | } 49 | 50 | public override ImageDesc EndContext() 51 | { 52 | g.Dispose(); 53 | ImageDesc im = new GdiImage 54 | { 55 | InnerImage = img 56 | }; 57 | return im; 58 | } 59 | 60 | public override void DrawImage(ImageDesc image, float x, float y) 61 | { 62 | var im = (image.InnerImage as Image) ?? Image.FromFile(image.FilePath); 63 | g.DrawImage(im, x, y); 64 | } 65 | 66 | public override void DrawImageCliped(ImageDesc image, float x, float y, float clipx, float clipy, float clipw, float cliph) 67 | { 68 | var im = (image.InnerImage as Image) ?? Image.FromFile(image.FilePath); 69 | g.DrawImage(im, 70 | x, y, 71 | RectangleF.FromLTRB(clipx, clipy, clipx + clipw, clipy + cliph), GraphicsUnit.Pixel); 72 | } 73 | 74 | public override void DrawImageClipedAndScaled(ImageDesc image, float x, float y, float w, float h, float clipx, float clipy, float clipw, float cliph) 75 | { 76 | var im = (image.InnerImage as Image) ?? Image.FromFile(image.FilePath); 77 | g.DrawImage(im, 78 | RectangleF.FromLTRB(x, y, x + w, y + h), 79 | RectangleF.FromLTRB(clipx, clipy, clipx + clipw, clipy + cliph), GraphicsUnit.Pixel); 80 | } 81 | 82 | public override void DrawImageScaled(ImageDesc image, float x, float y, float w, float h, float transparency) 83 | { 84 | var im = (image.InnerImage as Image); 85 | if (im == null) 86 | { 87 | if (image.FilePath == "") 88 | return; 89 | im = Image.FromFile(image.FilePath); 90 | } 91 | if (transparency > 0) 92 | { 93 | float[][] nArray ={ new float[] {1, 0, 0, 0, 0}, 94 | new float[] {0, 1, 0, 0, 0}, 95 | new float[] {0, 0, 1, 0, 0}, 96 | new float[] {0, 0, 0, Math.Clamp(transparency, 0, 1), 0}, 97 | new float[] {0, 0, 0, 0, 1}}; 98 | ColorMatrix matrix = new(nArray); 99 | ImageAttributes attributes = new(); 100 | attributes.SetColorMatrix(matrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap); 101 | var rect = Rectangle.FromLTRB((int)x, (int)y, (int)(x + w), (int)(y + h)); 102 | g.DrawImage(im, rect, 0, 0, im.Width, im.Height, GraphicsUnit.Pixel, attributes); 103 | } 104 | else 105 | g.DrawImage(im, 106 | RectangleF.FromLTRB(x, y, x + w, y + h), 107 | RectangleF.FromLTRB(0, 0, im.Width, im.Height), GraphicsUnit.Pixel); 108 | } 109 | 110 | public override void DrawLine(float width, float startx, float starty, float endx, float endy) 111 | { 112 | pen.Width = width; 113 | g.DrawLine(pen, startx, starty, endx, endy); 114 | } 115 | 116 | public override void DrawLine(ColorDesc color, float width, float startx, float starty, float endx, float endy) 117 | { 118 | pen.Color = Color.FromArgb((int)color.ColorArgb); 119 | pen.Width = width; 120 | g.DrawLine(pen, startx, starty, endx, endy); 121 | } 122 | 123 | public override void DrawString(string str, ColorDesc color, FontDesc font, float x, float y) 124 | { 125 | brush.Color = Color.FromArgb((int)color.ColorArgb); 126 | var f = new Font(font.Name, font.EmSize, ConvertStyle(font.Style)); 127 | g.DrawString(str, f, brush, x, y); 128 | } 129 | 130 | public override void DrawString(string str, float x, float y) 131 | { 132 | g.DrawString(str, font, brush, x, y); 133 | } 134 | 135 | public override void Fill(ColorDesc color) 136 | { 137 | g.Clear(Color.FromArgb((int)color.ColorArgb)); 138 | } 139 | 140 | public override void FillRectangle(ColorDesc color, float x, float y, float w, float h) 141 | { 142 | brush.Color = Color.FromArgb((int)color.ColorArgb); 143 | g.FillRectangle(brush, x, y, w, h); 144 | } 145 | 146 | public override void SetColor(ColorDesc color) 147 | { 148 | brush.Color = Color.FromArgb((int)color.ColorArgb); 149 | pen.Color = Color.FromArgb((int)color.ColorArgb); 150 | } 151 | 152 | public override void SetFont(string name, float emSize) 153 | { 154 | font = new(name, emSize, FontStyle.Regular); 155 | } 156 | 157 | public override void SetFont(string name, float emSize, FontDescStyle style) 158 | { 159 | font = new(name, emSize, ConvertStyle(style)); 160 | } 161 | 162 | public override void DrawStringLayout(string str, float x, float y, float w, float h, StringAdapterAlignment align) 163 | { 164 | var a = ConvertAlign(align); 165 | StringFormat sf = new StringFormat() 166 | { 167 | Alignment = a, 168 | }; 169 | g.DrawString(str, font, brush, RectangleF.FromLTRB(x, y, x + w, y + h), sf); 170 | } 171 | 172 | public override void DrawStringLayoutLTRB(string str, float l, float t, float r, float b, StringAdapterAlignment align) 173 | { 174 | var a = ConvertAlign(align); 175 | StringFormat sf = new StringFormat() 176 | { 177 | Alignment = a, 178 | }; 179 | g.DrawString(str, font, brush, RectangleF.FromLTRB(l, t, r, b), sf); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /Aff2Preview/MyGraphics/GraphicsAdapter.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace AffTools.MyGraphics; 4 | 5 | [StructLayout(LayoutKind.Explicit, Size = 4)] 6 | public struct ColorRaw 7 | { 8 | [FieldOffset(0)] public uint argb; 9 | [FieldOffset(3)] public byte a; 10 | [FieldOffset(2)] public byte r; 11 | [FieldOffset(1)] public byte g; 12 | [FieldOffset(0)] public byte b; 13 | } 14 | 15 | public class ColorDesc 16 | { 17 | public ColorRaw InnerColor; 18 | 19 | public uint ColorArgb => InnerColor.argb; 20 | 21 | public void SetColor(uint argb) 22 | => InnerColor.argb = argb; 23 | 24 | public void SetColorA(byte a) 25 | => InnerColor.a = a; 26 | 27 | public void SetColor(byte a, byte r, byte g, byte b) 28 | { 29 | InnerColor.a = a; 30 | InnerColor.r = r; 31 | InnerColor.g = g; 32 | InnerColor.b = b; 33 | } 34 | 35 | public static ColorDesc FromArgb(uint argb) 36 | { 37 | ColorDesc color = new(); 38 | color.SetColor(argb); 39 | return color; 40 | } 41 | public static ColorDesc FromArgb(byte a, byte r, byte g, byte b) 42 | { 43 | ColorDesc color = new(); 44 | color.SetColor(a,r,g,b); 45 | return color; 46 | } 47 | } 48 | 49 | public enum FontDescStyle 50 | { 51 | Regular = 0x0, 52 | Bold = 0x1, 53 | Italic = 0x2, 54 | Underline = 0x4, 55 | Strikeout = 0x8 56 | } 57 | 58 | public enum StringAdapterAlignment 59 | { 60 | Near = 0, 61 | Center = 1, 62 | Far = 2, 63 | } 64 | 65 | public class FontDesc 66 | { 67 | public object? InnerFont { get; set; } = null; 68 | public string Name { get; set; } = ""; 69 | public int PixelHeight { get; set; } = 0; 70 | public float EmSize { get; set; } = 0; 71 | public FontDescStyle Style { get; set; } = FontDescStyle.Regular; 72 | 73 | public FontDesc() { } 74 | 75 | public FontDesc(string name, float emSize, FontDescStyle style) 76 | { 77 | Name = name; 78 | EmSize = emSize; 79 | Style = style; 80 | } 81 | } 82 | 83 | public abstract class ImageDesc 84 | { 85 | public object? InnerImage { get; set; } = null; 86 | public string FilePath { get; set; } = ""; 87 | public abstract void FromFile(string filePath); 88 | public abstract int GetWidth(); 89 | public abstract int GetHeight(); 90 | public abstract void SaveToPng(string filePath); 91 | } 92 | 93 | public enum Alignment 94 | { 95 | Near, 96 | Center, 97 | Far, 98 | } 99 | 100 | public abstract class GraphicsAdapter 101 | { 102 | public abstract void BeginContext(int width, int height); 103 | public abstract ImageDesc EndContext(); 104 | public abstract void SetColor(ColorDesc color); 105 | public abstract void SetFont(string name, float emSize); 106 | public abstract void SetFont(string name, float emSize, FontDescStyle style); 107 | public abstract void Fill(ColorDesc color); 108 | public abstract void DrawImage(ImageDesc image, float x, float y); 109 | public abstract void DrawImageScaled(ImageDesc image, float x, float y, float width, float height, float transparency = 0); 110 | public abstract void DrawImageCliped(ImageDesc image, float x, float y, float clipx, float clipy, float clipw, float cliph); 111 | public abstract void DrawImageClipedAndScaled(ImageDesc image, float x, float y, float width, float height, float clipx, float clipy, float clipw, float cliph); 112 | public abstract void FillRectangle(ColorDesc color, float x, float y, float w, float h); 113 | public abstract void DrawLine(ColorDesc color, float width, float startx, float starty, float endx, float endy); 114 | public abstract void DrawLine(float width, float startx, float starty, float endx, float endy); 115 | public abstract void DrawString(string str, ColorDesc color, FontDesc font, float x, float y); 116 | public abstract void DrawString(string str, float x, float y); 117 | public abstract void DrawStringLayout(string str, float x, float y, float w, float h, StringAdapterAlignment align); 118 | public abstract void DrawStringLayoutLTRB(string str, float l, float t, float r, float b, StringAdapterAlignment align); 119 | } 120 | -------------------------------------------------------------------------------- /Aff2Preview/MyGraphics/SkiaAdapter.cs: -------------------------------------------------------------------------------- 1 | namespace AffTools.MyGraphics; 2 | 3 | public class SkiaAdapter : GraphicsAdapter 4 | { 5 | public override void BeginContext(int width, int height) => throw new NotImplementedException(); 6 | public override void DrawImage(ImageDesc image, float x, float y) => throw new NotImplementedException(); 7 | public override void DrawImageCliped(ImageDesc image, float x, float y, float clipx, float clipy, float clipw, float cliph) => throw new NotImplementedException(); 8 | public override void DrawImageClipedAndScaled(ImageDesc image, float x, float y, float width, float height, float clipx, float clipy, float clipw, float cliph) => throw new NotImplementedException(); 9 | public override void DrawImageScaled(ImageDesc image, float x, float y, float width, float height, float transparency = 0) => throw new NotImplementedException(); 10 | public override void DrawLine(ColorDesc color, float width, float startx, float starty, float endx, float endy) => throw new NotImplementedException(); 11 | public override void DrawLine(float width, float startx, float starty, float endx, float endy) => throw new NotImplementedException(); 12 | public override void DrawString(string str, ColorDesc color, FontDesc font, float x, float y) => throw new NotImplementedException(); 13 | public override void DrawString(string str, float x, float y) => throw new NotImplementedException(); 14 | public override void DrawStringLayout(string str, float x, float y, float w, float h, StringAdapterAlignment align) => throw new NotImplementedException(); 15 | public override void DrawStringLayoutLTRB(string str, float l, float t, float r, float b, StringAdapterAlignment align) => throw new NotImplementedException(); 16 | public override ImageDesc EndContext() => throw new NotImplementedException(); 17 | public override void Fill(ColorDesc color) => throw new NotImplementedException(); 18 | public override void FillRectangle(ColorDesc color, float x, float y, float w, float h) => throw new NotImplementedException(); 19 | public override void SetColor(ColorDesc color) => throw new NotImplementedException(); 20 | public override void SetFont(string name, float emSize) => throw new NotImplementedException(); 21 | public override void SetFont(string name, float emSize, FontDescStyle style) => throw new NotImplementedException(); 22 | } 23 | 24 | -------------------------------------------------------------------------------- /Aff2Preview/Program.cs: -------------------------------------------------------------------------------- 1 | using AffTools.Aff2Preview; 2 | 3 | AffRenderer affRenderer = new("assets/2.aff") 4 | { 5 | Title = "Gift", 6 | Artist = "Notorious(Nota & TRIAL)", 7 | Charter = "Misaka12456", 8 | Side = 0, 9 | Difficulty = 2, 10 | Rating = 10.3f, 11 | Notes = 0, 12 | ChartBpm = 200, 13 | IsMirror = false 14 | }; 15 | 16 | affRenderer.LoadResource( 17 | "assets/note.png", 18 | "assets/note_hold.png", 19 | "assets/arc_body.png", 20 | "assets/base.jpg", 21 | "assets/base.jpg"); 22 | 23 | var image = affRenderer.Draw(); 24 | 25 | image?.SaveToPng("output.png"); 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 616 SB License 2 | 3 | Copyright (c) 2022 InariAimu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The person should speak loudly with "616 SB!", before using this Software. 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AffTools 2 | 3 | [![Version](https://img.shields.io/badge/AffTools-2.1-coral)](#) 4 | [![C#](https://img.shields.io/badge/.NET-6.0-blue)](#) 5 | [![License](https://img.shields.io/static/v1?label=LICENSE&message=616%20SB&color=1f1e33)](/LICENSE) 6 | 7 | A toolset for Arcaea aff files. 8 | 9 | - Aff2Preview: 2D chart preview generator 10 | - AffAnalyzer: Analyze aff files 11 | 12 | ## Aff2Preview 13 | 14 | Generate 2D chart preview image by aff file. 15 | 16 | Use `System.Drawing.Common` to generate image. However, you can override `AffTools.MyGraphics.GraphicsAdapter` to use your own image library. 17 | 18 | Usage: 19 | 20 | ```csharp 21 | using AffTools.Aff2Preview; 22 | 23 | AffRenderer affRenderer = new("assets/2.aff") 24 | { 25 | Title = "Gift", 26 | Artist = "Notorious(Nota & TRIAL)", 27 | Charter = "Misaka12456", 28 | Side = 0, // 0:hikari 1:tairitsu 2:dynamix or tempestissimo 29 | Difficulty = 2, 30 | Rating = 10.3f, 31 | Notes = 0, // notes are counted by internal note counter now 32 | ChartBpm = 200, 33 | IsMirror = false, // controls whether the chart is mirrored or not 34 | }; 35 | 36 | // image resource to generate chart preview. 37 | // for each side, please specify the image manually. 38 | affRenderer.LoadResource( 39 | "assets/note.png", 40 | "assets/note_hold.png", 41 | "assets/arc_body.png", 42 | "assets/base.jpg", // empty string if unwanted 43 | "assets/base.jpg"); // empty string if unwanted 44 | 45 | var image = affRenderer.Draw(); 46 | 47 | image?.SaveToPng("output.png"); 48 | 49 | ``` 50 | 51 | Example output: `assets/2.aff` 52 | 53 | ![example](output.jpg) 54 | 55 | ## AffAnalyzer 56 | 57 | Can analyze aff file and generate chart data. 58 | 59 | - Note timing 60 | - Segment timing, bpm and beat per segment 61 | - Aff flaw detection 62 | - Note counting 63 | - Note count at specific timing 64 | - Note density 65 | 66 | ## LICENSE 67 | 68 | Assets: `CC-BY-NC` [Arcaea-Infinity/OpenArcaeaArts](https://github.com/Arcaea-Infinity/OpenArcaeaArts) 69 | 70 | `Gift`: Source: BOFXVI 71 | 72 | Aff & cover `616 SB License` Misaka12456 from [Arcaea-Infinity/FanmadeCharts](https://github.com/Arcaea-Infinity/FanmadeCharts) 73 | 74 | Part of note counting code modified from [Lolitania/ArcaeaChartNoteCounterLibrary](https://github.com/Lolitania/ArcaeaChartNoteCounterLibrary) under `BSD-3-Clause license`. 75 | 76 | Licensed under `616 SB License`. 77 | -------------------------------------------------------------------------------- /assets/2.aff: -------------------------------------------------------------------------------- 1 | AudioOffset:1226 2 | - 3 | timing(0,200.00,4.00); 4 | timing(69600,100.00,4.00); 5 | timing(73200,200.00,4.00); 6 | timing(79200,0.00,0.00); 7 | timing(79499,20000.00,4.00); 8 | timing(79500,0.00,0.00); 9 | timing(79799,20000.00,4.00); 10 | timing(79800,0.00,0.00); 11 | timing(80099,20000.00,4.00); 12 | timing(80100,0.00,0.00); 13 | timing(80399,20000.00,4.00); 14 | timing(80400,0.00,0.00); 15 | timing(80699,20000.00,4.00); 16 | timing(80700,0.00,0.00); 17 | timing(80999,20000.00,4.00); 18 | timing(81000,0.00,0.00); 19 | timing(81299,20000.00,4.00); 20 | timing(81300,0.00,0.00); 21 | timing(81599,20000.00,4.00); 22 | timing(81600,0.00,0.00); 23 | timing(81899,20000.00,4.00); 24 | timing(81900,0.00,0.00); 25 | timing(82199,20000.00,4.00); 26 | timing(82200,0.00,0.00); 27 | timing(82499,20000.00,4.00); 28 | timing(82500,0.00,0.00); 29 | timing(82799,20000.00,4.00); 30 | timing(82800,0.00,0.00); 31 | timing(83099,20000.00,4.00); 32 | timing(83100,0.00,0.00); 33 | timing(83399,20000.00,4.00); 34 | timing(83400,0.00,0.00); 35 | timing(83699,20000.00,4.00); 36 | timing(83700,0.00,0.00); 37 | timing(83999,20000.00,4.00); 38 | timing(84000,0.00,0.00); 39 | timing(84299,20000.00,4.00); 40 | timing(84300,0.00,0.00); 41 | timing(84599,20000.00,4.00); 42 | timing(84600,0.00,0.00); 43 | timing(84899,20000.00,4.00); 44 | timing(84900,0.00,0.00); 45 | timing(85199,20000.00,4.00); 46 | timing(85200,0.00,0.00); 47 | timing(85499,20000.00,4.00); 48 | timing(85500,0.00,0.00); 49 | timing(85799,20000.00,4.00); 50 | timing(85800,0.00,0.00); 51 | timing(86099,20000.00,4.00); 52 | timing(86100,0.00,0.00); 53 | timing(86399,20000.00,4.00); 54 | timing(86400,0.00,0.00); 55 | timing(86699,20000.00,4.00); 56 | timing(86700,0.00,0.00); 57 | timing(86999,20000.00,4.00); 58 | timing(87000,0.00,0.00); 59 | timing(87299,20000.00,4.00); 60 | timing(87300,0.00,0.00); 61 | timing(87599,20000.00,4.00); 62 | timing(87600,0.00,0.00); 63 | timing(87899,20000.00,4.00); 64 | timing(87900,0.00,0.00); 65 | timing(88199,20000.00,4.00); 66 | timing(88200,0.00,0.00); 67 | timing(88499,20000.00,4.00); 68 | timing(88500,200.00,4.00); 69 | timing(98400,100.00,4.00); 70 | timing(103200,200.00,0.25); 71 | timing(107100,1200.00,4.00); 72 | timing(107200,600.00,4.00); 73 | timing(107300,300.00,4.00); 74 | timing(107400,200.00,4.00); 75 | timing(108000,200.00,4.00); 76 | (450,1); 77 | (600,3); 78 | (750,2); 79 | (1200,2); 80 | (1200,4); 81 | (1350,3); 82 | (1650,4); 83 | (1950,1); 84 | (2250,4); 85 | (2400,1); 86 | (2550,3); 87 | (2700,1); 88 | (2775,3); 89 | (2850,1); 90 | (3150,3); 91 | (3450,3); 92 | (3600,4); 93 | (3750,2); 94 | (3900,4); 95 | (3975,2); 96 | (4050,4); 97 | (4350,1); 98 | (4425,3); 99 | (4500,2); 100 | (4650,2); 101 | (4800,2); 102 | (4950,3); 103 | (5100,2); 104 | (5175,3); 105 | (5250,2); 106 | (5550,3); 107 | (5850,3); 108 | (6000,3); 109 | (6150,2); 110 | (6300,3); 111 | (6375,2); 112 | (6450,3); 113 | (6600,2); 114 | (7200,1); 115 | (7350,3); 116 | (7500,1); 117 | (7575,3); 118 | (7650,1); 119 | (8025,3); 120 | (8250,3); 121 | (8400,4); 122 | (8550,2); 123 | (8700,4); 124 | (8775,2); 125 | (8850,4); 126 | (9225,2); 127 | (9450,2); 128 | (9600,4); 129 | (9750,3); 130 | (9900,2); 131 | (9975,3); 132 | (10050,2); 133 | (10200,1); 134 | (10425,3); 135 | (10500,2); 136 | (10650,3); 137 | (10800,3); 138 | (10950,3); 139 | (11100,2); 140 | (11250,2); 141 | (11400,4); 142 | (11550,3); 143 | (11850,1); 144 | (19200,2); 145 | (19275,4); 146 | (19350,2); 147 | (19425,4); 148 | (19800,1); 149 | (19875,3); 150 | (19950,1); 151 | (20025,3); 152 | (21300,2); 153 | (21375,3); 154 | (21450,2); 155 | (21525,3); 156 | (21750,2); 157 | (22050,3); 158 | (22350,1); 159 | (22500,2); 160 | (22650,3); 161 | (22800,3); 162 | (23100,2); 163 | (24150,3); 164 | (24600,1); 165 | (24900,1); 166 | (25200,4); 167 | (25500,1); 168 | (25800,4); 169 | (26400,3); 170 | (27000,3); 171 | (27150,2); 172 | (27300,1); 173 | (27450,2); 174 | (27750,2); 175 | (28050,3); 176 | (28200,2); 177 | (28275,2); 178 | (28800,1); 179 | (28875,4); 180 | (28950,1); 181 | (29025,4); 182 | (29100,2); 183 | (29175,3); 184 | (29250,2); 185 | (29325,3); 186 | (29400,1); 187 | (29475,4); 188 | (29550,1); 189 | (29625,4); 190 | (29700,2); 191 | (29775,3); 192 | (29850,2); 193 | (29925,3); 194 | (30000,4); 195 | (30300,4); 196 | (30600,4); 197 | (31350,3); 198 | (31500,1); 199 | (31575,3); 200 | (31650,1); 201 | (31800,1); 202 | (32025,3); 203 | (32100,1); 204 | (32250,3); 205 | (32400,4); 206 | (32550,2); 207 | (32700,4); 208 | (32775,2); 209 | (32850,4); 210 | (33000,4); 211 | (33150,1); 212 | (33225,3); 213 | (33300,2); 214 | (33450,3); 215 | (33450,4); 216 | (33600,2); 217 | (33750,3); 218 | (33900,2); 219 | (33975,3); 220 | (34050,2); 221 | (34200,2); 222 | (34425,3); 223 | (34500,2); 224 | (34650,3); 225 | (34800,3); 226 | (34950,2); 227 | (35100,3); 228 | (35175,2); 229 | (35250,3); 230 | (35400,1); 231 | (35400,3); 232 | (35550,1); 233 | (35700,2); 234 | (35700,4); 235 | (35850,4); 236 | (36000,1); 237 | (36150,1); 238 | (36300,2); 239 | (36450,2); 240 | (36600,3); 241 | (36750,3); 242 | (36900,4); 243 | (37050,4); 244 | (37200,4); 245 | (37350,4); 246 | (37500,3); 247 | (37650,3); 248 | (37800,2); 249 | (37950,2); 250 | (38100,1); 251 | (38250,1); 252 | (38400,1); 253 | (38550,2); 254 | (38700,3); 255 | (38850,4); 256 | (39000,1); 257 | (39075,3); 258 | (39150,1); 259 | (39225,3); 260 | (39300,2); 261 | (39375,4); 262 | (39450,2); 263 | (39525,4); 264 | (42300,4); 265 | (42450,3); 266 | (42600,4); 267 | (42750,3); 268 | (42900,4); 269 | (43200,1); 270 | (43500,1); 271 | (43575,2); 272 | (43650,1); 273 | (43800,2); 274 | (43875,3); 275 | (43950,2); 276 | (44400,1); 277 | (44400,3); 278 | (44550,2); 279 | (44700,1); 280 | (44700,3); 281 | (44850,3); 282 | (45000,2); 283 | (45000,4); 284 | (45150,3); 285 | (45300,1); 286 | (45300,4); 287 | (47100,1); 288 | (47250,2); 289 | (47400,1); 290 | (47550,2); 291 | (47700,1); 292 | (48000,4); 293 | (48300,4); 294 | (48375,3); 295 | (48450,4); 296 | (48600,3); 297 | (48675,2); 298 | (48750,3); 299 | (49200,4); 300 | (49200,2); 301 | (49350,3); 302 | (49500,4); 303 | (49500,2); 304 | (49650,2); 305 | (50100,4); 306 | (50100,1); 307 | (50400,3); 308 | (50400,4); 309 | (50550,1); 310 | (50625,3); 311 | (50700,2); 312 | (50775,3); 313 | (50850,2); 314 | (50925,3); 315 | (51300,2); 316 | (51375,3); 317 | (51450,2); 318 | (51525,3); 319 | (51900,2); 320 | (51975,3); 321 | (52050,2); 322 | (52125,3); 323 | (52800,1); 324 | (52875,3); 325 | (52950,1); 326 | (53025,3); 327 | (53400,2); 328 | (53475,4); 329 | (53550,2); 330 | (53625,4); 331 | (54000,1); 332 | (54075,3); 333 | (54150,1); 334 | (54225,3); 335 | (54600,2); 336 | (54675,4); 337 | (54750,2); 338 | (54825,4); 339 | (55500,1); 340 | (55500,3); 341 | (55800,1); 342 | (55950,3); 343 | (56100,2); 344 | (56400,3); 345 | (56550,2); 346 | (56625,3); 347 | (56700,2); 348 | (56775,3); 349 | (56850,1); 350 | (56925,4); 351 | (57000,1); 352 | (57075,4); 353 | (57150,2); 354 | (57300,3); 355 | (57450,2); 356 | (57900,3); 357 | (57975,4); 358 | (58050,3); 359 | (58125,4); 360 | (58350,2); 361 | (58500,1); 362 | (58575,2); 363 | (58650,1); 364 | (58725,2); 365 | (58875,3); 366 | (58950,2); 367 | (59025,3); 368 | (59175,3); 369 | (59250,2); 370 | (59325,3); 371 | (59700,1); 372 | (59775,3); 373 | (59850,2); 374 | (59925,4); 375 | (60600,2); 376 | (60750,4); 377 | (61050,1); 378 | (61350,1); 379 | (61650,4); 380 | (61800,1); 381 | (62100,4); 382 | (62700,1); 383 | (63000,4); 384 | (63300,1); 385 | (63825,4); 386 | (64425,1); 387 | (65100,1); 388 | (65400,1); 389 | (65400,2); 390 | (65550,2); 391 | (65850,1); 392 | (65850,3); 393 | (66450,4); 394 | (67050,1); 395 | (67350,4); 396 | (67650,1); 397 | (67800,1); 398 | (67875,2); 399 | (67950,1); 400 | (68025,2); 401 | (68100,1); 402 | (68175,3); 403 | (68250,1); 404 | (68325,3); 405 | (69000,3); 406 | (69000,4); 407 | (69225,3); 408 | (69450,2); 409 | (72600,1); 410 | (72675,3); 411 | (72750,2); 412 | (72900,2); 413 | (73050,4); 414 | (73425,3); 415 | (73650,4); 416 | (74400,1); 417 | (74625,1); 418 | (74625,3); 419 | (74850,1); 420 | (74850,4); 421 | (75000,4); 422 | (75225,2); 423 | (75225,4); 424 | (75450,1); 425 | (75450,4); 426 | (75900,1); 427 | (76050,2); 428 | (76500,3); 429 | (76650,4); 430 | (76800,3); 431 | (76950,3); 432 | (77100,2); 433 | (77250,2); 434 | (77400,4); 435 | (77550,4); 436 | (77700,1); 437 | (77850,1); 438 | (78000,2); 439 | (78300,3); 440 | (78900,2); 441 | (78900,3); 442 | (83700,4); 443 | (88500,4); 444 | (88950,3); 445 | (89100,2); 446 | (89625,2); 447 | (89850,3); 448 | (90150,3); 449 | (90300,2); 450 | (90750,2); 451 | (90900,3); 452 | (91050,2); 453 | (91500,2); 454 | (91950,2); 455 | (92100,3); 456 | (92250,2); 457 | (92550,3); 458 | (92850,2); 459 | (93000,3); 460 | (93150,1); 461 | (93300,2); 462 | (93450,3); 463 | (95400,1); 464 | (95400,2); 465 | (95700,3); 466 | (95700,4); 467 | (96000,2); 468 | (96075,3); 469 | (96150,2); 470 | (96225,3); 471 | (96300,1); 472 | (96375,4); 473 | (96450,1); 474 | (96525,4); 475 | (96600,2); 476 | (96675,3); 477 | (96750,2); 478 | (96825,3); 479 | (96900,1); 480 | (96975,4); 481 | (97050,1); 482 | (97125,4); 483 | (97350,3); 484 | (97500,2); 485 | (97650,1); 486 | (98100,1); 487 | (98175,3); 488 | (98250,2); 489 | (98325,4); 490 | (98400,3); 491 | (98700,2); 492 | (99000,4); 493 | (99150,3); 494 | (99450,2); 495 | (99750,1); 496 | (100050,2); 497 | (100200,3); 498 | (100350,2); 499 | (100650,3); 500 | (100800,4); 501 | (101100,3); 502 | (101250,4); 503 | (101550,4); 504 | (101700,3); 505 | (101850,4); 506 | (102000,1); 507 | (102225,2); 508 | (102450,1); 509 | (102600,2); 510 | (102825,1); 511 | (103050,2); 512 | (108300,3); 513 | (108750,3); 514 | (109050,3); 515 | (109200,3); 516 | (109500,3); 517 | (109800,2); 518 | (110100,2); 519 | (110400,2); 520 | (110700,1); 521 | (111000,2); 522 | (111300,2); 523 | (111600,2); 524 | (111900,2); 525 | (112200,3); 526 | (112500,3); 527 | (112950,2); 528 | (113100,1); 529 | (113100,3); 530 | (113250,2); 531 | (113400,3); 532 | (113550,2); 533 | (113550,4); 534 | (113700,3); 535 | (113850,2); 536 | (113850,4); 537 | (114150,1); 538 | (114150,4); 539 | (114300,2); 540 | (114300,3); 541 | (114450,1); 542 | (114450,4); 543 | (114600,1); 544 | (114600,2); 545 | (114750,2); 546 | (114750,3); 547 | (114900,3); 548 | (114900,4); 549 | (115050,2); 550 | (115050,3); 551 | (115200,2); 552 | (115275,3); 553 | (115350,2); 554 | (115425,3); 555 | (115500,2); 556 | (115575,3); 557 | (115650,2); 558 | (115725,3); 559 | (116400,3); 560 | (116700,2); 561 | (117000,3); 562 | (117600,3); 563 | (117750,2); 564 | (118050,2); 565 | (118200,3); 566 | (118275,4); 567 | (118500,3); 568 | (118650,4); 569 | (118800,1); 570 | (118950,2); 571 | (119100,3); 572 | (119550,4); 573 | (119700,1); 574 | (119700,3); 575 | (119850,2); 576 | (120150,3); 577 | (120600,2); 578 | (120750,4); 579 | (120825,1); 580 | (121050,2); 581 | (121050,3); 582 | (121200,1); 583 | (121350,3); 584 | (121425,2); 585 | (121650,4); 586 | (121800,2); 587 | (121950,3); 588 | (122100,1); 589 | (122100,3); 590 | (122250,2); 591 | (122250,4); 592 | (123000,3); 593 | (123225,2); 594 | (123600,2); 595 | (123825,3); 596 | (124200,1); 597 | (124500,4); 598 | (124800,1); 599 | (124875,3); 600 | (124950,1); 601 | (125025,3); 602 | (125100,2); 603 | (125175,4); 604 | (125250,2); 605 | (125325,4); 606 | (126600,3); 607 | (126825,2); 608 | (127200,2); 609 | (127650,2); 610 | (128250,2); 611 | (128250,3); 612 | (129150,1); 613 | (129150,3); 614 | (129300,2); 615 | (129450,2); 616 | (129450,4); 617 | (131400,1); 618 | (131400,3); 619 | (131550,2); 620 | (131700,1); 621 | (131775,4); 622 | (131850,1); 623 | (131925,4); 624 | (132000,3); 625 | (132225,2); 626 | (132450,1); 627 | (132450,3); 628 | (132600,2); 629 | (132825,3); 630 | (133050,2); 631 | (133050,4); 632 | (134700,2); 633 | (134775,3); 634 | (134850,2); 635 | (134925,3); 636 | (135300,2); 637 | (135375,3); 638 | (135450,2); 639 | (135525,3); 640 | (135600,1); 641 | (135900,4); 642 | (136650,3); 643 | (136800,4); 644 | (137025,3); 645 | (137250,2); 646 | (137250,4); 647 | (137400,1); 648 | (137625,2); 649 | (137850,1); 650 | (137850,3); 651 | (139050,1); 652 | (139425,3); 653 | (140025,2); 654 | (140400,3); 655 | (140550,3); 656 | (140700,2); 657 | (140850,2); 658 | (141000,4); 659 | (141150,4); 660 | (141300,1); 661 | (141450,1); 662 | (141600,1); 663 | (141675,4); 664 | (141750,1); 665 | (141825,4); 666 | (141900,2); 667 | (141975,3); 668 | (142050,2); 669 | (142125,3); 670 | (142200,1); 671 | (142275,4); 672 | (142350,1); 673 | (142425,4); 674 | (142500,2); 675 | (142575,3); 676 | (142650,2); 677 | (142725,3); 678 | (143700,2); 679 | (143700,3); 680 | hold(0,150,2); 681 | hold(0,150,3); 682 | hold(900,1050,3); 683 | hold(900,1050,4); 684 | hold(6825,7050,2); 685 | hold(11550,11700,2); 686 | hold(23400,23625,3); 687 | hold(23700,23925,2); 688 | hold(24000,24150,1); 689 | hold(26100,26250,1); 690 | hold(28500,28650,3); 691 | hold(40800,40950,1); 692 | hold(40800,40950,2); 693 | hold(41100,41250,3); 694 | hold(41400,41550,4); 695 | hold(41700,41850,3); 696 | hold(42000,42150,4); 697 | hold(44100,44250,4); 698 | hold(45600,45750,4); 699 | hold(45600,45750,3); 700 | hold(45900,46050,2); 701 | hold(46200,46350,1); 702 | hold(46500,46650,2); 703 | hold(46800,46950,1); 704 | hold(48900,49050,1); 705 | hold(49800,49950,1); 706 | hold(49800,49950,3); 707 | hold(52500,52650,2); 708 | hold(52500,52650,3); 709 | hold(54900,55050,1); 710 | hold(54900,55050,3); 711 | hold(68400,68700,4); 712 | hold(69600,69750,1); 713 | hold(69900,70050,3); 714 | hold(70200,70350,2); 715 | hold(70350,70500,3); 716 | hold(70500,70650,1); 717 | hold(70650,70800,4); 718 | hold(70800,70950,1); 719 | hold(70950,71100,3); 720 | hold(71250,71400,3); 721 | hold(71400,71550,2); 722 | hold(71550,71700,4); 723 | hold(71850,72000,4); 724 | hold(72000,72150,1); 725 | hold(72300,72450,2); 726 | hold(72450,72600,3); 727 | hold(73800,74250,1); 728 | hold(75600,75750,1); 729 | hold(75750,75900,2); 730 | hold(76200,76350,3); 731 | hold(76350,76500,4); 732 | hold(88800,89100,1); 733 | hold(89400,89700,4); 734 | hold(90000,90300,1); 735 | hold(90600,90900,4); 736 | hold(93600,93750,1); 737 | hold(93600,93750,2); 738 | hold(94050,94200,2); 739 | hold(94050,94200,3); 740 | hold(94500,94650,3); 741 | hold(94500,94650,4); 742 | hold(94950,95100,2); 743 | hold(94950,95100,3); 744 | hold(98400,99300,1); 745 | hold(99600,100500,4); 746 | hold(100800,101700,2); 747 | hold(102000,102900,3); 748 | hold(117825,118050,3); 749 | hold(118200,118500,2); 750 | hold(119250,119400,4); 751 | hold(129600,129900,1); 752 | hold(130050,130200,4); 753 | hold(136500,136650,2); 754 | hold(138000,138450,4); 755 | hold(138600,138900,1); 756 | hold(144000,144300,1); 757 | hold(144000,144300,4); 758 | arc(1500,1650,0.00,0.50,s,1.00,0.00,0,none,false); 759 | arc(1650,1725,0.50,0.50,s,0.00,0.00,0,none,false); 760 | arc(1800,1950,1.00,0.50,s,1.00,0.00,1,none,false); 761 | arc(1950,2025,0.50,0.50,s,0.00,0.00,1,none,false); 762 | arc(2025,2400,0.50,1.00,sisi,0.00,1.00,1,none,true); 763 | arc(2100,2250,0.00,0.50,s,1.00,0.00,0,none,false); 764 | arc(2250,2325,0.50,0.50,s,0.00,0.00,0,none,false); 765 | arc(2325,2400,0.50,0.00,sisi,0.00,1.00,0,none,true); 766 | arc(2400,12000,0.00,0.00,s,1.00,1.00,0,none,true)[arctap(3000),arctap(3225),arctap(3450)]; 767 | arc(2400,12000,1.00,1.00,s,1.00,1.00,1,none,true)[arctap(4200),arctap(4650)]; 768 | arc(3450,3600,0.00,0.25,si,1.00,1.00,0,none,true); 769 | arc(3600,12000,0.25,0.25,s,1.00,1.00,0,none,true)[arctap(5400),arctap(5625),arctap(5850),arctap(9600),arctap(10800)]; 770 | arc(4650,4800,1.00,0.75,si,1.00,1.00,1,none,true); 771 | arc(4800,12000,0.75,0.75,s,1.00,1.00,1,none,true)[arctap(6600),arctap(7050),arctap(10200),arctap(11100)]; 772 | arc(5850,6000,0.25,0.00,sisi,1.00,0.00,0,none,true); 773 | arc(6000,12000,0.00,0.00,s,0.00,0.00,0,none,true)[arctap(7800),arctap(8250),arctap(8100),arctap(11400)]; 774 | arc(7050,7200,0.75,1.00,sisi,1.00,0.00,1,none,true); 775 | arc(7200,12000,1.00,1.00,s,0.00,0.00,1,none,true)[arctap(9000),arctap(9150),arctap(9300),arctap(9450),arctap(11850)]; 776 | arc(12000,14250,1.00,0.75,sisi,1.00,0.00,1,none,false); 777 | arc(12000,14250,0.00,0.25,sisi,1.00,0.00,0,none,false); 778 | arc(12000,14400,0.75,0.50,sisi,1.00,0.00,1,none,true); 779 | arc(12000,14400,0.25,0.50,sisi,1.00,0.00,0,none,true)[arctap(14400)]; 780 | arc(12000,14400,1.00,1.25,sisi,1.00,0.00,1,none,true); 781 | arc(12000,14400,0.00,-0.25,sisi,1.00,0.00,0,none,true); 782 | arc(12000,14400,1.00,0.85,s,0.00,0.00,1,none,true); 783 | arc(12000,14400,0.00,0.15,s,0.00,0.00,0,none,true); 784 | arc(14400,14700,-0.25,0.00,so,0.00,0.00,0,none,true); 785 | arc(14400,14700,0.15,0.25,so,0.00,0.00,0,none,true)[arctap(14700)]; 786 | arc(14400,14700,0.85,0.50,si,0.00,0.00,1,none,true)[arctap(14550)]; 787 | arc(14400,14700,1.25,0.75,si,0.00,0.00,1,none,true); 788 | arc(14700,15300,0.00,-0.25,si,0.00,0.00,0,none,true)[arctap(15000)]; 789 | arc(14700,15300,0.25,0.00,si,0.00,0.00,0,none,true); 790 | arc(14700,15300,0.50,0.25,si,0.00,0.00,1,none,true); 791 | arc(14700,15300,0.75,0.50,si,0.00,0.00,1,none,true)[arctap(15225)]; 792 | arc(15300,15900,-0.25,0.25,si,0.00,0.00,0,none,true)[arctap(15600)]; 793 | arc(15300,15900,0.00,0.50,si,0.00,0.00,0,none,true)[arctap(15750)]; 794 | arc(15300,15900,0.25,0.75,si,0.00,0.00,0,none,true)[arctap(15450),arctap(15900)]; 795 | arc(15300,15900,0.50,1.00,si,0.00,0.00,0,none,true); 796 | arc(15900,16500,0.25,-0.25,si,0.00,0.00,0,none,true)[arctap(16425)]; 797 | arc(15900,16500,0.50,0.00,si,0.00,0.00,0,none,true); 798 | arc(15900,16500,0.75,0.25,si,0.00,0.00,1,none,true); 799 | arc(15900,16500,1.00,0.50,si,0.00,0.00,1,none,true)[arctap(16200)]; 800 | arc(16500,17100,-0.25,0.25,si,0.00,0.00,0,none,true)[arctap(16800),arctap(16950)]; 801 | arc(16500,17100,0.00,0.50,si,0.00,0.00,0,none,true)[arctap(16650)]; 802 | arc(16500,17100,0.25,0.75,si,0.00,0.00,0,none,true); 803 | arc(16500,17100,0.50,1.00,si,0.00,0.00,0,none,true)[arctap(16875),arctap(17025)]; 804 | arc(17100,17700,0.25,-0.15,si,0.00,0.15,0,none,true)[arctap(17400),arctap(17550)]; 805 | arc(17100,17700,0.50,0.00,si,0.00,0.00,0,none,true)[arctap(17100),arctap(17250)]; 806 | arc(17100,17700,0.75,0.25,si,0.00,0.00,1,none,true)[arctap(17175),arctap(17325)]; 807 | arc(17100,17700,1.00,0.50,si,0.00,0.15,1,none,true)[arctap(17475),arctap(17625)]; 808 | arc(17700,18300,-0.15,0.25,si,0.15,0.30,0,none,true)[arctap(18000),arctap(18150)]; 809 | arc(17700,18300,0.00,0.50,si,0.00,0.00,0,none,true)[arctap(17700),arctap(17850)]; 810 | arc(17700,18300,0.25,0.75,si,0.00,0.00,0,none,true)[arctap(17775),arctap(17925)]; 811 | arc(17700,18300,0.50,1.00,si,0.15,0.30,0,none,true)[arctap(18075),arctap(18225)]; 812 | arc(18300,18900,0.25,-0.05,si,0.30,0.45,0,none,true)[arctap(18600),arctap(18750)]; 813 | arc(18300,18900,0.50,0.00,si,0.00,0.00,0,none,true)[arctap(18300),arctap(18450)]; 814 | arc(18300,18900,0.75,0.25,si,0.00,0.00,1,none,true)[arctap(18375),arctap(18525)]; 815 | arc(18300,18900,1.00,0.50,si,0.30,0.45,1,none,true)[arctap(18825),arctap(18675)]; 816 | arc(18900,19500,-0.05,0.25,si,0.45,0.60,0,none,true)[arctap(19500)]; 817 | arc(18900,19500,0.00,0.50,si,0.00,0.00,0,none,true)[arctap(18900),arctap(19050)]; 818 | arc(18900,19500,0.25,0.75,si,0.00,0.00,0,none,true)[arctap(18975),arctap(19125)]; 819 | arc(18900,19500,0.50,1.00,si,0.45,0.60,0,none,true); 820 | arc(19500,20100,0.25,0.00,si,0.60,0.80,0,none,true)[arctap(19650)]; 821 | arc(19500,20100,0.50,0.00,si,0.00,0.00,0,none,true); 822 | arc(19500,20100,0.75,0.25,si,0.00,0.00,1,none,true); 823 | arc(19500,20100,1.00,0.50,si,0.60,0.80,1,none,true)[arctap(19575),arctap(19725)]; 824 | arc(20100,20700,0.00,0.00,sisi,0.80,1.00,0,none,true)[arctap(20100),arctap(20250)]; 825 | arc(20100,20700,0.50,1.00,sisi,0.80,1.00,1,none,true)[arctap(20175),arctap(20325)]; 826 | arc(20100,20700,0.00,0.00,s,0.00,0.00,0,none,true)[arctap(20400),arctap(20550)]; 827 | arc(20100,20700,0.25,1.00,si,0.00,0.00,1,none,true)[arctap(20475),arctap(20625)]; 828 | arc(20700,21600,1.00,1.00,s,1.00,1.00,1,none,true)[arctap(20700)]; 829 | arc(20700,21600,0.00,0.00,s,1.00,1.00,0,none,true)[arctap(20700)]; 830 | arc(20700,21000,1.00,1.00,s,0.00,0.00,1,none,true)[arctap(21000)]; 831 | arc(20700,21000,0.00,0.00,s,0.00,0.00,0,none,true)[arctap(21000)]; 832 | arc(21600,22200,1.00,1.00,soso,1.00,0.00,1,none,true)[arctap(21900),arctap(22200)]; 833 | arc(21600,22200,0.00,0.00,soso,1.00,0.00,0,none,false); 834 | arc(21600,22800,1.00,1.00,s,1.00,1.00,1,none,true)[arctap(22575)]; 835 | arc(21600,22800,0.00,0.00,s,1.00,1.00,0,none,true); 836 | arc(22800,23325,1.00,1.00,soso,1.00,0.00,1,none,false); 837 | arc(22800,23400,0.00,0.00,soso,1.00,0.00,0,none,true)[arctap(22950),arctap(23250)]; 838 | arc(23325,23700,1.00,1.00,s,0.00,0.00,1,none,true); 839 | arc(23400,23625,0.00,0.00,s,0.00,0.00,0,none,false); 840 | arc(23625,24000,0.00,0.00,sisi,0.00,1.00,0,none,true); 841 | arc(23700,23925,1.00,1.00,s,0.00,0.00,1,none,false); 842 | arc(23925,24300,1.00,1.00,sisi,0.00,1.00,1,none,true); 843 | arc(24000,24900,0.00,0.00,s,1.00,1.00,0,none,true)[arctap(24225)]; 844 | arc(24300,24600,1.00,1.00,soso,1.00,0.00,1,none,false); 845 | arc(24600,25200,1.00,0.50,si,0.00,0.00,1,none,false); 846 | arc(24900,25200,0.00,0.50,si,1.00,1.00,0,none,true)[arctap(25050)]; 847 | arc(25200,26250,0.50,0.50,s,0.00,0.00,1,none,false); 848 | arc(25200,25950,0.50,0.50,s,1.00,1.00,0,none,true)[arctap(25350),arctap(25650),arctap(25950)]; 849 | arc(25950,26100,0.50,1.00,si,1.00,1.00,1,none,true); 850 | arc(25950,26400,0.50,0.25,si,1.00,1.00,0,none,true); 851 | arc(26100,31200,1.00,1.00,s,1.00,1.00,1,none,true)[arctap(26700),arctap(27000),arctap(28350),arctap(30150),arctap(30450),arctap(30900),arctap(31200)]; 852 | arc(26400,26850,0.25,1.00,sisi,1.00,0.00,0,none,false); 853 | arc(26850,27600,1.00,0.00,sisi,0.00,1.00,0,none,true); 854 | arc(27600,27750,1.00,1.00,s,1.00,1.00,1,none,false); 855 | arc(27600,31200,0.00,0.00,s,1.00,1.00,0,none,true)[arctap(28650),arctap(31200)]; 856 | arc(27900,28050,0.00,0.00,s,1.00,1.00,0,none,false); 857 | arc(30000,30150,0.00,0.00,s,1.00,1.00,0,none,false); 858 | arc(30150,30150,0.00,0.50,s,1.00,0.00,0,none,false); 859 | arc(30150,30300,0.50,0.50,s,0.00,0.00,0,none,false); 860 | arc(30300,30300,0.50,0.00,s,0.00,1.00,0,none,false); 861 | arc(30300,30450,0.00,0.00,s,1.00,1.00,0,none,false); 862 | arc(30450,30450,0.00,0.50,s,1.00,0.00,0,none,false); 863 | arc(30450,30600,0.50,0.50,s,0.00,0.00,0,none,false); 864 | arc(30600,30600,0.50,0.00,s,0.00,1.00,0,none,false); 865 | arc(30600,30900,0.00,0.00,s,1.00,1.00,0,none,false); 866 | arc(30900,30900,0.50,0.00,s,0.00,1.00,0,none,false); 867 | arc(30900,31050,0.50,0.50,s,0.00,0.00,0,none,false); 868 | arc(31200,31500,1.00,0.50,so,1.00,1.00,1,none,true); 869 | arc(31200,31500,0.00,0.50,so,1.00,1.00,0,none,true); 870 | arc(31500,40350,0.50,0.50,s,1.00,1.00,0,none,true)[arctap(36000),arctap(36300),arctap(36600),arctap(36900),arctap(37200),arctap(37500),arctap(37800),arctap(38100),arctap(38400),arctap(38550),arctap(38700),arctap(38850),arctap(39600),arctap(39750),arctap(39900),arctap(40050),arctap(40200),arctap(40350)]; 871 | arc(40350,40500,0.50,0.50,sisi,1.00,0.00,0,none,true)[arctap(40500)]; 872 | arc(41100,41362,0.00,0.00,s,1.00,1.00,0,none,false); 873 | arc(41362,41437,0.00,0.50,s,1.00,0.00,0,none,false); 874 | arc(41437,41662,0.50,0.50,s,0.00,0.00,0,none,false); 875 | arc(41662,41700,0.50,0.00,s,0.00,1.00,0,none,false); 876 | arc(41700,41962,0.00,0.00,s,1.00,1.00,0,none,false); 877 | arc(41962,42037,0.00,0.50,s,1.00,0.00,0,none,false); 878 | arc(42037,42262,0.50,0.50,s,0.00,0.00,0,none,false); 879 | arc(42262,42300,0.50,-0.10,s,0.00,0.00,0,none,false); 880 | arc(42300,42412,-0.10,-0.10,s,0.00,0.00,0,none,false); 881 | arc(42412,42450,-0.10,0.50,s,0.00,0.00,0,none,false); 882 | arc(42450,42562,0.50,0.50,s,0.00,0.00,0,none,false); 883 | arc(42562,42600,0.50,-0.10,s,0.00,0.00,0,none,false); 884 | arc(42600,42712,-0.10,-0.10,s,0.00,0.00,0,none,false); 885 | arc(42712,42750,-0.10,0.50,s,0.00,0.00,0,none,false); 886 | arc(42750,42862,0.50,0.50,s,0.00,0.00,0,none,false); 887 | arc(42862,42900,0.50,-0.10,s,0.00,0.00,0,none,false); 888 | arc(42900,43012,-0.10,-0.10,s,0.00,0.00,0,none,false); 889 | arc(43012,44100,-0.10,0.00,sisi,0.00,1.00,0,none,true); 890 | arc(43200,43237,1.00,0.50,s,1.00,0.00,1,none,false); 891 | arc(43237,43350,0.50,0.50,s,0.00,0.00,1,none,false); 892 | arc(44100,44137,0.00,0.50,s,1.00,0.00,0,none,false); 893 | arc(44100,50400,0.00,0.00,s,1.00,1.00,0,none,true); 894 | arc(44137,44250,0.50,0.50,s,0.00,0.00,0,none,false); 895 | arc(45900,46162,1.00,1.00,s,1.00,1.00,1,none,false); 896 | arc(46162,46237,1.00,0.50,s,1.00,0.00,1,none,false); 897 | arc(46237,46462,0.50,0.50,s,0.00,0.00,1,none,false); 898 | arc(46462,46500,0.50,1.00,s,0.00,1.00,1,none,false); 899 | arc(46500,46762,1.00,1.00,s,1.00,1.00,1,none,false); 900 | arc(46762,46837,1.00,0.50,s,1.00,0.00,1,none,false); 901 | arc(46837,47062,0.50,0.50,s,0.00,0.00,1,none,false); 902 | arc(47062,47100,0.50,1.10,s,0.00,0.00,1,none,false); 903 | arc(47100,47212,1.10,1.10,s,0.00,0.00,1,none,false); 904 | arc(47212,47250,1.10,0.50,s,0.00,0.00,1,none,false); 905 | arc(47250,47362,0.50,0.50,s,0.00,0.00,1,none,false); 906 | arc(47362,47400,0.50,1.10,s,0.00,0.00,1,none,false); 907 | arc(47400,47512,1.10,1.10,s,0.00,0.00,1,none,false); 908 | arc(47512,47550,1.10,0.50,s,0.00,0.00,1,none,false); 909 | arc(47550,47662,0.50,0.50,s,0.00,0.00,1,none,false); 910 | arc(47662,47700,0.50,1.10,s,0.00,0.00,1,none,false); 911 | arc(47700,47812,1.10,1.10,s,0.00,0.00,1,none,false); 912 | arc(47812,48900,1.10,1.00,sisi,0.00,1.00,1,none,true); 913 | arc(48000,48037,0.00,0.50,s,1.00,0.00,0,none,false); 914 | arc(48037,48150,0.50,0.50,s,0.00,0.00,0,none,false); 915 | arc(48900,48937,1.00,0.50,s,1.00,0.00,1,none,false); 916 | arc(48900,50400,1.00,0.50,si,1.00,1.00,1,none,true); 917 | arc(48937,49050,0.50,0.50,s,0.00,0.00,1,none,false); 918 | arc(50400,51000,0.00,0.50,so,1.00,1.00,0,none,true)[arctap(51000)]; 919 | arc(50400,51000,0.50,1.00,so,1.00,1.00,1,none,true); 920 | arc(51000,51600,0.50,0.00,so,1.00,1.00,0,none,true)[arctap(51150)]; 921 | arc(51000,51600,1.00,0.50,so,1.00,1.00,1,none,true)[arctap(51075),arctap(51225)]; 922 | arc(51600,52200,0.00,0.50,so,1.00,1.00,0,none,true)[arctap(51600),arctap(51750)]; 923 | arc(51600,52200,0.50,1.00,so,1.00,1.00,1,none,true)[arctap(51675),arctap(51825)]; 924 | arc(52200,52800,1.00,0.50,so,1.00,1.00,1,none,true)[arctap(52275),arctap(52425)]; 925 | arc(52200,52800,0.50,0.00,so,1.00,1.00,0,none,true)[arctap(52200),arctap(52350)]; 926 | arc(52800,53400,0.00,0.50,so,1.00,1.00,0,none,true)[arctap(53100),arctap(53250)]; 927 | arc(52800,53400,0.50,1.00,so,1.00,1.00,1,none,true)[arctap(53175),arctap(53325)]; 928 | arc(53400,54000,0.50,0.00,so,1.00,1.00,0,none,true)[arctap(53700),arctap(53850)]; 929 | arc(53400,54000,1.00,0.50,so,1.00,1.00,1,none,true)[arctap(53775),arctap(53925)]; 930 | arc(54000,54600,0.00,0.50,so,1.00,1.00,0,none,true)[arctap(54300),arctap(54450)]; 931 | arc(54000,54600,0.50,1.00,so,1.00,1.00,1,none,true)[arctap(54375),arctap(54525)]; 932 | arc(54600,55200,1.00,0.50,so,1.00,1.00,1,none,true)[arctap(55200)]; 933 | arc(54600,55200,0.50,0.00,so,1.00,1.00,0,none,true)[arctap(55200)]; 934 | arc(56250,56400,1.00,1.00,soso,1.00,0.00,1,none,false); 935 | arc(57600,57750,0.00,0.00,soso,1.00,0.00,0,none,false); 936 | arc(57600,60000,0.00,0.00,s,1.00,1.00,0,none,true)[arctap(58800),arctap(59400)]; 937 | arc(58200,58350,1.00,1.00,soso,1.00,0.00,1,none,false); 938 | arc(58200,60000,1.00,1.00,s,1.00,1.00,1,none,true)[arctap(59100),arctap(59400)]; 939 | arc(60000,60300,1.00,1.00,s,1.00,1.00,1,none,false); 940 | arc(60000,60300,0.00,0.00,s,1.00,1.00,0,none,false); 941 | arc(60000,60300,1.00,0.50,si,1.00,1.00,1,none,true); 942 | arc(60000,60300,0.00,0.50,si,1.00,1.00,0,none,true); 943 | arc(60300,60300,1.00,0.75,s,1.00,0.00,1,none,false); 944 | arc(60300,60450,0.25,0.25,s,0.00,0.00,0,none,false); 945 | arc(60300,60300,0.00,0.25,s,1.00,0.00,0,none,false); 946 | arc(60300,60450,0.75,0.75,s,0.00,0.00,1,none,false); 947 | arc(60300,64800,0.50,0.50,s,1.00,1.00,0,none,true)[arctap(60900),arctap(61500),arctap(61950),arctap(62250),arctap(62550),arctap(62850),arctap(63150),arctap(63225),arctap(63450),arctap(64050)]; 948 | arc(60450,60600,0.25,0.00,sisi,0.00,1.00,0,none,false); 949 | arc(60450,61200,0.75,1.00,si,0.00,0.00,1,none,true); 950 | arc(60600,60750,0.00,0.50,soso,1.00,0.00,0,none,false); 951 | arc(60750,60900,0.50,1.00,si,0.00,0.00,0,none,false); 952 | arc(60900,61200,1.00,0.00,so,0.00,0.00,0,none,false); 953 | arc(61200,61350,1.00,1.00,s,0.00,0.00,1,none,false); 954 | arc(61200,62400,0.00,0.00,s,0.00,0.00,0,none,true); 955 | arc(61350,61500,1.00,0.00,s,0.00,0.00,1,none,false); 956 | arc(61500,61650,0.00,0.00,s,0.00,0.00,1,none,false); 957 | arc(61650,61800,0.00,1.00,s,0.00,0.00,1,none,false); 958 | arc(61800,61950,1.00,1.00,s,0.00,0.00,1,none,false); 959 | arc(61950,62100,1.00,0.00,s,0.00,0.00,1,none,false); 960 | arc(62100,62250,0.00,0.00,s,0.00,0.00,1,none,false); 961 | arc(62250,62400,0.00,1.00,s,0.00,0.00,1,none,false); 962 | arc(62400,62550,0.00,0.00,s,0.00,0.00,0,none,false); 963 | arc(62400,63600,1.00,1.00,s,0.00,0.00,1,none,true); 964 | arc(62550,62700,0.00,1.00,s,0.00,0.00,0,none,false); 965 | arc(62700,62850,1.00,1.00,s,0.00,0.00,0,none,false); 966 | arc(62850,63000,1.00,0.00,s,0.00,0.00,0,none,false); 967 | arc(63000,63150,0.00,0.00,s,0.00,0.00,0,none,false); 968 | arc(63150,63300,0.00,1.00,s,0.00,0.00,0,none,false); 969 | arc(63300,63450,1.00,1.00,s,0.00,0.00,0,none,false); 970 | arc(63450,63600,1.00,0.00,s,0.00,0.00,0,none,false); 971 | arc(63600,63825,1.00,0.00,si,0.00,0.00,1,none,false); 972 | arc(63600,64200,0.00,0.00,s,0.00,0.00,0,none,true); 973 | arc(63825,64050,0.00,1.00,so,0.00,0.00,1,none,false); 974 | arc(64050,64800,1.00,0.00,si,0.00,0.00,1,none,true); 975 | arc(64200,64425,0.00,1.00,si,0.00,0.00,0,none,false); 976 | arc(64425,64650,1.00,0.00,so,0.00,0.00,0,none,false); 977 | arc(64800,65400,0.00,1.00,si,0.00,0.00,1,none,false); 978 | arc(64800,66000,0.50,0.00,si,1.00,1.00,0,none,true); 979 | arc(65400,66000,1.00,1.00,sisi,0.00,1.00,1,none,true); 980 | arc(66000,66225,0.00,0.50,sisi,1.00,0.00,0,none,false); 981 | arc(66000,66600,1.00,1.00,s,1.00,1.00,1,none,true)[arctap(66225)]; 982 | arc(66225,66450,0.50,0.00,so,0.00,0.00,0,none,false); 983 | arc(66450,66600,0.00,0.00,sisi,0.00,1.00,0,none,true); 984 | arc(66600,67200,0.00,0.00,s,1.00,1.00,0,none,true)[arctap(66825)]; 985 | arc(66600,66825,1.00,0.50,sisi,1.00,0.00,1,none,false); 986 | arc(66825,67050,0.50,1.00,so,0.00,0.00,1,none,false); 987 | arc(67050,67500,1.00,1.00,sisi,0.00,1.00,1,none,true); 988 | arc(67200,67350,0.00,0.50,sisi,1.00,0.00,0,none,false); 989 | arc(67350,67500,0.50,0.00,soso,0.00,1.00,0,none,false); 990 | arc(67500,67650,1.00,0.50,sisi,1.00,0.00,1,none,false); 991 | arc(67650,67800,0.50,1.00,soso,0.00,1.00,1,none,false); 992 | arc(73200,73650,0.00,0.00,s,1.00,1.00,0,none,false); 993 | arc(73650,78600,0.00,0.00,s,1.00,1.00,0,none,true)[arctap(76800),arctap(77400),arctap(78000),arctap(78600)]; 994 | arc(73800,74400,1.00,0.50,soso,1.00,0.00,1,none,false); 995 | arc(74400,76800,0.50,1.00,sisi,0.00,1.00,1,none,true); 996 | arc(76800,78600,1.00,1.00,s,1.00,1.00,1,none,true)[arctap(77100),arctap(77700),arctap(78300),arctap(78600)]; 997 | arc(80100,80100,0.00,-0.50,s,0.80,0.00,0,none,true); 998 | arc(80100,80100,0.00,-0.50,s,0.00,0.80,0,none,true); 999 | arc(81300,81300,0.50,0.00,s,0.80,0.00,0,none,true); 1000 | arc(81300,81300,0.50,0.00,s,0.00,0.80,0,none,true); 1001 | arc(82500,82500,1.00,0.50,s,0.80,0.00,1,none,true); 1002 | arc(82500,82500,0.50,1.00,s,0.80,0.00,1,none,true); 1003 | arc(83700,83700,1.25,1.25,s,1.00,0.00,1,none,true); 1004 | arc(83700,83700,1.25,1.00,s,0.00,0.60,1,none,true); 1005 | arc(83700,83700,1.25,1.50,s,0.00,0.60,1,none,true); 1006 | arc(91200,91500,0.00,0.00,s,1.00,1.00,0,none,false); 1007 | arc(91500,97800,0.00,0.00,s,1.00,1.00,0,none,true)[arctap(97200),arctap(97350),arctap(97800)]; 1008 | arc(91650,91950,1.00,1.00,s,1.00,1.00,1,none,false); 1009 | arc(91950,97800,1.00,1.00,s,1.00,1.00,1,none,true)[arctap(97500),arctap(97650),arctap(97800)]; 1010 | arc(92400,92700,0.00,0.00,s,1.00,1.00,0,none,false); 1011 | arc(93000,93300,1.00,1.00,s,1.00,1.00,1,none,false); 1012 | arc(108000,108075,0.00,0.25,s,1.00,0.00,0,none,false); 1013 | arc(108000,108150,0.00,0.25,si,1.00,1.00,0,none,true); 1014 | arc(108000,108300,0.00,0.75,si,1.00,1.00,1,none,true); 1015 | arc(108075,109200,0.25,0.25,s,0.00,0.00,0,none,false); 1016 | arc(108150,112800,0.25,0.25,s,1.00,1.00,0,none,true)[arctap(108600),arctap(108900),arctap(109350),arctap(109650),arctap(110850),arctap(112350),arctap(112650),arctap(112800)]; 1017 | arc(108300,112800,0.75,0.75,s,1.00,1.00,1,none,true)[arctap(109950),arctap(110250),arctap(111150),arctap(111450),arctap(111750),arctap(112050),arctap(112800)]; 1018 | arc(110400,110475,1.00,0.75,s,1.00,0.00,1,none,false); 1019 | arc(110475,111600,0.75,0.75,s,0.00,0.00,1,none,false); 1020 | arc(112800,114000,0.25,0.00,si,1.00,1.00,0,none,true)[arctap(114000)]; 1021 | arc(112800,114000,0.75,1.00,si,1.00,1.00,1,none,true)[arctap(114000)]; 1022 | arc(114000,117000,1.00,1.00,s,1.00,1.00,1,none,true)[arctap(116700),arctap(116325),arctap(116025),arctap(115875),arctap(116175)]; 1023 | arc(114000,117000,0.00,0.00,s,1.00,1.00,0,none,true)[arctap(116400),arctap(117000),arctap(115800),arctap(115950),arctap(116100),arctap(116250)]; 1024 | arc(117000,117600,1.00,0.50,so,1.00,1.00,1,none,true)[arctap(117525),arctap(117375)]; 1025 | arc(117000,117600,0.00,0.50,so,1.00,1.00,0,none,true)[arctap(117300),arctap(117450)]; 1026 | arc(117600,143400,0.50,0.50,s,1.00,1.00,0,none,true)[arctap(117900),arctap(118350),arctap(118800),arctap(119250),arctap(119550),arctap(119850),arctap(120300),arctap(120900),arctap(121500),arctap(121950),arctap(123450),arctap(124050),arctap(124350),arctap(124650),arctap(125475),arctap(125550),arctap(125700),arctap(125775),arctap(125925),arctap(126000),arctap(127050),arctap(127500),arctap(127950),arctap(128550),arctap(128700),arctap(128850),arctap(128400),arctap(129900),arctap(130350),arctap(130650),arctap(133800),arctap(133950),arctap(134100),arctap(134250),arctap(135600),arctap(135900),arctap(136200),arctap(143400)]; 1027 | arc(120000,120450,0.50,-0.25,sisi,1.00,0.00,0,none,false); 1028 | arc(120450,120600,0.50,1.25,sisi,1.00,0.00,1,none,false); 1029 | arc(122400,122850,0.50,1.00,sisi,1.00,0.00,1,none,false); 1030 | arc(122400,122850,0.50,0.00,sisi,1.00,0.00,0,none,false); 1031 | arc(122850,123000,1.00,1.00,sisi,0.00,1.00,1,none,true); 1032 | arc(122850,123000,0.00,0.00,sisi,0.00,1.00,0,none,true); 1033 | arc(123000,126825,1.00,1.00,s,1.00,1.00,1,none,true)[arctap(123600),arctap(123825),arctap(124050),arctap(125625),arctap(126825)]; 1034 | arc(123000,126600,0.00,0.00,s,1.00,1.00,0,none,true)[arctap(123000),arctap(123225),arctap(123450),arctap(125400),arctap(125850),arctap(126600)]; 1035 | arc(126600,127200,0.00,0.00,sisi,1.00,0.00,0,none,true); 1036 | arc(126825,127200,1.00,1.00,sisi,1.00,0.00,1,none,true); 1037 | arc(127200,143100,1.00,1.00,s,0.00,0.00,1,none,true)[arctap(127350),arctap(127875),arctap(128100),arctap(128700),arctap(128850),arctap(129750),arctap(130500),arctap(132600),arctap(133200),arctap(133350),arctap(133650),arctap(133500),arctap(134475),arctap(134625),arctap(135225),arctap(135075),arctap(137400),arctap(139650),arctap(140850),arctap(140700),arctap(141300),arctap(141450),arctap(143100)]; 1038 | arc(127200,142800,0.00,0.00,s,0.00,0.00,0,none,true)[arctap(127425),arctap(127800),arctap(128100),arctap(128400),arctap(128550),arctap(130200),arctap(132000),arctap(133200),arctap(133350),arctap(133500),arctap(133650),arctap(134400),arctap(134550),arctap(135000),arctap(135150),arctap(136800),arctap(140250),arctap(140400),arctap(140550),arctap(141000),arctap(141150),arctap(142800)]; 1039 | arc(130800,131250,1.00,1.00,s,0.00,0.00,1,none,false); 1040 | arc(130800,131250,0.00,0.00,s,0.00,0.00,0,none,false); 1041 | arc(138000,138450,0.50,0.50,s,1.00,1.00,0,none,false); 1042 | arc(138600,139050,0.50,0.50,s,1.00,1.00,1,none,false); 1043 | arc(139200,139650,0.00,0.00,s,0.00,0.00,0,none,false); 1044 | arc(139800,140250,1.00,1.00,s,0.00,0.00,1,none,false); 1045 | timinggroup(noinput){ 1046 | timing(0,200.00,4.00); 1047 | timing(69600,100.00,4.00); 1048 | timing(73200,200.00,4.00); 1049 | timing(79200,0.00,0.00); 1050 | timing(79499,20000.00,4.00); 1051 | timing(79500,0.00,0.00); 1052 | timing(79799,20000.00,4.00); 1053 | timing(79800,0.00,0.00); 1054 | timing(80099,20000.00,4.00); 1055 | timing(80100,0.00,0.00); 1056 | timing(80399,20000.00,4.00); 1057 | timing(80400,0.00,0.00); 1058 | timing(80699,20000.00,4.00); 1059 | timing(80700,0.00,0.00); 1060 | timing(80999,20000.00,4.00); 1061 | timing(81000,0.00,0.00); 1062 | timing(81299,20000.00,4.00); 1063 | timing(81300,0.00,0.00); 1064 | timing(81599,20000.00,4.00); 1065 | timing(81600,0.00,0.00); 1066 | timing(81899,20000.00,4.00); 1067 | timing(81900,0.00,0.00); 1068 | timing(82199,20000.00,4.00); 1069 | timing(82200,0.00,0.00); 1070 | timing(82499,20000.00,4.00); 1071 | timing(82500,0.00,0.00); 1072 | timing(82799,20000.00,4.00); 1073 | timing(82800,0.00,0.00); 1074 | timing(83099,20000.00,4.00); 1075 | timing(83100,0.00,0.00); 1076 | timing(83399,20000.00,4.00); 1077 | timing(83400,0.00,0.00); 1078 | timing(83699,20000.00,4.00); 1079 | timing(83700,0.00,0.00); 1080 | timing(83999,20000.00,4.00); 1081 | timing(84000,0.00,0.00); 1082 | timing(84299,20000.00,4.00); 1083 | timing(84300,0.00,0.00); 1084 | timing(84599,20000.00,4.00); 1085 | timing(84600,0.00,0.00); 1086 | timing(84899,20000.00,4.00); 1087 | timing(84900,0.00,0.00); 1088 | timing(85199,20000.00,4.00); 1089 | timing(85200,0.00,0.00); 1090 | timing(85499,20000.00,4.00); 1091 | timing(85500,0.00,0.00); 1092 | timing(85799,20000.00,4.00); 1093 | timing(85800,0.00,0.00); 1094 | timing(86099,20000.00,4.00); 1095 | timing(86100,0.00,0.00); 1096 | timing(86399,20000.00,4.00); 1097 | timing(86400,0.00,0.00); 1098 | timing(86699,20000.00,4.00); 1099 | timing(86700,0.00,0.00); 1100 | timing(86999,20000.00,4.00); 1101 | timing(87000,0.00,0.00); 1102 | timing(87299,20000.00,4.00); 1103 | timing(87300,0.00,0.00); 1104 | timing(87599,20000.00,4.00); 1105 | timing(87600,0.00,0.00); 1106 | timing(87899,20000.00,4.00); 1107 | timing(87900,0.00,0.00); 1108 | timing(88199,20000.00,4.00); 1109 | timing(88200,0.00,0.00); 1110 | timing(88499,20000.00,4.00); 1111 | timing(88500,200.00,4.00); 1112 | (80100,1); 1113 | (81300,2); 1114 | (82500,3); 1115 | (84900,1); 1116 | (86100,2); 1117 | (87300,3); 1118 | }; 1119 | timinggroup(){ 1120 | timing(0,200.00,4.00); 1121 | timing(69600,100.00,4.00); 1122 | timing(73200,200.00,4.00); 1123 | timing(79200,0.00,0.00); 1124 | timing(79499,20000.00,4.00); 1125 | timing(79500,0.00,0.00); 1126 | timing(79799,20000.00,4.00); 1127 | timing(79800,0.00,0.00); 1128 | timing(80099,20000.00,4.00); 1129 | timing(80100,0.00,0.00); 1130 | timing(80399,20000.00,4.00); 1131 | timing(80400,0.00,0.00); 1132 | timing(80699,20000.00,4.00); 1133 | timing(80700,0.00,0.00); 1134 | timing(80999,20000.00,4.00); 1135 | timing(81000,0.00,0.00); 1136 | timing(81299,20000.00,4.00); 1137 | timing(81300,0.00,0.00); 1138 | timing(81599,20000.00,4.00); 1139 | timing(81600,0.00,0.00); 1140 | timing(81899,20000.00,4.00); 1141 | timing(81900,0.00,0.00); 1142 | timing(82199,20000.00,4.00); 1143 | timing(82200,0.00,0.00); 1144 | timing(82499,20000.00,4.00); 1145 | timing(82500,0.00,0.00); 1146 | timing(82799,20000.00,4.00); 1147 | timing(82800,0.00,0.00); 1148 | timing(83099,20000.00,4.00); 1149 | timing(83100,0.00,0.00); 1150 | timing(83399,20000.00,4.00); 1151 | timing(83400,0.00,0.00); 1152 | timing(83699,20000.00,4.00); 1153 | timing(83700,0.00,0.00); 1154 | timing(83999,20000.00,4.00); 1155 | timing(84000,0.00,0.00); 1156 | timing(84299,20000.00,4.00); 1157 | timing(84300,0.00,0.00); 1158 | timing(84599,20000.00,4.00); 1159 | timing(84600,0.00,0.00); 1160 | timing(84899,20000.00,4.00); 1161 | timing(84900,0.00,0.00); 1162 | timing(85199,20000.00,4.00); 1163 | timing(85200,0.00,0.00); 1164 | timing(85499,20000.00,4.00); 1165 | timing(85500,0.00,0.00); 1166 | timing(85799,20000.00,4.00); 1167 | timing(85800,0.00,0.00); 1168 | timing(86099,20000.00,4.00); 1169 | timing(86100,0.00,0.00); 1170 | timing(86399,20000.00,4.00); 1171 | timing(86400,0.00,0.00); 1172 | timing(86699,20000.00,4.00); 1173 | timing(86700,0.00,0.00); 1174 | timing(86999,20000.00,4.00); 1175 | timing(87000,0.00,0.00); 1176 | timing(87299,20000.00,4.00); 1177 | timing(87300,0.00,0.00); 1178 | timing(87599,20000.00,4.00); 1179 | timing(87600,0.00,0.00); 1180 | timing(87899,20000.00,4.00); 1181 | timing(87900,0.00,0.00); 1182 | timing(88199,20000.00,4.00); 1183 | timing(88200,0.00,0.00); 1184 | timing(88499,20000.00,4.00); 1185 | timing(88500,200.00,4.00); 1186 | timing(98400,100.00,4.00); 1187 | timing(103200,200.00,4.00); 1188 | arc(103200,103275,0.50,0.48,s,0.00,0.03,0,none,false); 1189 | arc(103275,103350,0.48,0.47,s,0.03,0.06,0,none,true); 1190 | arc(103275,103350,0.50,0.52,s,0.00,0.03,1,none,false); 1191 | arc(103350,103425,0.47,0.45,s,0.06,0.09,0,none,false); 1192 | arc(103350,103425,0.52,0.53,s,0.03,0.06,1,none,true); 1193 | arc(103425,103500,0.45,0.44,s,0.09,0.12,0,none,true); 1194 | arc(103425,103500,0.53,0.55,s,0.06,0.09,1,none,false); 1195 | arc(103500,103575,0.44,0.42,s,0.12,0.15,0,none,false); 1196 | arc(103500,103575,0.55,0.56,s,0.09,0.12,1,none,true); 1197 | arc(103575,103650,0.42,0.41,s,0.15,0.18,0,none,true); 1198 | arc(103575,103650,0.56,0.58,s,0.12,0.15,1,none,false); 1199 | arc(103650,103725,0.41,0.40,s,0.18,0.21,0,none,false); 1200 | arc(103650,103725,0.58,0.59,s,0.15,0.18,1,none,true); 1201 | arc(103725,103800,0.40,0.38,s,0.21,0.24,0,none,true); 1202 | arc(103725,103800,0.59,0.60,s,0.18,0.21,1,none,false); 1203 | arc(103800,103875,0.38,0.37,s,0.24,0.27,0,none,false); 1204 | arc(103800,103875,0.60,0.62,s,0.21,0.24,1,none,true); 1205 | arc(103875,103950,0.37,0.35,s,0.27,0.30,0,none,true); 1206 | arc(103875,103950,0.62,0.63,s,0.24,0.27,1,none,false); 1207 | arc(103950,104025,0.35,0.34,s,0.30,0.33,0,none,false); 1208 | arc(103950,104025,0.63,0.65,s,0.27,0.30,1,none,true); 1209 | arc(104025,104100,0.34,0.32,s,0.33,0.35,0,none,true); 1210 | arc(104025,104100,0.65,0.66,s,0.30,0.33,1,none,false); 1211 | arc(104100,104175,0.32,0.31,s,0.35,0.38,0,none,false); 1212 | arc(104100,104175,0.66,0.68,s,0.33,0.35,1,none,true); 1213 | arc(104175,104250,0.31,0.29,s,0.38,0.41,0,none,true); 1214 | arc(104175,104250,0.68,0.69,s,0.35,0.38,1,none,false); 1215 | arc(104250,104325,0.29,0.28,s,0.41,0.44,0,none,false); 1216 | arc(104250,104325,0.69,0.71,s,0.38,0.41,1,none,true); 1217 | arc(104325,104400,0.28,0.27,s,0.44,0.46,0,none,true); 1218 | arc(104325,104400,0.71,0.72,s,0.41,0.44,1,none,false); 1219 | arc(104400,104475,0.27,0.25,s,0.46,0.49,0,none,false); 1220 | arc(104400,104475,0.72,0.73,s,0.44,0.46,1,none,true); 1221 | arc(104475,104550,0.25,0.24,s,0.49,0.52,0,none,true); 1222 | arc(104475,104550,0.73,0.75,s,0.46,0.49,1,none,false); 1223 | arc(104550,104625,0.24,0.23,s,0.52,0.54,0,none,false); 1224 | arc(104550,104625,0.75,0.76,s,0.49,0.52,1,none,true); 1225 | arc(104625,104700,0.23,0.22,s,0.54,0.57,0,none,true); 1226 | arc(104625,104700,0.76,0.77,s,0.52,0.54,1,none,false); 1227 | arc(104700,104775,0.22,0.20,s,0.57,0.59,0,none,false); 1228 | arc(104700,104775,0.77,0.78,s,0.54,0.57,1,none,true); 1229 | arc(104775,104850,0.20,0.19,s,0.59,0.62,0,none,true); 1230 | arc(104775,104850,0.78,0.80,s,0.57,0.59,1,none,false); 1231 | arc(104850,104925,0.19,0.18,s,0.62,0.64,0,none,false); 1232 | arc(104850,104925,0.80,0.81,s,0.59,0.62,1,none,true); 1233 | arc(104925,105000,0.18,0.17,s,0.64,0.66,0,none,true); 1234 | arc(104925,105000,0.81,0.82,s,0.62,0.64,1,none,false); 1235 | arc(105000,105075,0.17,0.16,s,0.66,0.69,0,none,false); 1236 | arc(105000,105075,0.82,0.83,s,0.64,0.66,1,none,true); 1237 | arc(105075,105150,0.16,0.15,s,0.69,0.71,0,none,true); 1238 | arc(105075,105150,0.83,0.84,s,0.66,0.69,1,none,false); 1239 | arc(105150,105225,0.15,0.14,s,0.71,0.73,0,none,false); 1240 | arc(105150,105225,0.84,0.85,s,0.69,0.71,1,none,true); 1241 | arc(105225,105300,0.14,0.13,s,0.73,0.75,0,none,true); 1242 | arc(105225,105300,0.85,0.86,s,0.71,0.73,1,none,false); 1243 | arc(105300,105375,0.13,0.12,s,0.75,0.77,0,none,false); 1244 | arc(105300,105375,0.86,0.87,s,0.73,0.75,1,none,true); 1245 | arc(105375,105450,0.12,0.11,s,0.77,0.79,0,none,true); 1246 | arc(105375,105450,0.87,0.88,s,0.75,0.77,1,none,false); 1247 | arc(105450,105525,0.11,0.10,s,0.79,0.81,0,none,false); 1248 | arc(105450,105525,0.88,0.89,s,0.77,0.79,1,none,true); 1249 | arc(105525,105600,0.10,0.09,s,0.81,0.82,0,none,true); 1250 | arc(105525,105600,0.89,0.90,s,0.79,0.81,1,none,false); 1251 | arc(105600,105675,0.09,0.08,s,0.82,0.84,0,none,false); 1252 | arc(105600,105675,0.90,0.91,s,0.81,0.82,1,none,true); 1253 | arc(105675,105750,0.08,0.07,s,0.84,0.86,0,none,true); 1254 | arc(105675,105750,0.91,0.92,s,0.82,0.84,1,none,false); 1255 | arc(105750,105825,0.07,0.06,s,0.86,0.87,0,none,false); 1256 | arc(105750,105825,0.92,0.93,s,0.84,0.86,1,none,true); 1257 | arc(105825,105900,0.06,0.06,s,0.87,0.89,0,none,true); 1258 | arc(105825,105900,0.93,0.94,s,0.86,0.87,1,none,false); 1259 | arc(105900,105975,0.06,0.05,s,0.89,0.90,0,none,false); 1260 | arc(105900,105975,0.94,0.94,s,0.87,0.89,1,none,true); 1261 | arc(105975,106050,0.05,0.04,s,0.90,0.91,0,none,true); 1262 | arc(105975,106050,0.94,0.95,s,0.89,0.90,1,none,false); 1263 | arc(106050,106125,0.04,0.04,s,0.91,0.92,0,none,false); 1264 | arc(106050,106125,0.95,0.96,s,0.90,0.91,1,none,true); 1265 | arc(106125,106200,0.04,0.03,s,0.92,0.94,0,none,true); 1266 | arc(106125,106200,0.96,0.96,s,0.91,0.92,1,none,false); 1267 | arc(106200,106275,0.03,0.03,s,0.94,0.95,0,none,false); 1268 | arc(106200,106275,0.96,0.97,s,0.92,0.94,1,none,true); 1269 | arc(106275,106350,0.03,0.02,s,0.95,0.95,0,none,true); 1270 | arc(106275,106350,0.97,0.97,s,0.94,0.95,1,none,false); 1271 | arc(106350,106425,0.02,0.02,s,0.95,0.96,0,none,false); 1272 | arc(106350,106425,0.97,0.98,s,0.95,0.95,1,none,true); 1273 | arc(106425,106500,0.02,0.01,s,0.96,0.97,0,none,true); 1274 | arc(106425,106500,0.98,0.98,s,0.95,0.96,1,none,false); 1275 | arc(106500,106575,0.01,0.01,s,0.97,0.98,0,none,false); 1276 | arc(106500,106575,0.98,0.99,s,0.96,0.97,1,none,true); 1277 | arc(106575,106650,0.01,0.01,s,0.98,0.98,0,none,true); 1278 | arc(106575,106650,0.99,0.99,s,0.97,0.98,1,none,false); 1279 | arc(106650,106725,0.01,0.01,s,0.98,0.99,0,none,false); 1280 | arc(106650,106725,0.99,0.99,s,0.98,0.98,1,none,true); 1281 | arc(106725,106800,0.01,0.00,s,0.99,0.99,0,none,true); 1282 | arc(106725,106800,0.99,0.99,s,0.98,0.99,1,none,false); 1283 | arc(106800,106875,0.00,0.00,s,0.99,1.00,0,none,false); 1284 | arc(106800,106875,0.99,1.00,s,0.99,0.99,1,none,true); 1285 | arc(106875,106950,0.00,0.00,s,1.00,1.00,0,none,true); 1286 | arc(106875,106950,1.00,1.00,s,0.99,1.00,1,none,false); 1287 | arc(106950,107025,0.00,0.00,s,1.00,1.00,0,none,false); 1288 | arc(106950,107025,1.00,1.00,s,1.00,1.00,1,none,true); 1289 | arc(107025,107100,1.00,1.00,s,1.00,1.00,1,none,false); 1290 | }; -------------------------------------------------------------------------------- /assets/arc_body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arcaea-Infinity/Aff2Preview/774b759bab250d6043b2bf6d71971c48593d8513/assets/arc_body.png -------------------------------------------------------------------------------- /assets/base.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arcaea-Infinity/Aff2Preview/774b759bab250d6043b2bf6d71971c48593d8513/assets/base.jpg -------------------------------------------------------------------------------- /assets/note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arcaea-Infinity/Aff2Preview/774b759bab250d6043b2bf6d71971c48593d8513/assets/note.png -------------------------------------------------------------------------------- /assets/note_hold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arcaea-Infinity/Aff2Preview/774b759bab250d6043b2bf6d71971c48593d8513/assets/note_hold.png -------------------------------------------------------------------------------- /output.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arcaea-Infinity/Aff2Preview/774b759bab250d6043b2bf6d71971c48593d8513/output.jpg --------------------------------------------------------------------------------