https://commons.wikimedia.org/wiki/File:Fourier_series_square_wave_circles_animation.gif
Forked from André Michelle's Pen Fourier series.
A Pen by Captain Anonymous on CodePen.
<div> | |
<h3>Fourier series</h3> | |
<canvas width="512" height="256"></canvas> | |
<div> | |
<span>Frequency</span> | |
<input frequency type="range" min="-5" max="-3" value="-3.5" step="0.0001"> | |
</div> | |
<div> | |
<span>Order</span> | |
<input order type="range" min="0" max="16" value="4" step="1"> | |
</div> | |
<div> | |
<span>Waveform</span> | |
<form id="waveform"> | |
<label>Square | |
<input type="radio" name="waveform" value="square" checked> | |
</label> | |
<label>Sawtooth | |
<input type="radio" name="waveform" value="sawtooth"> | |
</label> | |
</form> | |
</div> | |
</div> |
var frequencyInput = document.querySelector("input[frequency]"); | |
var orderInput = document.querySelector("input[order]"); | |
var waveformInput = document.getElementById("waveform").elements["waveform"]; | |
var canvas = document.querySelector("canvas"); | |
var context = canvas.getContext("2d"); | |
var PI2 = Math.PI * 2.0; | |
var Scale = 64.0; | |
var time = 0.0; | |
var startTime = new Date().getTime(); | |
var values = []; | |
var valuePointer = 0; | |
var x = 128.0, | |
y = 128.0; | |
function fourier(order) { | |
var phase = order * time * PI2; | |
var radius = 4.0 / (order * Math.PI) * Scale; | |
context.beginPath(); | |
context.lineWidth = 1.0; | |
context.strokeStyle = "rgba(255,128,32,1.0)"; | |
context.arc(x, y, radius, 0, PI2); | |
context.stroke(); | |
context.strokeStyle = "rgba(255,255,255,0.4)"; | |
context.moveTo(x, y); | |
x += Math.cos(phase) * radius; | |
y += Math.sin(phase) * radius; | |
context.lineTo(x, y); | |
context.stroke(); | |
}; | |
function connect() { | |
context.beginPath(); | |
context.moveTo(x + 0.5, y + 0.5); | |
context.lineTo(256 + 0.5, y + 0.5); | |
context.strokeStyle = "rgba(255,255,32,1.0)"; | |
context.stroke(); | |
}; | |
function drawWave() { | |
values[valuePointer++ & 255] = y; | |
context.beginPath(); | |
context.strokeStyle = "rgba(0,255,0,1)"; | |
context.moveTo(256 + 0.5, y + 0.5); | |
for (var i = 1; i < 256; ++i) { | |
context.lineTo(256 + i + 0.5, values[(valuePointer - i) & 255] + 0.5); | |
} | |
context.stroke(); | |
} | |
(function frame() { | |
canvas.width = canvas.clientWidth; | |
canvas.height = canvas.clientHeight; | |
x = 144.0; | |
y = 128.0; | |
switch (waveformInput.value) { | |
case "square": | |
for (var order = 0; order <= orderInput.value; order++) { | |
fourier((order << 1) + 1); | |
} | |
break; | |
case "sawtooth": | |
for (var order = 1; order <= orderInput.value; order++) { | |
fourier(order << 1); | |
} | |
break; | |
} | |
connect(); | |
drawWave(); | |
var now = new Date().getTime(); | |
time += (now - startTime) * Math.pow(10.0, frequencyInput.value); | |
startTime = now; | |
window.requestAnimationFrame(frame); | |
})(); |
html, body { | |
margin: 0; | |
padding: 0; | |
width: 100%; | |
height: 100%; | |
font-family: Open Sans; | |
font-size: 12px; | |
color: orange; | |
overflow: hidden; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
display: -webkit-flex; | |
-webkit-align-items: center; | |
-webkit-justify-content: center; | |
background: #222; | |
} | |
h3 { | |
margin: 0 0 3px 1px; | |
} | |
canvas { | |
background: black; | |
} | |
span { | |
width: 80px; | |
margin: 0; | |
padding: 0; | |
display: inline-block; | |
} | |
form, label { | |
display: inline-block; | |
} | |
input[type="range"] { | |
margin: 0; | |
padding: 0; | |
width: 432px; | |
display: inline; | |
vertical-align: middle; | |
} |