Make voice grid visibly animate
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m6s
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m6s
This commit is contained in:
@@ -737,6 +737,7 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
|||||||
|
|
||||||
Future<List<PlaceRecommendation>>? _nearbyPlacesFuture;
|
Future<List<PlaceRecommendation>>? _nearbyPlacesFuture;
|
||||||
StreamSubscription<Amplitude>? _amplitudeSub;
|
StreamSubscription<Amplitude>? _amplitudeSub;
|
||||||
|
Timer? _visualTimer;
|
||||||
var _step = 0;
|
var _step = 0;
|
||||||
var _informationUnits = 0.0;
|
var _informationUnits = 0.0;
|
||||||
var _recording = false;
|
var _recording = false;
|
||||||
@@ -745,7 +746,9 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
|||||||
var _noiseDb = -72.0;
|
var _noiseDb = -72.0;
|
||||||
var _voicePeakDb = -34.0;
|
var _voicePeakDb = -34.0;
|
||||||
var _liveLevel = 0.0;
|
var _liveLevel = 0.0;
|
||||||
|
var _visualPhase = 0.0;
|
||||||
DateTime? _lastInformationAt;
|
DateTime? _lastInformationAt;
|
||||||
|
DateTime? _lastAmplitudeAt;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -755,6 +758,7 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_amplitudeSub?.cancel();
|
_amplitudeSub?.cancel();
|
||||||
|
_visualTimer?.cancel();
|
||||||
_waveController.dispose();
|
_waveController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@@ -784,7 +788,9 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
|||||||
await _waveController.startRecording();
|
await _waveController.startRecording();
|
||||||
await _amplitudeSub?.cancel();
|
await _amplitudeSub?.cancel();
|
||||||
_lastInformationAt = DateTime.now();
|
_lastInformationAt = DateTime.now();
|
||||||
|
_lastAmplitudeAt = null;
|
||||||
_amplitudeSub = _waveController.amplitudeStream.listen(_handleAmplitude);
|
_amplitudeSub = _waveController.amplitudeStream.listen(_handleAmplitude);
|
||||||
|
_startVisualTick();
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_micAllowed = true;
|
_micAllowed = true;
|
||||||
@@ -798,8 +804,11 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
|||||||
Future<void> _stopRecording() async {
|
Future<void> _stopRecording() async {
|
||||||
await _amplitudeSub?.cancel();
|
await _amplitudeSub?.cancel();
|
||||||
_amplitudeSub = null;
|
_amplitudeSub = null;
|
||||||
|
_visualTimer?.cancel();
|
||||||
|
_visualTimer = null;
|
||||||
await _waveController.stopRecording();
|
await _waveController.stopRecording();
|
||||||
_lastInformationAt = null;
|
_lastInformationAt = null;
|
||||||
|
_lastAmplitudeAt = null;
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -813,12 +822,16 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
|||||||
final currentDb = amplitude.current;
|
final currentDb = amplitude.current;
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final level = _normalizeDbLevel(currentDb);
|
final level = _normalizeDbLevel(currentDb);
|
||||||
final informationDelta = _consumeInformationDelta(level, now);
|
final informationLevel = currentDb > -64
|
||||||
|
? math.max(0.22, level)
|
||||||
|
: level * 0.35;
|
||||||
|
final informationDelta = _consumeInformationDelta(informationLevel, now);
|
||||||
|
_lastAmplitudeAt = now;
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_voiceSamples
|
_voiceSamples
|
||||||
..removeAt(0)
|
..removeAt(0)
|
||||||
..add(level);
|
..add(math.max(0.04, level));
|
||||||
_liveLevel = _smoothLevel(_liveLevel, level);
|
_liveLevel = _smoothLevel(_liveLevel, level);
|
||||||
_informationUnits = math.min(
|
_informationUnits = math.min(
|
||||||
_minimumInformationUnits,
|
_minimumInformationUnits,
|
||||||
@@ -830,6 +843,30 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
|||||||
.setReviewDuration(_waveController.timeElapsed);
|
.setReviewDuration(_waveController.timeElapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _startVisualTick() {
|
||||||
|
_visualTimer?.cancel();
|
||||||
|
_visualTimer = Timer.periodic(const Duration(milliseconds: 70), (_) {
|
||||||
|
if (!mounted || !_recording) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_visualPhase += 0.34;
|
||||||
|
final hasFreshAmplitude =
|
||||||
|
_lastAmplitudeAt != null &&
|
||||||
|
DateTime.now().difference(_lastAmplitudeAt!).inMilliseconds < 160;
|
||||||
|
final baseLevel = hasFreshAmplitude ? _liveLevel : 0.10;
|
||||||
|
final wave =
|
||||||
|
(math.sin(_visualPhase) * 0.5 + 0.5) * (0.16 + baseLevel * 0.34);
|
||||||
|
final visualLevel = (0.06 + baseLevel * 0.70 + wave).clamp(0.0, 1.0);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_voiceSamples
|
||||||
|
..removeAt(0)
|
||||||
|
..add(visualLevel);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
double _normalizeDbLevel(double currentDb) {
|
double _normalizeDbLevel(double currentDb) {
|
||||||
final db = currentDb.clamp(-160.0, 0.0);
|
final db = currentDb.clamp(-160.0, 0.0);
|
||||||
if (db < _noiseDb) {
|
if (db < _noiseDb) {
|
||||||
@@ -1095,7 +1132,7 @@ class _VoiceStep extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final canFinish = canContinue && !isRecording;
|
final canFinish = canContinue;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -1392,7 +1429,8 @@ class _VoiceInformationPainter extends CustomPainter {
|
|||||||
final signal = samples.isEmpty
|
final signal = samples.isEmpty
|
||||||
? 0.0
|
? 0.0
|
||||||
: samples[sampleIndex].clamp(0.0, 1.0);
|
: samples[sampleIndex].clamp(0.0, 1.0);
|
||||||
final filled = _cellOrder(cellIndex, totalCells) < activeCells;
|
final fillOrder = (rows - 1 - row) * columns + column;
|
||||||
|
final filled = fillOrder < activeCells;
|
||||||
final x = startX + column * (cellSize + gap);
|
final x = startX + column * (cellSize + gap);
|
||||||
final y = startY + row * (cellSize + gap);
|
final y = startY + row * (cellSize + gap);
|
||||||
final rect = RRect.fromRectAndRadius(
|
final rect = RRect.fromRectAndRadius(
|
||||||
@@ -1442,8 +1480,6 @@ class _VoiceInformationPainter extends CustomPainter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int _cellOrder(int index, int total) => ((index + 11) * 73) % total;
|
|
||||||
|
|
||||||
double _hashUnit(int index) {
|
double _hashUnit(int index) {
|
||||||
final value = math.sin(index * 12.9898 + 78.233) * 43758.5453;
|
final value = math.sin(index * 12.9898 + 78.233) * 43758.5453;
|
||||||
return value - value.floorToDouble();
|
return value - value.floorToDouble();
|
||||||
|
|||||||
Reference in New Issue
Block a user