feat: display beats as arrows with correct time offset
This commit is contained in:
parent
9eb8a29382
commit
e9ba842e21
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue