summaryrefslogtreecommitdiff
path: root/lib/utils
diff options
context:
space:
mode:
Diffstat (limited to 'lib/utils')
-rw-r--r--lib/utils/esense_input.dart166
-rw-r--r--lib/utils/simfile.dart151
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);
+ }
+ }
+}