146 lines
3.8 KiB
Dart
146 lines
3.8 KiB
Dart
import 'dart:ffi';
|
||
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;
|
||
|
||
Map<double, String> beats = {};
|
||
}
|
||
|
||
class Simfile {
|
||
String directoryPath;
|
||
String? simfilePath;
|
||
String? audioPath;
|
||
String? bannerPath;
|
||
String? lines;
|
||
|
||
// tags of simfile
|
||
Map<String, String> tags = {};
|
||
|
||
Chart? chartSimplest;
|
||
|
||
Map<double, double> bpms = {};
|
||
double offset = 0;
|
||
|
||
Simfile(this.directoryPath);
|
||
|
||
void _parseChart({required List<String> keys, required String value}) {
|
||
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);
|
||
}
|
||
|
||
double bpm = bpms.entries.first.value;
|
||
|
||
for (final (measureIndex, measure) in measures.indexed) {
|
||
for (final (noteIndex, noteData) in measure.indexed) {
|
||
double beat = measureIndex * 4.0 +
|
||
(noteIndex.toDouble() / measure.length) * 4.0;
|
||
double minutesPerBeat = 1.0 / bpm;
|
||
double offsetMinutes = offset / 60.0;
|
||
chart.beats[beat * minutesPerBeat + offsetMinutes] = noteData;
|
||
}
|
||
}
|
||
|
||
chart.measures = measures;
|
||
chartSimplest = chart;
|
||
}
|
||
}
|
||
|
||
void _parseTag(RegExpMatch fieldData) {
|
||
List<String> keys =
|
||
fieldData[1]!.split(':').map((key) => key.trim()).toList();
|
||
String value = fieldData[2]!;
|
||
if (keys[0] == "BPMS") {
|
||
for (final pairRaw in value.split(',')) {
|
||
List<String> pair = pairRaw.split('=');
|
||
if (pair.length != 2) {
|
||
continue;
|
||
}
|
||
double time = double.parse(pair[0]);
|
||
double bpm = double.parse(pair[1]);
|
||
bpms[time] = bpm;
|
||
}
|
||
}
|
||
|
||
if (keys[0] == "OFFSET") {
|
||
offset = double.parse(value);
|
||
}
|
||
|
||
if (keys[0] != "NOTES") {
|
||
tags[keys[0]] = value;
|
||
return;
|
||
}
|
||
_parseChart(keys: keys, value: value);
|
||
}
|
||
|
||
void load() {
|
||
simfilePath = Directory(directoryPath)
|
||
.listSync()
|
||
.firstWhere((entity) => entity.path.endsWith('.sm'),
|
||
orElse: () => File('')).path;
|
||
|
||
audioPath = Directory(directoryPath)
|
||
.listSync()
|
||
.firstWhere((entity) => entity.path.endsWith('.ogg'),
|
||
orElse: () => File(''))
|
||
.path;
|
||
|
||
lines = File(simfilePath!).readAsStringSync();
|
||
|
||
RegExp commentsRegExp = RegExp(r'//.*$');
|
||
lines = lines?.replaceAll(commentsRegExp, '');
|
||
RegExp fieldDataRegExp = RegExp(r'#([^;]+):([^;]*);');
|
||
|
||
for (final fieldData in fieldDataRegExp.allMatches(lines!)) {
|
||
_parseTag(fieldData);
|
||
}
|
||
}
|
||
}
|