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(); 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> { class _LevelState extends State<Level> {
final player = AudioPlayer(); final player = AudioPlayer();
Simfile? simfile; Simfile? simfile;
@ -23,6 +41,8 @@ class _LevelState extends State<Level> {
StreamSubscription? _durationSubscription; StreamSubscription? _durationSubscription;
StreamSubscription? _positionSubscription; StreamSubscription? _positionSubscription;
List<Note> notes = [];
@override @override
void setState(VoidCallback fn) { void setState(VoidCallback fn) {
// Subscriptions only can be closed asynchronously, // Subscriptions only can be closed asynchronously,
@ -79,6 +99,25 @@ class _LevelState extends State<Level> {
simfile = Simfile(simfilePath); simfile = Simfile(simfilePath);
simfile!.load(); 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); print(audioPath);
player.play(DeviceFileSource(audioPath)); player.play(DeviceFileSource(audioPath));
@ -86,7 +125,6 @@ class _LevelState extends State<Level> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
@ -123,18 +161,16 @@ class _LevelState extends State<Level> {
)), )),
), ),
body: Stack(children: [ body: Stack(children: [
Arrow( ...notes.map((note) {
position: -100.0, return Arrow(
), position: _position != null
Arrow( ? (note.time - _position!.inMilliseconds / 60000.0) *
position: 00.0, 20 *
), MediaQuery.of(context).size.height
Arrow( : 0.0,
position: 100.0, direction: note.direction,
), );
Arrow( }),
position: 200.0,
),
Positioned( Positioned(
top: 50, top: 50,
width: MediaQuery.of(context).size.width, width: MediaQuery.of(context).size.width,
@ -171,15 +207,16 @@ class _LevelState extends State<Level> {
class Arrow extends StatelessWidget { class Arrow extends StatelessWidget {
final double position; final double position;
final ArrowDirection direction;
const Arrow({super.key, required this.position}); const Arrow({super.key, required this.position, required this.direction});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Positioned( return Positioned(
left: MediaQuery.of(context).size.width / 2 - 25, // Center the arrow left: MediaQuery.of(context).size.width / 2 - 50, // Center the arrow
top: position, bottom: position + 50,
child: Icon(size: 100, Icons.arrow_forward), child: Icon(size: 100, color: Colors.redAccent.shade400, direction.icon),
); );
} }
} }

View file

@ -1,3 +1,4 @@
import 'dart:ffi';
import 'dart:io'; import 'dart:io';
enum Difficulty { Beginner, Easy, Medium, Hard, Challenge, Edit } enum Difficulty { Beginner, Easy, Medium, Hard, Challenge, Edit }
@ -42,6 +43,9 @@ class Simfile {
Chart? chartSimplest; Chart? chartSimplest;
Map<double, double> bpms = {};
double offset = 0;
Simfile(this.path); Simfile(this.path);
void load() { void load() {
@ -55,6 +59,22 @@ class Simfile {
List<String> keys = List<String> keys =
fieldData[1]!.split(':').map((key) => key.trim()).toList(); fieldData[1]!.split(':').map((key) => key.trim()).toList();
String value = fieldData[2]!; 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") { if (keys[0] != "NOTES") {
tags[keys[0]] = value; tags[keys[0]] = value;
continue; continue;