{
   "labels" : [
      "gui",
      "animation",
      "simple harmonic motion",
      "spectrum"
   ],
   "is_private" : null,
   "id" : "1-5fL",
   "code" : "(\r\n//Created by Eli Fieldsteel 2022 Feb 26\r\n\r\nvar shm, spectrum;\r\n\r\n//frequency-scaling factor: closer to zero -> slow motion\r\nvar freq_scale = 0.5;\r\n\r\n//amplitude-scaling factor: pixel radius when amplitude = 1\r\nvar amp_scale = 80;\r\n\r\n//spectrum is provided as an array containing one or more events\r\n//each event represents a sine wave with keys for 'freq', 'amp', and 'phs'\r\n\r\n//uncomment for examples\r\n/*sine (the default)*/ spectrum = [ (freq:1, amp:1, phs:0) ];\r\n// /*harmonics 1 & 2 */ spectrum = [ (freq:1, amp:1, phs:0), (freq:2, amp:1/2, phs:0) ];\r\n// /*harmonics 1 thru 4 */ spectrum = ((1..4).collect({ |n| (freq:n, amp:1/n, phs:0) }));\r\n// /*sawtooth*/ spectrum = ((1..12).collect({ |n| (freq:n, amp:1/n, phs:0) }));\r\n// /*square*/ spectrum = ((1,3..11).collect({ |n| (freq:n, amp:1/n, phs:0) }));\r\n// /*triangle*/ spectrum = ((1,3..11).collect({ |n| (freq:n, amp:1/(n**2), phs:n.odd.asInteger*pi+(pi/2)) }));\r\n// /*impulse (i.e. \"Blip\")*/ spectrum = ((1..12).collect({ |n| (freq:n, amp:1/4, phs:pi/2) }));\r\n\r\nWindow.closeAll;\r\n\r\nshm = {\r\n\targ input = [(freq:1, amp:1, phs:0)], freqscl=0.5, ampscl=75, datasize=200, framerate=40;\r\n\tvar win, cview, wview, phs;\r\n\tvar t = 0; //time/phase counter\r\n\tvar wavedata = Array.newClear(datasize); //waveform y-coordinate values\r\n\r\n\twin = Window(\r\n\t\t\"Simple Harmonic Motion Animation — (spacebar to pause/unpause)\",\r\n\t\tRect(100, 100, 1020, 520)\r\n\t).background_(Color.gray(0.2)).front;\r\n\twin.view.decorator_(FlowLayout(win.view.bounds, 10@10, 10@10));\r\n\tcview = UserView(win.view, 1000@500).background_(Color.gray(0.25));\r\n\r\n\tcview.drawFunc_({ |v|\r\n\t\tvar center, newcenter, x, y;\r\n\r\n\t\t//dividing line between circles/waveform\r\n\t\tPen.width_(2);\r\n\t\tPen.strokeColor_(Color.gray(0.5));\r\n\t\tPen.line(600@0, 600@520);\r\n\t\tPen.stroke;\r\n\r\n\t\t//draw circles\r\n\t\tPen.capStyle_(1);\r\n\t\tPen.width_(2);\r\n\t\tinput.do({ |sine, i|\r\n\t\t\tif (center == nil) { center = 300@250} { center = newcenter };\r\n\t\t\tx = cos(( (((t * freqscl) + (sine.phs / sine.freq ))) * sine.freq ) % 2pi) * sine.amp;\r\n\t\t\ty = sin(( (((t * freqscl) + (sine.phs / sine.freq ))) * sine.freq ) % 2pi) * sine.amp;\r\n\t\t\tnewcenter = (x@y) * ampscl * Point(1,-1);\r\n\t\t\tnewcenter = newcenter.translate(center);\r\n\t\t\tPen.strokeColor_(Color.gray(i.linlin(0, input.size, 0.7, 0.5), 0.8));\r\n\t\t\tPen.addArc(center, sine.amp * ampscl, 0, 2pi);\r\n\t\t});\r\n\t\tPen.stroke;\r\n\r\n\t\t//draw radii\r\n\t\tcenter = nil;\r\n\t\tPen.width_(4);\r\n\t\tinput.do({ |sine, i|\r\n\t\t\tif (center == nil) { center = 300@250} { center = newcenter };\r\n\t\t\tx = cos(( (((t * freqscl) + (sine.phs / sine.freq ))) * sine.freq ) % 2pi) * sine.amp;\r\n\t\t\ty = sin(( (((t * freqscl) + (sine.phs / sine.freq ))) * sine.freq ) % 2pi) * sine.amp;\r\n\t\t\tnewcenter = (x@y) * ampscl * Point(1,-1);\r\n\t\t\tnewcenter = newcenter.translate(center);\r\n\t\t\tPen.strokeColor_(Color(0.25, i.linlin(0, input.size-1, 0.55, 0.85), 0.95, 0.9));\r\n\t\t\tPen.line(center, newcenter);\r\n\t\t\tPen.stroke;\r\n\t\t});\r\n\r\n\t\t//draw horizontal line connecting radii to waveform\r\n\t\tPen.width_(2);\r\n\t\tPen.strokeColor_(Color.gray(1,0.3));\r\n\t\tPen.line(600@(newcenter.y), newcenter);\r\n\t\tPen.stroke;\r\n\r\n\t\t//store y-coordinate\r\n\t\twavedata = wavedata.rotate(1);\r\n\t\twavedata.put(0, newcenter.y);\r\n\r\n\t\t//draw waveform with connecting lines between adjacent wavedata values\r\n\t\tPen.width_(3);\r\n\t\tPen.strokeColor_(Color(1, 0.75, 0, 0.6));\r\n\t\twavedata.drop(-1).do({ |y,i|\r\n\t\t\tif (y.notNil && wavedata.wrapAt(i+1).notNil)\r\n\t\t\t{Pen.line(Point(600 + (i*2), y), Point(600 + (i*2+2), wavedata.wrapAt(i+1)))};\r\n\t\t});\r\n\t\tPen.stroke;\r\n\r\n\t\t//advance time\r\n\t\t(t = t + (2pi/framerate))%2pi;\r\n\r\n\t});\r\n\r\n\tcview.frameRate_(framerate);\r\n\tcview.animate_(true);\r\n\r\n\t//spacebar to pause\r\n\tcview.keyDownAction_({ |v, char|\r\n\t\tif (char == $ ) { cview.animate_(cview.animate.not) };\r\n\t});\r\n};\r\n\r\nshm.(spectrum, freq_scale, amp_scale); //run\r\n)",
   "author" : "eli.fieldsteel",
   "name" : "Animation of Simple Harmonic Motion",
   "ancestor_list" : [],
   "description" : "A GUI for animating summation of partials as simple harmonic motion, depicted as circular rotation correlated with a graph of amplitude vs. time."
}
