feat: read and parse Simfiles
This commit is contained in:
		
							parent
							
								
									97598c741d
								
							
						
					
					
						commit
						9eb8a29382
					
				|  | @ -3,6 +3,7 @@ import 'dart:io'; | ||||||
| 
 | 
 | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:audioplayers/audioplayers.dart'; | import 'package:audioplayers/audioplayers.dart'; | ||||||
|  | import 'package:sense_the_rhythm/simfile.dart'; | ||||||
| 
 | 
 | ||||||
| class Level extends StatefulWidget { | class Level extends StatefulWidget { | ||||||
|   const Level({super.key, required this.stepmaniaFolderPath}); |   const Level({super.key, required this.stepmaniaFolderPath}); | ||||||
|  | @ -14,6 +15,7 @@ class Level extends StatefulWidget { | ||||||
| 
 | 
 | ||||||
| class _LevelState extends State<Level> { | class _LevelState extends State<Level> { | ||||||
|   final player = AudioPlayer(); |   final player = AudioPlayer(); | ||||||
|  |   Simfile? simfile; | ||||||
|   bool _isPlaying = true; |   bool _isPlaying = true; | ||||||
|   Duration? _duration; |   Duration? _duration; | ||||||
|   Duration? _position; |   Duration? _position; | ||||||
|  | @ -51,10 +53,7 @@ class _LevelState extends State<Level> { | ||||||
|     _positionSubscription = player.onPositionChanged.listen( |     _positionSubscription = player.onPositionChanged.listen( | ||||||
|       (p) => setState(() => _position = p), |       (p) => setState(() => _position = p), | ||||||
|     ); |     ); | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   @override |  | ||||||
|   Widget build(BuildContext context) { |  | ||||||
|     player.onDurationChanged.listen((Duration d) { |     player.onDurationChanged.listen((Duration d) { | ||||||
|       // print('Max duration: $d'); |       // print('Max duration: $d'); | ||||||
|       setState(() => _duration = d); |       setState(() => _duration = d); | ||||||
|  | @ -65,12 +64,29 @@ class _LevelState extends State<Level> { | ||||||
|       setState(() => _position = p); |       setState(() => _position = p); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |     String simfilePath = Directory(widget.stepmaniaFolderPath) | ||||||
|  |         .listSync() | ||||||
|  |         .firstWhere((entity) => entity.path.endsWith('.sm'), | ||||||
|  |             orElse: () => File('')) | ||||||
|  |         .path; | ||||||
|  | 
 | ||||||
|     String audioPath = Directory(widget.stepmaniaFolderPath) |     String audioPath = Directory(widget.stepmaniaFolderPath) | ||||||
|         .listSync() |         .listSync() | ||||||
|         .firstWhere((entity) => entity.path.endsWith('.ogg'), |         .firstWhere((entity) => entity.path.endsWith('.ogg'), | ||||||
|             orElse: () => File('')) |             orElse: () => File('')) | ||||||
|         .path; |         .path; | ||||||
|  | 
 | ||||||
|  |     simfile = Simfile(simfilePath); | ||||||
|  |     simfile!.load(); | ||||||
|  | 
 | ||||||
|  |     print(audioPath); | ||||||
|  | 
 | ||||||
|     player.play(DeviceFileSource(audioPath)); |     player.play(DeviceFileSource(audioPath)); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  | 
 | ||||||
|     return Scaffold( |     return Scaffold( | ||||||
|         appBar: AppBar( |         appBar: AppBar( | ||||||
|           leading: IconButton( |           leading: IconButton( | ||||||
|  |  | ||||||
							
								
								
									
										89
									
								
								lib/simfile.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								lib/simfile.dart
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,89 @@ | ||||||
|  | import 'dart:io'; | ||||||
|  | 
 | ||||||
|  | enum Difficulty { Beginner, Easy, Medium, Hard, Challenge, Edit } | ||||||
|  | 
 | ||||||
|  | // These are the standard note values: | ||||||
|  | // | ||||||
|  | //     0 – No note | ||||||
|  | //     1 – Normal note | ||||||
|  | //     2 – Hold head | ||||||
|  | //     3 – Hold/Roll tail | ||||||
|  | //     4 – Roll head | ||||||
|  | //     M – Mine (or other negative note) | ||||||
|  | // | ||||||
|  | // Later versions of StepMania accept other note values which may not work in older versions: | ||||||
|  | // | ||||||
|  | //     K – Automatic keysound | ||||||
|  | //     L – Lift note | ||||||
|  | //     F – Fake note | ||||||
|  | 
 | ||||||
|  | RegExp noteTypes = RegExp(r'^([012345MKLF]+)\s*([,;])?'); | ||||||
|  | 
 | ||||||
|  | class Chart { | ||||||
|  |   String? chartType; | ||||||
|  |   // Description/author | ||||||
|  |   String? author; | ||||||
|  |   // Difficulty (one of Beginner, Easy, Medium, Hard, Challenge, Edit) | ||||||
|  |   Difficulty? difficulty; | ||||||
|  |   // Numerical meter | ||||||
|  |   int? numericalMeter; | ||||||
|  |   // Groove radar values, generated by the program | ||||||
|  |   String? radarValues; | ||||||
|  | 
 | ||||||
|  |   List<List<String>>? measures; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class Simfile { | ||||||
|  |   String path; | ||||||
|  |   String? lines; | ||||||
|  | 
 | ||||||
|  |   // tags of simfile | ||||||
|  |   Map<String, String> tags = {}; | ||||||
|  | 
 | ||||||
|  |   Chart? chartSimplest; | ||||||
|  | 
 | ||||||
|  |   Simfile(this.path); | ||||||
|  | 
 | ||||||
|  |   void load() { | ||||||
|  |     lines = File(path).readAsStringSync(); | ||||||
|  | 
 | ||||||
|  |     RegExp commentsRegExp = RegExp(r'//.*$'); | ||||||
|  |     lines = lines?.replaceAll(commentsRegExp, ''); | ||||||
|  |     RegExp fieldDataRegExp = RegExp(r'#([^;]+):([^;]*);'); | ||||||
|  | 
 | ||||||
|  |     for (final fieldData in fieldDataRegExp.allMatches(lines!)) { | ||||||
|  |       List<String> keys = | ||||||
|  |           fieldData[1]!.split(':').map((key) => key.trim()).toList(); | ||||||
|  |       String value = fieldData[2]!; | ||||||
|  |       if (keys[0] != "NOTES") { | ||||||
|  |         tags[keys[0]] = value; | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       Chart chart = Chart(); | ||||||
|  |       chart.chartType = keys[1]; | ||||||
|  |       chart.author = keys[2]; | ||||||
|  |       chart.difficulty = Difficulty.values.byName(keys[3]); | ||||||
|  |       chart.numericalMeter = int.parse(keys[4]); | ||||||
|  |       chart.radarValues = keys[5]; | ||||||
|  | 
 | ||||||
|  |       if (chartSimplest == null || | ||||||
|  |           (chart.difficulty!.index <= chartSimplest!.difficulty!.index && | ||||||
|  |               chart.numericalMeter! <= chartSimplest!.numericalMeter!)) { | ||||||
|  |         List<List<String>> measures = []; | ||||||
|  |         for (final measureRaw in value.split(',')) { | ||||||
|  |           List<String> measure = []; | ||||||
|  |           for (final noteRaw in measureRaw.split('\n')) { | ||||||
|  |             String note = noteRaw.trim(); | ||||||
|  |             if (noteTypes.hasMatch(note)) { | ||||||
|  |               measure.add(note); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |           measures.add(measure); | ||||||
|  |         } | ||||||
|  |         chart.measures = measures; | ||||||
|  |         chartSimplest = chart; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
		Loading…
	
		Reference in a new issue