Skip to content

Instantly share code, notes, and snippets.

@cshannon3
Created July 3, 2020 20:38
Show Gist options
  • Save cshannon3/e772cc1e74d42b78477ac1412e9b1be6 to your computer and use it in GitHub Desktop.
Save cshannon3/e772cc1e74d42b78477ac1412e9b1be6 to your computer and use it in GitHub Desktop.
Fourier
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:math';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Container(
height: double.infinity,
width: double.infinity,
// color:Colors.green,
child: Fourier2())
// Center(
// child: MyWidget(),
//),
),
);
}
}
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('Hello, World!', style: Theme.of(context).textTheme.headline4);
}
}
class Fourier2 extends StatefulWidget {
const Fourier2();
@override
_Fourier2State createState() => _Fourier2State();
}
class _Fourier2State extends State<Fourier2> {
List<WaveVa> notes = noteData;
ComboWave comboWave;
Rect mainBox;
@override
void initState() {
super.initState();
mainBox = Rect.fromLTWH(0.0, 0.0, 500.0, 500.0);
print(mainBox.width);
comboWave = ComboWave(
totalProgressPerCall: -4, // percent of circle progressed per call
waveColor: Colors.green,
waves: [],
width: mainBox.width / 2,
centerNode: Offset(mainBox.center.dx,
mainBox.center.dy - mainBox.height / 2 - mainBox.top),
maxLength: mainBox.height / 6,
maxTraceHeight: mainBox.height / 8);
notes.forEach((n) => n.init());
refresh();
}
play() {
//notes.where((n)=>n.isActive).forEach((n)=>n.audio?.play());
}
refresh() {
comboWave.reset();
notes.forEach((n) {
if (n.isActive) {
// n.audio?.setVolume(100*n.amp);
comboWave.waves.add(n.toWave());
}
});
setState(() {});
}
@override
void dispose() {
// notes.forEach((note)=>note.audio?.dispose());
super.dispose();
}
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return mobileLayout(size);
}
List<Widget> _buildKeys2(double height) {
List<Widget> keysWidgets = [];
notes.forEach((note) {
keysWidgets.add(_buildKey2(note, height));
});
return keysWidgets;
// 49 C#/ 51 D#/ 54 F#/ 56 G#/ 58 A#/ //61 / 63/ 66/ 68/ 70
}
Widget _buildKey2(WaveVa note, double height) {
return Container(
height: height,
width: 50.0,
decoration: BoxDecoration(
color: note.isSharp ? Colors.black : Colors.white,
border: Border(
left: BorderSide(color: Colors.white24, width: 1.0),
right: BorderSide(color: Colors.white24, width: 1.0),
bottom: BorderSide(color: Colors.white24, width: 1.0),
)),
child: new Container(
color: note.isActive ? note.color.withOpacity(0.5) : null,
padding: EdgeInsets.all(1.0),
child: Column(
children: <Widget>[
Text(
note.keyName,
style: TextStyle(
color: note.isSharp ? Colors.white : Colors.black,
),
),
Expanded(
child: !note.isActive
? Container()
: ListView(
children: List.generate(5, ((i) {
return Padding(
padding: EdgeInsets.symmetric(vertical: .0),
child: Container(
height: 15.0,
child: FlatButton(
onPressed: () {
note.amp = (1 -
i / 5); //?note.amp=(1-2*i/5):note.amp=0.9;
setState(() {
refresh();
});
},
child: Container(
color: note.amp >= (1 - i / 5)
? Colors.red
: Colors.grey,
),
),
),
);
})))),
IconButton(
icon: Icon(
Icons.fiber_manual_record,
color: note.isActive ? Colors.red : Colors.grey,
),
onPressed: () {
setState(() {
note.isActive = !note.isActive;
refresh();
});
},
),
// )
],
),
),
);
}
Widget mobileLayout(Size size) {
comboWave.centerNode = Offset(size.width / 2, size.height / 4);
return ListView(
children: <Widget>[
Container(
height: 150.0,
color: Colors.white.withOpacity(0.5),
child: new ListView(
scrollDirection: Axis.horizontal,
children: _buildKeys2(size.height / 5),
), // ListView
),
Row(
children: <Widget>[
IconButton(
icon: Icon(comboWave.paused ? Icons.play_arrow : Icons.pause),
onPressed: () {
comboWave.paused = !comboWave.paused;
setState(() {});
},
),
Expanded(
child: Slider(
min: 0.0,
max: 20.0,
divisions: 10,
onChanged: (newVal) {
comboWave.totalProgressPerCall = -newVal;
refresh();
setState(() {});
},
value: -comboWave.totalProgressPerCall,
),
),
],
),
Container(
height: 400.0,
width: double.infinity,
child: FourierLines(comboWave)),
Container(
height: 200.0,
),
],
);
}
}
class FourierLines extends StatefulWidget {
// Lines fourierLines;
ComboWave comboWave;
FourierLines(
this.comboWave,
);
@override
_FourierLinesState createState() => _FourierLinesState();
}
class _FourierLinesState extends State<FourierLines> {
Timer _timer;
Stopwatch stopwatch = Stopwatch();
ComboWave comboWave;
double padding = 20.0;
@override
void initState() {
super.initState();
_timer?.cancel(); // cancel old timer if it exists
comboWave = widget.comboWave;
//print(comboWave.waves)
_timer = Timer.periodic(Duration(milliseconds: 20), _update);
}
_update(Timer t) {
comboWave.update();
setState(() {});
}
@override
void dispose() {
super.dispose();
_timer?.cancel();
}
@override
Widget build(BuildContext context) {
Size s = MediaQuery.of(context).size;
return Stack(
children: <Widget>[
CustomPaint(
painter: FourierPainter(self: widget.comboWave),
child: Container(
height: s.height,
width: s.width,
//color: Colors.blue,
)),
...comboWidget(),
...traces()
],
);
}
Widget traceWidget(List trace, Color waveColor) {
return Container(
padding: EdgeInsets.only(right: padding),
width: double.infinity,
height: double.infinity,
// color: widget.backgroundColor,
child: ClipRect(
child: CustomPaint(
painter: TracePainter(
dataSet: trace,
traceColor: waveColor ?? Colors.green,
),
),
),
);
}
List<Widget> traces() {
List<Widget> out = [];
if (comboWave != null) {
out.add(traceWidget(comboWave.trace, comboWave.waveColor));
comboWave.waves.forEach((w) {
if (w.isShown) out.add(traceWidget(w.trace, w.waveColor));
});
}
return out;
}
List<Widget> comboWidget() {
return [
// Center(
// child:
Transform(
transform: Matrix4.translationValues(
// radius *
comboWave.selfNode.dx,
// -radius *
comboWave.selfNode.dy,
0.0)
..rotateZ(
comboWave.rot,
),
child: FractionalTranslation(
translation: Offset(-0.5, -0.5),
child: Container(
height: 30.0,
width: 10.0,
decoration: BoxDecoration(
color: Colors.green,
//shape: BoxShape.circle,
),
),
),
),
]..addAll(List.generate((comboWave.waves.length), (wavenum) {
return Transform(
transform: Matrix4.translationValues(
// radius *
comboWave.waves[wavenum].selfNode.dx,
// -radius *
comboWave.waves[wavenum].selfNode.dy,
0.0)
..rotateZ(
comboWave.waves[wavenum].rot,
),
child: FractionalTranslation(
translation: Offset(-0.5, -0.5),
child: Container(
height: 30.0,
width: 10.0,
decoration: BoxDecoration(
color: //Colors.black
comboWave.waves[wavenum].waveColor,
//shape: BoxShape.circle,
),
),
),
// ),
);
}));
}
}
class TracePainter extends CustomPainter {
final List dataSet;
final Color traceColor;
TracePainter({this.dataSet, this.traceColor = Colors.white});
@override
void paint(Canvas canvas, Size size) {
final tracePaint = Paint()
..strokeJoin = StrokeJoin.round
..strokeWidth = 2.0
..color = traceColor
..style = PaintingStyle.stroke;
final axisPaint = Paint()
..strokeWidth = 1.0
..color = Colors.white;
// only start plot if dataset has data
int length = dataSet.length;
if (length > 0) {
Path trace = Path();
trace.moveTo(0.0, dataSet[0].toDouble() + size.height / 2);
// generate trace path
for (int p = 0; p < length; p++) {
trace.lineTo(p.toDouble(), dataSet[p].toDouble() + size.height / 2);
}
canvas.drawPath(trace, tracePaint);
Offset yStart =
Offset(0.0, size.height / 2); // - (0.0 - yMin)) * yScale);
Offset yEnd =
Offset(size.width, size.height / 2); // - (0.0 - yMin) _;//* yScale);
canvas.drawLine(yStart, yEnd, axisPaint);
}
}
@override
bool shouldRepaint(TracePainter old) => true;
}
class ComboWave {
List<Wave> waves;
Wave comboWave;
double totalProgressPerCall;
double tickprogress;
List<double> trace = [];
Color waveColor;
double maxLength = 150.0;
double maxTraceHeight = 200.0;
double width = 600;
double thickness = 4.0;
Offset centerNode = Offset(0.0, 0.0);
Offset selfNode = Offset(0.0, 0.0);
double x = 0;
double y = 0;
double rot = 0;
bool paused = false;
ComboWave(
{@required this.waves,
this.waveColor = Colors.green,
this.totalProgressPerCall = 1.0,
this.tickprogress = 0.0,
this.centerNode,
this.width,
this.maxLength,
this.maxTraceHeight});
reset() {
tickprogress = 0.0;
trace = [];
waves = [];
}
update() {
if (paused) return;
x = 0.0;
y = 0.0;
rot = 0.0;
Offset startNode = centerNode;
waves.forEach((w) {
startNode = updateWave(w, startNode);
});
rot = rot / waves.length;
tickprogress += totalProgressPerCall;
trace.add(y * maxTraceHeight);
if (trace.length > width) trace.removeAt(0);
selfNode =
Offset(centerNode.dx + maxLength * x, centerNode.dy + maxLength * y);
}
Offset updateWave(Wave w, Offset startNode) {
w.trace.add(K(w.progress) * w.fractionOfFull * maxTraceHeight);
if (w.trace.length > width) w.trace.removeAt(0);
w.updateWave(tickprogress,
startNode); //.centerNode, info.maxTraceHeight, info.maxLength
x += w.x;
y += w.y;
rot += w.rot;
w.selfNode = Offset(
centerNode.dx + maxLength * w.x, centerNode.dy + maxLength * w.y);
w.endNodeLocation = Offset(w.nodeLocation.dx + maxLength * w.x,
w.nodeLocation.dy + maxLength * w.y);
return w.endNodeLocation;
}
}
class Wave {
// Using my trig so a circle is 4 quadrants of 100% each, full rotation is 400 %
bool isShown = true;
double progressPerFrame; // Frequency,
double progress;
List<double> trace = [];
final Color waveColor;
final double fractionOfFull;
// double width=600.0;
Offset nodeLocation = Offset(0.0, 0.0);
Offset endNodeLocation = Offset(0.0, 0.0);
Offset selfNode = Offset(0.0, 0.0);
// For the fourier example, length represents both amplitude of the wave and radius of the circle
double x = 0.0;
double y = 0.0;
double rot = 0.0;
//WaveVals waveVals;
Wave({
this.progressPerFrame = 1.0,
this.progress = 0.0,
@required this.waveColor,
@required this.fractionOfFull,
this.nodeLocation = const Offset(0.0, 0.0),
});
updateWave(double newProgress, Offset startlocation) {
//Offset startlocation, Offset center, double traceHeight, double maxLength
progress = progressPerFrame * newProgress;
nodeLocation = startlocation;
y = fractionOfFull * K(progress);
x = fractionOfFull * Z(progress);
rot = rad(progress);
}
}
class FourierPainter extends CustomPainter {
ComboWave self;
FourierPainter({this.self});
@override
bool shouldRepaint(FourierPainter oldDelegate) {
return true;
}
void paint(Canvas canvas, Size size) {
self.waves.forEach((lineNode) {
Paint p = Paint()
..color = lineNode.waveColor
..strokeWidth = self.thickness
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke;
canvas.drawLine(lineNode.nodeLocation, lineNode.endNodeLocation, p);
});
}
}
class WaveVa {
double key;
bool isSharp;
String keyName;
double freq;
double amp = 0.3; // vol
//AudioPlayerController audio;
bool isActive;
Color color;
WaveVa(
{this.freq,
this.isSharp = false,
this.keyName,
this.key,
this.isActive = false,
this.color});
init() {
//String a = "assets/audio/piano_$key.mp3";
//audio = AudioPlayerController.asset(a);
// audio.initialize();
}
Wave toWave() =>
Wave(fractionOfFull: amp, progressPerFrame: freq, waveColor: color);
}
List<WaveVa> noteData = [
WaveVa(
keyName: "C",
key: 52,
isSharp: false,
freq: 1,
color: Colors.blue,
isActive: true),
WaveVa(
keyName: "C#", key: 53, isSharp: true, freq: 1.066, color: Colors.cyan),
WaveVa(
keyName: "D",
key: 54,
isSharp: false,
freq: 1.129,
color: Colors.lightBlue),
WaveVa(keyName: "D#", key: 55, isSharp: true, freq: 1.19, color: Colors.teal),
WaveVa(
keyName: "E",
key: 56,
isSharp: false,
freq: 1.26,
color: Colors.yellow,
isActive: true),
WaveVa(
keyName: "F",
key: 57,
isSharp: false,
freq: 1.34,
color: Colors.deepOrange),
WaveVa(
keyName: "F#", key: 58, isSharp: true, freq: 1.42, color: Colors.green),
WaveVa(keyName: "G", key: 59, isSharp: false, freq: 1.5, color: Colors.lime),
WaveVa(
keyName: "G#", key: 60, isSharp: true, freq: 1.597, color: Colors.amber),
WaveVa(keyName: "A", key: 61, isSharp: false, freq: 1.69, color: Colors.pink),
WaveVa(
keyName: "A#", key: 62, isSharp: true, freq: 1.79, color: Colors.purple),
WaveVa(
keyName: "B", key: 63, isSharp: false, freq: 1.89, color: Colors.indigo),
WaveVa(keyName: "C", key: 64, isSharp: false, freq: 2, color: Colors.blue),
];
double K(double progress) {
return sin(progress * (pi / 200));
}
double Z(double progress) {
return cos(progress * (pi / 200));
}
double X(double progress) {
return tan(progress * (pi / 200));
}
double rad(double progress) {
return progress * (pi / 200);
}
double toProgress(double rad) {
return rad * (200.0 / pi);
}
double progressFromZ(double zlength, double radius) {
return toProgress(acos(zlength / radius));
}
double progressFromK(double klength, double radius) {
return toProgress(asin(klength / radius));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment