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