Created
July 3, 2020 20:38
-
-
Save cshannon3/e772cc1e74d42b78477ac1412e9b1be6 to your computer and use it in GitHub Desktop.
Fourier
This file contains hidden or 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 '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