Skip to content

Commit 545aa77

Browse files
feat: implemented the sound meter (#2739)
Co-authored-by: Anashuman Singh <anash.cheema@gmail.com>
1 parent 52663df commit 545aa77

7 files changed

Lines changed: 460 additions & 3 deletions

File tree

lib/constants.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,7 @@ String lx = 'Lx';
166166
String maxScaleError = 'Max Scale';
167167
String lightSensorError = 'Light sensor error:';
168168
String lightSensorInitialError = 'Failed to initialize light sensor:';
169+
String soundMeterError = 'Sound sensor error:';
170+
String soundMeterInitialError = 'Sound sensor initialization error:';
171+
String db = 'dB';
172+
String soundMeterTitle = 'Sound Meter';

lib/main.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'package:pslab/view/settings_screen.dart';
1313
import 'package:pslab/view/about_us_screen.dart';
1414
import 'package:pslab/view/software_licenses_screen.dart';
1515
import 'package:pslab/others/theme.dart';
16+
import 'package:pslab/view/soundmeter_screen.dart';
1617
import 'constants.dart';
1718

1819
void main() {
@@ -33,6 +34,7 @@ void main() {
3334

3435
class MyApp extends StatelessWidget {
3536
const MyApp({super.key});
37+
3638
@override
3739
Widget build(BuildContext context) {
3840
_preCacheImages(context);
@@ -53,6 +55,7 @@ class MyApp extends StatelessWidget {
5355
'/accelerometer': (context) => const AccelerometerScreen(),
5456
'/gyroscope': (context) => const GyroscopeScreen(),
5557
'/luxmeter': (context) => const LuxMeterScreen(),
58+
'/soundmeter': (context) => const SoundMeterScreen(),
5659
},
5760
);
5861
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import 'dart:async';
2+
import 'dart:math';
3+
import 'package:fl_chart/fl_chart.dart';
4+
import 'package:pslab/others/logger_service.dart';
5+
import 'package:flutter_audio_capture/flutter_audio_capture.dart';
6+
import 'package:flutter/foundation.dart';
7+
import 'package:pslab/constants.dart';
8+
9+
class SoundMeterStateProvider extends ChangeNotifier {
10+
double _currentDb = 0.0;
11+
Timer? _timeTimer;
12+
final List<double> _dbData = [];
13+
final List<double> _timeData = [];
14+
final List<FlSpot> dbChartData = [];
15+
FlutterAudioCapture? _audioCapture;
16+
double _startTime = 0;
17+
double _currentTime = 0;
18+
final int _maxLength = 50;
19+
double _dbMin = 0;
20+
double _dbMax = 0;
21+
double _dbSum = 0;
22+
int _dataCount = 0;
23+
24+
void initializeSensors() async {
25+
try {
26+
_audioCapture = FlutterAudioCapture();
27+
28+
await _audioCapture!.init();
29+
30+
_startTime = DateTime.now().millisecondsSinceEpoch / 1000.0;
31+
_timeTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
32+
_currentTime =
33+
(DateTime.now().millisecondsSinceEpoch / 1000.0) - _startTime;
34+
_updateData();
35+
notifyListeners();
36+
});
37+
38+
await _audioCapture!.start(
39+
(Float32List audioData) {
40+
_currentDb = _calculateDecibels(audioData);
41+
notifyListeners();
42+
},
43+
(error) {
44+
logger.e(
45+
"$soundMeterError $error",
46+
);
47+
},
48+
sampleRate: 44100,
49+
bufferSize: 4096,
50+
);
51+
} catch (e) {
52+
logger.e("$soundMeterInitialError $e");
53+
}
54+
}
55+
56+
double _calculateDecibels(Float32List audioData) {
57+
if (audioData.isEmpty) return 0.0;
58+
59+
double sum = 0;
60+
for (double sample in audioData) {
61+
sum += sample * sample;
62+
}
63+
double rms = sqrt(sum / audioData.length);
64+
65+
if (rms <= 0) return 0.0;
66+
67+
double dbFS = 20 * log(rms) / ln10;
68+
69+
double dbSPL = dbFS + 94;
70+
71+
return dbSPL.clamp(20.0, 120.0);
72+
}
73+
74+
void disposeSensors() {
75+
_audioCapture?.stop();
76+
_timeTimer?.cancel();
77+
}
78+
79+
@override
80+
void dispose() {
81+
disposeSensors();
82+
super.dispose();
83+
}
84+
85+
void _updateData() {
86+
final db = _currentDb;
87+
final time = _currentTime;
88+
_dbData.add(db);
89+
_timeData.add(time);
90+
_dbSum += db;
91+
_dataCount++;
92+
if (_dbData.length > _maxLength) {
93+
final removedValue = _dbData.removeAt(0);
94+
_timeData.removeAt(0);
95+
_dbSum -= removedValue;
96+
_dataCount--;
97+
}
98+
if (_dbData.isNotEmpty) {
99+
_dbMin = _dbData.reduce(min);
100+
_dbMax = _dbData.reduce(max);
101+
}
102+
dbChartData.clear();
103+
for (int i = 0; i < _dbData.length; i++) {
104+
dbChartData.add(FlSpot(_timeData[i], _dbData[i]));
105+
}
106+
notifyListeners();
107+
}
108+
109+
double getCurrentDb() => _currentDb;
110+
double getMinDb() => _dbMin;
111+
double getMaxDb() => _dbMax;
112+
double getAverageDb() => _dataCount > 0 ? _dbSum / _dataCount : 0.0;
113+
List<FlSpot> getDbChartData() => dbChartData;
114+
int getDataLength() => dbChartData.length;
115+
double getCurrentTime() => _currentTime;
116+
double getMaxTime() => _timeData.isNotEmpty ? _timeData.last : 0;
117+
double getMinTime() => _timeData.isNotEmpty ? _timeData.first : 0;
118+
double getTimeInterval() {
119+
if (_currentTime <= 10) return 2;
120+
if (_currentTime <= 30) return 5;
121+
return 10;
122+
}
123+
}

lib/view/instruments_screen.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,18 @@ class _InstrumentsScreenState extends State<InstrumentsScreen> {
6161
);
6262
}
6363
break;
64+
case 15:
65+
if (Navigator.canPop(context) &&
66+
ModalRoute.of(context)?.settings.name == '/soundmeter') {
67+
Navigator.popUntil(context, ModalRoute.withName('/soundmeter'));
68+
} else {
69+
Navigator.pushNamedAndRemoveUntil(
70+
context,
71+
'/soundmeter',
72+
(route) => route.isFirst,
73+
);
74+
}
75+
break;
6476
default:
6577
break;
6678
}

0 commit comments

Comments
 (0)