feat: read and parse Simfiles

This commit is contained in:
Orangerot 2024-12-28 01:29:12 +01:00
parent 97598c741d
commit 9eb8a29382
2 changed files with 108 additions and 3 deletions

View file

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:sense_the_rhythm/simfile.dart';
class Level extends StatefulWidget {
const Level({super.key, required this.stepmaniaFolderPath});
@ -14,6 +15,7 @@ class Level extends StatefulWidget {
class _LevelState extends State<Level> {
final player = AudioPlayer();
Simfile? simfile;
bool _isPlaying = true;
Duration? _duration;
Duration? _position;
@ -51,10 +53,7 @@ class _LevelState extends State<Level> {
_positionSubscription = player.onPositionChanged.listen(
(p) => setState(() => _position = p),
);
}
@override
Widget build(BuildContext context) {
player.onDurationChanged.listen((Duration d) {
// print('Max duration: $d');
setState(() => _duration = d);
@ -65,12 +64,29 @@ class _LevelState extends State<Level> {
setState(() => _position = p);
});
String simfilePath = Directory(widget.stepmaniaFolderPath)
.listSync()
.firstWhere((entity) => entity.path.endsWith('.sm'),
orElse: () => File(''))
.path;
String audioPath = Directory(widget.stepmaniaFolderPath)
.listSync()
.firstWhere((entity) => entity.path.endsWith('.ogg'),
orElse: () => File(''))
.path;
simfile = Simfile(simfilePath);
simfile!.load();
print(audioPath);
player.play(DeviceFileSource(audioPath));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(

89
lib/simfile.dart Normal file
View 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;
}
}
}
}