feat: display beats as arrows with correct time offset

This commit is contained in:
Orangerot 2024-12-28 23:41:36 +01:00
parent 9eb8a29382
commit e9ba842e21
2 changed files with 74 additions and 17 deletions

View file

@ -13,6 +13,24 @@ class Level extends StatefulWidget {
State<Level> createState() => _LevelState();
}
enum ArrowDirection {
left(Icons.arrow_back),
down(Icons.arrow_downward),
up(Icons.arrow_upward),
right(Icons.arrow_forward);
const ArrowDirection(this.icon);
final IconData icon;
}
class Note {
final double time;
final ArrowDirection direction;
const Note({required this.time, required this.direction});
}
class _LevelState extends State<Level> {
final player = AudioPlayer();
Simfile? simfile;
@ -23,6 +41,8 @@ class _LevelState extends State<Level> {
StreamSubscription? _durationSubscription;
StreamSubscription? _positionSubscription;
List<Note> notes = [];
@override
void setState(VoidCallback fn) {
// Subscriptions only can be closed asynchronously,
@ -79,6 +99,25 @@ class _LevelState extends State<Level> {
simfile = Simfile(simfilePath);
simfile!.load();
double bpm = simfile!.bpms.entries.first.value;
for (final (measureIndex, measure)
in simfile!.chartSimplest!.measures!.indexed) {
for (final (noteIndex, noteData) in measure.indexed) {
int arrowIndex = noteData.indexOf('1');
if (arrowIndex < 0 || arrowIndex > 3) {
continue;
}
notes.add(Note(
time: (measureIndex * 4.0 +
(noteIndex.toDouble() / measure.length) * 4.0) *
1.0 /
bpm +
simfile!.offset / 60.0,
direction: ArrowDirection.values[arrowIndex]));
}
}
print(audioPath);
player.play(DeviceFileSource(audioPath));
@ -86,7 +125,6 @@ class _LevelState extends State<Level> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
@ -123,18 +161,16 @@ class _LevelState extends State<Level> {
)),
),
body: Stack(children: [
Arrow(
position: -100.0,
),
Arrow(
position: 00.0,
),
Arrow(
position: 100.0,
),
Arrow(
position: 200.0,
),
...notes.map((note) {
return Arrow(
position: _position != null
? (note.time - _position!.inMilliseconds / 60000.0) *
20 *
MediaQuery.of(context).size.height
: 0.0,
direction: note.direction,
);
}),
Positioned(
top: 50,
width: MediaQuery.of(context).size.width,
@ -171,15 +207,16 @@ class _LevelState extends State<Level> {
class Arrow extends StatelessWidget {
final double position;
final ArrowDirection direction;
const Arrow({super.key, required this.position});
const Arrow({super.key, required this.position, required this.direction});
@override
Widget build(BuildContext context) {
return Positioned(
left: MediaQuery.of(context).size.width / 2 - 25, // Center the arrow
top: position,
child: Icon(size: 100, Icons.arrow_forward),
left: MediaQuery.of(context).size.width / 2 - 50, // Center the arrow
bottom: position + 50,
child: Icon(size: 100, color: Colors.redAccent.shade400, direction.icon),
);
}
}

View file

@ -1,3 +1,4 @@
import 'dart:ffi';
import 'dart:io';
enum Difficulty { Beginner, Easy, Medium, Hard, Challenge, Edit }
@ -42,6 +43,9 @@ class Simfile {
Chart? chartSimplest;
Map<double, double> bpms = {};
double offset = 0;
Simfile(this.path);
void load() {
@ -55,6 +59,22 @@ class Simfile {
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;
continue;