Created
May 16, 2024 16:28
-
-
Save galaktyk/91fc1077661e76da7515151f307580df to your computer and use it in GitHub Desktop.
viz
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:ffi'; | |
import 'dart:async'; | |
import 'dart:math' as math; | |
import 'dart:typed_data'; | |
import 'package:flutter/material.dart'; | |
import 'package:english_words/english_words.dart'; | |
import "dart:math"; | |
import 'dart:async'; | |
import 'package:flutter/material.dart'; | |
import 'package:lottie/lottie.dart'; | |
import 'package:record/record.dart'; | |
import 'package:mic_stream/mic_stream.dart'; | |
String fullSubtitle = "hello"; | |
double mapValue( | |
double x, double inMin, double inMax, double outMin, double outMax) { | |
return (x - inMin) * (outMax - outMin) / (inMax - inMin) + outMin; | |
} | |
void main() { | |
runApp(const MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
const MyApp({super.key}); | |
// This widget is the root of your application. | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Flutter Demo', | |
theme: ThemeData( | |
brightness: Brightness.light, | |
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), | |
useMaterial3: true, | |
), | |
darkTheme: ThemeData( | |
brightness: Brightness.dark, | |
/* dark theme settings */ | |
), | |
themeMode: ThemeMode.dark, | |
home: const MyHomePage(title: 'Flutter Demo Home Page'), | |
); | |
} | |
} | |
class MyHomePage extends StatefulWidget { | |
const MyHomePage({super.key, required this.title}); | |
final String title; | |
@override | |
State<MyHomePage> createState() => _MyHomePageState(); | |
} | |
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin { | |
int _counter = 0; | |
final _record = AudioRecorder(); | |
double minDecibels = -15; | |
double maxDecibels = -4.6; | |
double currentDecibels = -90.0; | |
double currentLoudness = 0.0; | |
double _ema_loudness = 0.0; | |
double _ema_N = 5; | |
int randomAnimChunk = 0; | |
double frameMax = 300; | |
late List<List<double>> animTimestampArray; | |
late final AnimationController _controller; | |
final random = Random(); | |
bool shouldChangeChunk = false; | |
@override | |
void initState() { | |
super.initState(); | |
_controller = AnimationController(vsync: this); | |
_controller.addListener(() { | |
//print(_controller.value); | |
}); | |
animTimestampArray = List.generate(3, (_) => List.filled(2, 0.0)); | |
animTimestampArray[0][0] = 0; | |
animTimestampArray[0][1] = 29; | |
animTimestampArray[1][0] = 44; | |
animTimestampArray[1][1] = 74; | |
animTimestampArray[2][0] = 161; | |
animTimestampArray[2][1] = 179; | |
} | |
Future<void> _startRecord() async { | |
print("_startRecord"); | |
if (!await _record.hasPermission()) { | |
print("don't have permission"); | |
return; | |
} | |
final stream = await _record.startStream( | |
const RecordConfig( | |
encoder: AudioEncoder.pcm16bits, | |
), | |
); | |
stream.listen( | |
(event) { | |
currentLoudness = calculateLoudness(event); | |
//print("currentLoudness: " + currentLoudness.toString()); | |
//_controller.value = currentLoudness * 0.27 / 1.0; | |
if (currentLoudness > 0.1) { | |
double rangeStart = animTimestampArray[randomAnimChunk][0]; | |
double rangeEnd = animTimestampArray[randomAnimChunk][1]; | |
print("randomAnimChunk: " + randomAnimChunk.toString()); | |
double animTargetFrame = | |
mapValue(currentLoudness, 0, 1, rangeStart, rangeEnd); | |
double animTargetFloat = mapValue(animTargetFrame, 0, frameMax, 0, 1); | |
_controller.animateTo(animTargetFloat); | |
setState(() { | |
shouldChangeChunk = true; | |
}); | |
} else { | |
if (shouldChangeChunk) { | |
setState(() { | |
randomAnimChunk = random.nextInt(3); | |
shouldChangeChunk = false; | |
print("change anim chunk to :" + randomAnimChunk.toString()); | |
// Jump to start frame of new anim section. | |
_controller.value = mapValue( | |
animTimestampArray[randomAnimChunk][0], 0, frameMax, 0, 1); | |
}); | |
} | |
} | |
}, | |
onDone: () {}, | |
); | |
} | |
void startSubtibleFetchingLoop() { | |
void loop() { | |
// Generate a random delay between 1 and 3 seconds | |
final randomSeconds = 0.2 + random.nextDouble(); | |
final randomDuration = | |
Duration(milliseconds: (randomSeconds * 1000).round()); | |
setState(() { | |
fullSubtitle += " " + all[Random().nextInt(all.length)]; | |
}); | |
// Print a message with the current time | |
//print(fullSubtitle); | |
// Schedule the next iteration of the loop after the random delay | |
Future.delayed(randomDuration, loop); | |
} | |
// Start the loop for the first time | |
loop(); | |
} | |
double calculateDecibels(Uint8List event) { | |
var samples = Int16List.view(event.buffer); | |
// Calculate RMS | |
double sumOfSquares = 0.0; | |
for (var sample in samples) { | |
sumOfSquares += math.pow(sample, 2).toDouble(); | |
} | |
double rms = math.sqrt(sumOfSquares / samples.length); | |
rms = math.max(rms, 1.0); | |
double db = 20 * math.log(rms / 32768.0) / math.ln10; | |
db = math.max(db, -80.0); | |
return db; | |
} | |
double calculateLoudness(Uint8List event) { | |
final db = calculateDecibels(event); | |
//print(db); | |
double clampedDbfs = db.clamp(minDecibels, maxDecibels); | |
double normalized = | |
(clampedDbfs - minDecibels) / (maxDecibels - minDecibels); | |
double loudness = math.pow(normalized, 16).toDouble(); | |
double k = 2 / (_ema_N + 1); | |
setState(() { | |
_ema_loudness = loudness * k + _ema_loudness * (1 - k); | |
}); | |
return _ema_loudness; | |
} | |
void _incrementCounter() { | |
//startSubtibleFetchingLoop(); | |
_startRecord(); | |
_controller.value = 0; | |
//_controller.animateTo(1.0); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
// TRY THIS: Try changing the color here to a specific color (to | |
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar | |
// change color while the other colors stay the same. | |
backgroundColor: Theme.of(context).colorScheme.inversePrimary, | |
// Here we take the value from the MyHomePage object that was created by | |
// the App.build method, and use it to set our appbar title. | |
title: Text(widget.title), | |
), | |
body: Center( | |
// Center is a layout widget. It takes a single child and positions it | |
// in the middle of the parent. | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: <Widget>[ | |
Text( | |
fullSubtitle, | |
style: TextStyle(fontSize: 20), | |
), | |
Lottie.asset( | |
'assets/Circular Visualizer Podcast 1.json', | |
controller: _controller, | |
onLoaded: (composition) { | |
// Configure the AnimationController with the duration of the | |
// Lottie file and start the animation. | |
_controller | |
..duration = composition.duration | |
..forward(); | |
}, | |
), | |
], | |
), | |
), | |
floatingActionButton: FloatingActionButton( | |
onPressed: _incrementCounter, | |
tooltip: 'Start subtitle', | |
child: const Icon(Icons.start), | |
), // This trailing comma makes auto-formatting nicer for build methods. | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment