Skip to content

Instantly share code, notes, and snippets.

@galaktyk
Created May 16, 2024 16:28
Show Gist options
  • Save galaktyk/91fc1077661e76da7515151f307580df to your computer and use it in GitHub Desktop.
Save galaktyk/91fc1077661e76da7515151f307580df to your computer and use it in GitHub Desktop.
viz
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