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