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();
 | 
					  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),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue