diff options
Diffstat (limited to 'lib/utils')
| -rw-r--r-- | lib/utils/esense_input.dart | 166 | ||||
| -rw-r--r-- | lib/utils/simfile.dart | 151 | 
2 files changed, 317 insertions, 0 deletions
| diff --git a/lib/utils/esense_input.dart b/lib/utils/esense_input.dart new file mode 100644 index 0000000..d909c0d --- /dev/null +++ b/lib/utils/esense_input.dart @@ -0,0 +1,166 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:esense_flutter/esense.dart'; +import 'package:flutter/material.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:sense_the_rhythm/models/arrow_direction.dart'; +import 'package:sense_the_rhythm/models/input_direction.dart'; + +class ESenseInput { +  static final instance = ESenseInput._(); + +  ESenseManager eSenseManager = ESenseManager('unknown'); +  ValueNotifier<String> deviceStatus = ValueNotifier(''); +  StreamSubscription? subscription; + +  String eSenseDeviceName = ''; +  bool connected = false; +  bool sampling = false; + +  int sampleRate = 20; + +  InputDirection inputDirection = InputDirection(); +  int x = 0; +  int y = 0; +  int z = 0; + +  ESenseInput._() { +    _listenToESense(); +  } + +  Future<void> _askForPermissions() async { +    if (!Platform.isAndroid && !Platform.isIOS) return; +    if (!(await Permission.bluetoothScan.request().isGranted && +        await Permission.bluetoothConnect.request().isGranted && +        await Permission.bluetooth.request().isGranted)) { +      print( +          'WARNING - no permission to use Bluetooth granted. Cannot access eSense device.'); +    } +    // for some strange reason, Android requires permission to location for Bluetooth to work.....? +    if (Platform.isAndroid) { +      if (!(await Permission.locationWhenInUse.request().isGranted)) { +        print( +            'WARNING - no permission to access location granted. Cannot access eSense device.'); +      } +    } +  } + +  void _listenToESense() { +    // if you want to get the connection events when connecting, +    // set up the listener BEFORE connecting... +    eSenseManager.connectionEvents.listen((event) { +      print('CONNECTION event: $event'); + +      // when we're connected to the eSense device, we can start listening to events from it +      // if (event.type == ConnectionType.connected) _listenToESenseEvents(); + +      connected = false; +      switch (event.type) { +        case ConnectionType.connected: +          deviceStatus.value = 'connected'; +          connected = true; +          _startListenToSensorEvents(); +          break; +        case ConnectionType.unknown: +          deviceStatus.value = 'unknown'; +          break; +        case ConnectionType.disconnected: +          deviceStatus.value = 'disconnected'; +          sampling = false; +          _pauseListenToSensorEvents(); +          break; +        case ConnectionType.device_found: +          deviceStatus.value = 'device_found'; +          break; +        case ConnectionType.device_not_found: +          deviceStatus.value = 'device_not_found'; +          break; +      } +    }); +  } + +  Stream<ButtonEventChanged> buttonEvents() { +    return eSenseManager.eSenseEvents +        .where((event) => event.runtimeType == ButtonEventChanged) +        .cast(); +  } + +  void _startListenToSensorEvents() async { +    // // any changes to the sampling frequency must be done BEFORE listening to sensor events +    print('setting sampling frequency...'); +    bool successs = await eSenseManager.setSamplingRate(sampleRate); +    if (successs) { +      print('setSamplingRate success'); +    } else { +      print('setSamplingRate fail'); +    } + +    // subscribe to sensor event from the eSense device +    subscription = eSenseManager.sensorEvents.listen((event) { +      // print('SENSOR event: $event'); +      if (event.gyro != null) { +        _parseGyroData(event.gyro!); +      } +    }); +    sampling = true; +  } + +  void _pauseListenToSensorEvents() async { +    subscription?.cancel(); +    sampling = false; +  } + +  void _parseGyroData(List<int> data) { +    // Float value in deg/s = Gyro value / Gyro scale factor +    // The default configuration is +- 500deg/s for the gyroscope. +    x = (x + (15 * data[0] ~/ (500 * sampleRate))) % 360; +    y = (y + (15 * data[1] ~/ (500 * sampleRate))) % 360; +    z = (z + (15 * data[2] ~/ (500 * sampleRate))) % 360; +    print('$x, $y, $z'); +    // print('${(z.toDouble() / 500.0 * (1.0 / sampleRate.toDouble())) * 7.5}'); +    // print('${z.toDouble() / 500.0 * (1.0 / 10.0)}'); +  } + +  void resetAngles() { +    inputDirection.reset(); +    x = 0; +    y = 0; +    z = 0; +  } + +  InputDirection getInputDirection(ArrowDirection expect) { +    inputDirection.up = z > 270 && z < 340; +    inputDirection.down = z > 40 && z < 180; +    inputDirection.left = y > 40 && y < 180; +    inputDirection.right = y > 270 && y < 340; + +    if (expect == ArrowDirection.up && inputDirection.up || +        expect == ArrowDirection.down && inputDirection.down) { +      y = 0; +      print("ehit"); +    } +    if (expect == ArrowDirection.left && inputDirection.left || +        expect == ArrowDirection.right && inputDirection.right) { +      z = 0; +      print("ehit"); +    } + +    return inputDirection; +  } + +  Future<void> connectToESense(String deviceName) async { +    if (!connected) { +      await _askForPermissions(); +      print('Trying to connect to eSense device namend \'$deviceName\''); +      eSenseDeviceName = deviceName; +      eSenseManager.deviceName = deviceName; +      connected = await eSenseManager.connect(); +      print( +          'Trying to connect to eSense device namend \'${eSenseManager.deviceName}\''); + +      deviceStatus.value = connected ? 'connecting...' : 'connection failed'; +      print(deviceStatus.value); +    } +  } +} diff --git a/lib/utils/simfile.dart b/lib/utils/simfile.dart new file mode 100644 index 0000000..0af734f --- /dev/null +++ b/lib/utils/simfile.dart @@ -0,0 +1,151 @@ +import 'dart:io'; + +enum Difficulty { Beginner, Easy, Medium, Hard, Challenge, Edit } + +// These are the standard note values: +// +//     0 – No note +//     1 – Normal note +//     2 – Hold head +//     3 – Hold/Roll tail +//     4 – Roll head +//     M – Mine (or other negative note) +// +// Later versions of StepMania accept other note values which may not work in older versions: +// +//     K – Automatic keysound +//     L – Lift note +//     F – Fake note + +RegExp noteTypes = RegExp(r'^([012345MKLF]+)\s*([,;])?'); + +class Chart { +  String? chartType; +  // Description/author +  String? author; +  // Difficulty (one of Beginner, Easy, Medium, Hard, Challenge, Edit) +  Difficulty? difficulty; +  // Numerical meter +  int? numericalMeter; +  // Groove radar values, generated by the program +  String? radarValues; + +  List<List<String>>? measures; + +  Map<double, String> beats = {}; +} + +class Simfile { +  String directoryPath; +  String? simfilePath; +  String? audioPath; +  String? bannerPath; +  String? lines; + +  // tags of simfile +  Map<String, String> tags = {}; + +  Chart? chartSimplest; + +  Map<double, double> bpms = {}; +  double offset = 0; + +  Simfile(this.directoryPath); + +  void _parseChart({required List<String> keys, required String value}) { +    Chart chart = Chart(); +    chart.chartType = keys[1]; +    chart.author = keys[2]; +    chart.difficulty = Difficulty.values.byName(keys[3]); +    chart.numericalMeter = int.parse(keys[4]); +    chart.radarValues = keys[5]; + +    if (chartSimplest == null || +        (chart.difficulty!.index <= chartSimplest!.difficulty!.index && +            chart.numericalMeter! <= chartSimplest!.numericalMeter!)) { +      List<List<String>> measures = []; +      for (final measureRaw in value.split(',')) { +        List<String> measure = []; +        for (final noteRaw in measureRaw.split('\n')) { +          String note = noteRaw.trim(); +          if (noteTypes.hasMatch(note)) { +            measure.add(note); +          } +        } +        measures.add(measure); +      } + +      double bpm = bpms.entries.first.value; + +      for (final (measureIndex, measure) in measures.indexed) { +        for (final (noteIndex, noteData) in measure.indexed) { +          double beat = measureIndex * 4.0 + +              (noteIndex.toDouble() / measure.length) * 4.0; +          double minutesPerBeat = 1.0 / bpm; +          double offsetMinutes = offset / 60.0; +          chart.beats[beat * minutesPerBeat + offsetMinutes] = noteData; +        } +      } + +      chart.measures = measures; +      chartSimplest = chart; +    } +  } + +  void _parseTag(RegExpMatch fieldData) { +    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; +      return; +    } +    _parseChart(keys: keys, value: value); +  } + +  void load() { +    simfilePath = Directory(directoryPath) +        .listSync() +        .firstWhere((entity) => entity.path.endsWith('.sm'), +            orElse: () => File('')) +        .path; + +    audioPath = Directory(directoryPath) +        .listSync() +        .firstWhere((entity) => entity.path.endsWith('.ogg'), +            orElse: () => File('')) +        .path; + +    bannerPath = Directory(directoryPath) +        .listSync() +        .firstWhere((file) => file.path.toLowerCase().endsWith('banner.png'), +            orElse: () => File('')) +        .path; + +    lines = File(simfilePath!).readAsStringSync(); + +    RegExp commentsRegExp = RegExp(r'//.*$'); +    lines = lines?.replaceAll(commentsRegExp, ''); +    RegExp fieldDataRegExp = RegExp(r'#([^;]+):([^;]*);'); + +    for (final fieldData in fieldDataRegExp.allMatches(lines!)) { +      _parseTag(fieldData); +    } +  } +} | 
