mirror of
https://github.com/namidaco/namida.git
synced 2026-03-13 08:12:29 +08:00
176 lines
6.1 KiB
Dart
176 lines
6.1 KiB
Dart
import 'package:namida/controller/platform/waveform_extractor/waveform_extractor.dart';
|
|
import 'package:namida/controller/player_controller.dart';
|
|
import 'package:namida/controller/settings_controller.dart';
|
|
import 'package:namida/controller/vibrator_controller.dart';
|
|
import 'package:namida/core/constants.dart';
|
|
import 'package:namida/core/extensions.dart';
|
|
import 'package:namida/core/utils.dart';
|
|
|
|
class WaveformController {
|
|
static WaveformController get inst => _instance;
|
|
static final WaveformController _instance = WaveformController._internal();
|
|
WaveformController._internal();
|
|
|
|
int get _defaultUserBarsCount => settings.waveformTotalBars.value;
|
|
|
|
RxBaseCore<bool> get isWaveformUIEnabled => _isWaveformUIEnabled;
|
|
final _isWaveformUIEnabled = false.obso;
|
|
|
|
late final currentWaveformUIRx = List<double>.filled(_defaultUserBarsCount, -1, growable: false).obs;
|
|
|
|
List<double> _currentWaveform = [];
|
|
|
|
var _currentScaleLookup = <double>[];
|
|
int _currentScaleMaxIndex = -1;
|
|
|
|
bool get isDummy => _currentWaveform.isEmpty;
|
|
|
|
void resetWaveform() {
|
|
_currentWaveform = [];
|
|
_currentScaleLookup = [];
|
|
_currentScaleMaxIndex = -1;
|
|
_isWaveformUIEnabled.value = false;
|
|
}
|
|
|
|
/// Extracts waveform data from a given track, or immediately read from .wave file if exists, then assigns wavedata to [_currentWaveform].
|
|
Future<void> generateWaveform({required String path, required Duration duration, required bool Function(String path) stillPlaying}) async {
|
|
final samplePerSecond = _waveformExtractor.getSampleRateFromDuration(
|
|
audioDuration: duration,
|
|
maxSampleRate: 400,
|
|
scaleFactor: 0.4,
|
|
);
|
|
|
|
List<num> waveformData = [];
|
|
await Future.wait([
|
|
_waveformExtractor.extractWaveformData(path, samplesPerSecond: samplePerSecond).catchError((_) => <num>[]).then((value) async {
|
|
if (value.isNotEmpty) {
|
|
waveformData = value;
|
|
} else if (stillPlaying(path)) {
|
|
waveformData = await _waveformExtractor.extractWaveformData(path).catchError((_) => <num>[]); // re-extracting without samples (out of boundaries error)
|
|
}
|
|
}),
|
|
Future.delayed(const Duration(milliseconds: 800)),
|
|
]);
|
|
|
|
if (waveformData.isNotEmpty && stillPlaying(path)) {
|
|
// ----- Updating [_currentWaveform]
|
|
const maxWaveformCount = 2000;
|
|
final numberOfScales = duration.inMilliseconds ~/ _positionDividor;
|
|
final downscaledLists = _downscaledWaveformLists(
|
|
targetSizes: [maxWaveformCount, numberOfScales],
|
|
original: waveformData,
|
|
);
|
|
|
|
_currentWaveform = downscaledLists[maxWaveformCount] ?? [];
|
|
_currentScaleLookup = downscaledLists[numberOfScales] ?? [];
|
|
_currentScaleMaxIndex = _currentScaleLookup.length - 1;
|
|
|
|
calculateUIWaveform();
|
|
}
|
|
}
|
|
|
|
void calculateUIWaveform() async {
|
|
if (_currentWaveform.isEmpty) return;
|
|
|
|
final waveform = _getCalculatedUIWaveform(
|
|
targetSize: _defaultUserBarsCount,
|
|
original: _currentWaveform,
|
|
);
|
|
currentWaveformUIRx.value = waveform;
|
|
_isWaveformUIEnabled.value = true;
|
|
}
|
|
|
|
static List<double> _getCalculatedUIWaveform({required List<double> original, required int targetSize}) {
|
|
const maxClamping = 64.0;
|
|
final clamping = original.isEmpty ? null : maxClamping;
|
|
final downscaled = original.changeListSize(
|
|
targetSize: targetSize,
|
|
multiplier: 0.9,
|
|
clampToMax: clamping,
|
|
enforceClampToMax: (minValue, maxValue) => false,
|
|
);
|
|
return downscaled;
|
|
}
|
|
|
|
static Map<num, List<double>> _downscaledWaveformLists({required List<num> original, required List<int> targetSizes}) {
|
|
final newLists = <num, List<double>>{};
|
|
const maxClamping = 64.0;
|
|
targetSizes.loop((targetSize) {
|
|
newLists[targetSize] = original.changeListSize(
|
|
targetSize: targetSize,
|
|
clampToMax: maxClamping,
|
|
enforceClampToMax: (minValue, maxValue) {
|
|
// -- checking if max value is greater than `maxClamping`;
|
|
// -- since clamping tries to normalize among all lists variations
|
|
return maxValue > maxClamping * 2.0;
|
|
},
|
|
);
|
|
});
|
|
return newLists;
|
|
}
|
|
|
|
static const _positionDividor = 50;
|
|
static const _positionDividorWithOffset = isKuru ? _positionDividor - 3.58 : 50;
|
|
|
|
double getCurrentAnimatingScale(int positionInMs) {
|
|
return _getCurrentAnimatingScaleGeneral(positionInMs, settings.animatingThumbnailIntensity.value);
|
|
}
|
|
|
|
double getCurrentAnimatingScaleLyrics(int positionInMs) {
|
|
return _getCurrentAnimatingScaleGeneral(positionInMs, settings.animatingThumbnailIntensityLyrics.value);
|
|
}
|
|
|
|
double getCurrentAnimatingScaleMinimized(int positionInMs) {
|
|
return _getCurrentAnimatingScaleGeneral(positionInMs, settings.animatingThumbnailIntensityMinimized.value);
|
|
}
|
|
|
|
double _getCurrentAnimatingScaleGeneral(int positionInMs, int intensity) {
|
|
if (intensity > 0) {
|
|
final posInMap = positionInMs ~/ _positionDividorWithOffset;
|
|
final dynamicScale = posInMap > _currentScaleMaxIndex ? _defaultMinimumScale : _currentScaleLookup[posInMap];
|
|
final finalScale = dynamicScale * intensity * 0.00005;
|
|
if (finalScale.isNaN || finalScale > 0.35) return _defaultMinimumScale;
|
|
|
|
if (settings.extra.mediaWaveHaptic == true) {
|
|
_vibrateHaptic(finalScale);
|
|
}
|
|
|
|
return finalScale;
|
|
}
|
|
|
|
return _defaultMinimumScale;
|
|
}
|
|
|
|
void _vibrateHaptic(double scale) {
|
|
if (Player.inst.isPlaying.value == false) return;
|
|
if (scale > _defaultMinimumScale) {
|
|
final haptic = VibratorController.interfaceHapticIgnoreSettings;
|
|
if (scale < 0.03) {
|
|
haptic.light();
|
|
haptic.light();
|
|
haptic.light();
|
|
} else if (scale < 0.06) {
|
|
haptic.light();
|
|
haptic.medium();
|
|
haptic.medium();
|
|
haptic.light();
|
|
} else if (scale < 0.10) {
|
|
haptic.medium();
|
|
haptic.high();
|
|
haptic.high();
|
|
haptic.high();
|
|
haptic.medium();
|
|
} else {
|
|
haptic.high();
|
|
haptic.high();
|
|
haptic.high();
|
|
haptic.high();
|
|
}
|
|
}
|
|
}
|
|
|
|
static const _defaultMinimumScale = 0.01;
|
|
|
|
final _waveformExtractor = WaveformExtractor.platform()..init();
|
|
}
|