Last active
April 30, 2018 21:56
-
-
Save JoshCheek/9d7ac00aaa57477d3080acc6f1965fb1 to your computer and use it in GitHub Desktop.
Fourier Transform https://vimeo.com/267305699, https://vimeo.com/266566509 and https://vimeo.com/264743988
This file contains 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
require 'graphics' | |
class IDK < Graphics::Simulation | |
def initialize(functions, *rest) | |
@functions = functions | |
super *rest | |
end | |
def draw(n) | |
clear :black | |
sample(@functions, n, 20) | |
.each_cons(2) { |(x1, y1), (x2, y2)| line x1, y1, x2, y2, :white } | |
end | |
end | |
def sample(fns, upto, num_fn_samples) | |
fns = fns.dup | |
points = [] | |
while 0 < upto && fns.any? | |
fn = fns.shift | |
size = [upto, num_fn_samples].min | |
upto -= num_fn_samples | |
size.times do |crnt| | |
points << fn[crnt.to_f/(num_fn_samples-1)] | |
end | |
end | |
points | |
end | |
def center(width:, height:, fns:, margin:, maintain_ratio:) | |
n_samples = 100 | |
samples = fns.flat_map do |fn| | |
n_samples.times.map { |i| fn[i.to_f/(n_samples-1)] } | |
end | |
xs, ys = samples.transpose | |
minx, maxx = xs.minmax | |
miny, maxy = ys.minmax | |
scale_x = (width - 2*margin) / (maxx - minx) | |
scale_y = (height - 2*margin) / (maxy - miny) | |
scale_x = scale_y = [scale_x, scale_y].min if maintain_ratio | |
translate_x = width / 2 | |
translate_y = height / 2 | |
fns.map do |fn| | |
lambda do |t| | |
x, y = fn[t] | |
# require "pry" | |
# binding().pry if 0.5 <= t | |
[x * scale_x + translate_x, y * scale_y + translate_y] | |
end | |
end | |
end | |
include Math | |
width = 800 | |
height = 600 | |
functions = center( | |
width: width, | |
height: height, | |
margin: 20, | |
maintain_ratio: true, | |
fns: [ | |
-> t { [cos(PI*t) , sin(PI*t) + 1] }, # top | |
-> t { [cos(PI*t+PI/2) - 1 , sin(PI*t+PI/2) ] }, # left | |
-> t { [cos(PI+PI*t) , sin(PI+PI*t) - 1] }, # bottom | |
-> t { [cos(PI*t+PI*3/2) + 1 , sin(PI*t+PI*3/2) ] }, # right | |
], | |
) | |
p sample(functions, 20*functions.length, 20).map { |x, y| [x.round, y.round] } | |
IDK.new(functions, width, height, 31).run |
This file contains 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
module Fourier | |
extend self | |
extend Math | |
include Math | |
def to(fx) | |
n = fx.length | |
n.times.map do |u| | |
real = imaginary = 0 | |
fx.each_with_index do |(f_real, f_imaginary), k| | |
p = -2*PI*u*k/n | |
c, s = cos(p), sin(p) | |
real += c*f_real + s*f_imaginary | |
imaginary += c*f_imaginary - s*f_real | |
end | |
[real/n, imaginary/n] | |
end | |
end | |
def from(fu, m=fu.length) | |
n = fu.length | |
n.times.map do |k| | |
real = imaginary = 0 | |
fu.take(m).each_with_index do |(f_real, f_imaginary), u| | |
p = 2*PI*u*k/n | |
c, s = cos(p), sin(p) | |
real += c*f_real + s*f_imaginary | |
imaginary += c*f_imaginary - s*f_real | |
end | |
[real, imaginary] | |
end | |
end | |
end |
This file contains 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
require 'graphics' | |
require_relative 'fourier' | |
class ShowFourier < Graphics::Simulation | |
include Math | |
def initialize(points, width, height, num_colours) | |
super width, height, num_colours | |
@frequencies = Fourier.to(points) | |
@progress = [] | |
@x_off = w/2 | |
@y_off = h/2 | |
end | |
def draw(iteration) | |
clear :black | |
n = @frequencies.length | |
iteration = 1 + (iteration / 10) | |
# the machine to draw it all | |
two_pi = 2*PI | |
p_no_u = two_pi*iteration.pred/n | |
mag_and_angle = @frequencies.each_with_index.map do |(f_real, f_imag), u| | |
[f_real, f_imag, p_no_u*u] | |
end | |
sum_x = sum_y = 0 | |
pts = [] | |
mag_and_angle.each do |r, i, ø| | |
c, s = cos(ø), sin(ø) | |
sum_x += c*r + s*i | |
sum_y += c*i - s*r | |
mag = sqrt(r*r + i*i) | |
# omit spammy results | |
if 2 <= mag | |
center_circle sum_x, sum_y, mag, :white unless mag < 2 | |
pts << sum_x << sum_y | |
end | |
end | |
last_x, last_y = pts.last(2) | |
draw_points pts, :green#, true | |
# crosshairs | |
center_line last_x, -h, last_x, 2*h, :red# , true | |
center_line -w, last_y, 2*w, last_y, :red# , true | |
# the drawing up to this point | |
p_no_u = two_pi*iteration.pred/n | |
real = imaginary = 0 | |
@frequencies.each_with_index do |(f_real, f_imaginary), u| | |
p = p_no_u*u | |
c, s = cos(p), sin(p) | |
real += c*f_real + s*f_imaginary | |
imaginary += c*f_imaginary - s*f_real | |
end | |
@progress << real << imaginary | |
draw_points @progress, :magenta, true | |
end | |
def draw_points(points, colour, thick = false) | |
0.step by: 2, to: points.length-3 do |i| | |
center_line points[i+0], points[i+1], points[i+2], points[i+3], colour, thick | |
end | |
end | |
def center_circle(x, y, r, colour) | |
circle x+w/2, y+h/2, r, colour | |
end | |
def center_line(x1, y1, x2, y2, colour, thick=false) | |
x1 += @x_off | |
x2 += @x_off | |
y1 += @y_off | |
y2 += @y_off | |
line x1, y1, x2, y2, colour | |
if thick | |
line x1+1, y1, x2+1, y2, colour | |
line x1-1, y1, x2-1, y2, colour | |
line x1, y1+1, x2, y2+1, colour | |
line x1, y1-1, x2, y2-1, colour | |
end | |
end | |
end | |
w, h = 800, 600 | |
# note: https://www.mobilefish.com/services/record_mouse_coordinates/record_mouse_coordinates.php | |
points = %w[193,47 166,125 140,204 123,193 99,189 74,196 58,213 49,237 52,261 65,279 86,292 113,295 135,282 152,258 201,95 212,127 218,150 213,168 201,185 192,200 203,214 219,205 233,191 242,170 244,149 242,131 233,111] | |
.map { |n| | |
# uhhhhh this is fkn dumb -.^ the drawing thing should either | |
# not mess w/ coords, or should get them correct | |
x, y = n.split(?,).map(&:to_i) | |
[x*2, h-y] | |
} | |
xmin, xmax = points.map(&:first).minmax | |
ymin, ymax = points.map(&:last).minmax | |
points = points.map do |x, y| | |
[ (xmax - xmin) / 2 + x - xmin - w/2, | |
(ymax - ymin) / 2 + y - ymin - h/2, | |
] | |
end | |
points = [[540, 440], [538, 463], [532, 485], [523, 507], [510, 526], [495, 543], [477, 557], [456, 568], [434, 576], [412, 580], [388, 580], [366, 576], [344, 568], [323, 557], [305, 543], [290, 526], [277, 507], [268, 485], [262, 463], [260, 440], [260, 440], [237, 438], [215, 432], [193, 423], [174, 410], [157, 395], [143, 377], [132, 356], [124, 334], [120, 312], [120, 288], [124, 266], [132, 244], [143, 223], [157, 205], [174, 190], [193, 177], [215, 168], [237, 162], [260, 160], [260, 160], [262, 137], [268, 115], [277, 93], [290, 74], [305, 57], [323, 43], [344, 32], [366, 24], [388, 20], [412, 20], [434, 24], [456, 32], [477, 43], [495, 57], [510, 74], [523, 93], [532, 115], [538, 137], [540, 160], [540, 160], [563, 162], [585, 168], [607, 177], [626, 190], [643, 205], [657, 223], [668, 244], [676, 266], [680, 288], [680, 312], [676, 334], [668, 356], [657, 377], [643, 395], [626, 410], [607, 423], [585, 432], [563, 438], [540, 440]] | |
.map { |x, y| [x-w/2, y-h/2] } | |
points.push points[0] | |
# Draw it | |
ShowFourier.new(points, w, h, 31).run |
This file contains 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
require 'graphics' | |
require_relative 'fourier' | |
class DrawFourier < Graphics::Simulation | |
def initialize(*) | |
super | |
@points = [] | |
@state = [:get_points] | |
end | |
def draw(iteration) | |
clear :black | |
if @state[0] == :get_points | |
text "Draw with the mouse, click when done", 50, 50, :white | |
x, y, clicked, * = mouse | |
if clicked | |
@frequencies = Fourier.to(@points) | |
@state = [:transform, iteration] | |
at_exit { p @points } | |
else | |
point = [x, y] | |
@points << point unless point == @points.last | |
end | |
draw_points @points | |
else | |
n = @frequencies.length | |
m = n - (iteration - @state[1]) + 1 | |
text "#{m}/#{n}", 50, 50, :white | |
draw_points Fourier.from(@frequencies, m) | |
sleep 1 unless m == n | |
end | |
end | |
private def draw_points(points) | |
points.each_cons(2) { |p1, p2| line *p1, *p2, :white } | |
end | |
end | |
DrawFourier.new(800, 600, 31).run |
This file contains 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
require 'graphics' | |
require_relative 'fourier' | |
class ShowFourier < Graphics::Simulation | |
def initialize(points, width, height, num_colours) | |
super width, height, num_colours | |
@frequencies = Fourier.to(points) | |
@m = @frequencies.length | |
@mode = :points | |
add_keydown_handler("j") { self.m = m - 1 } | |
add_keydown_handler("k") { self.m = m + 1 } | |
add_keydown_handler("p") { @mode = :points } | |
add_keydown_handler("f") { @mode = :frequencies } | |
end | |
def draw(iteration) | |
n = @frequencies.length | |
clear :black | |
text "#{m}/#{n}", 50, h-50, :white | |
frequencies = @frequencies.take(m) | |
case mode | |
when :points | |
points = Fourier.from frequencies | |
when :frequencies | |
points = frequencies | |
else raise "Wat? #{mode.inspect}" | |
end | |
points.each_cons(2) { |p1, p2| line *p1, *p2, :white } | |
end | |
private | |
attr_reader :m, :mode | |
def m=(m) | |
m = 2 if m < 2 | |
m = @frequencies.length if @frequencies.length < m | |
@m = m | |
end | |
end | |
w = 800 | |
h = 600 | |
r = 250 | |
n = 8 | |
# circle | |
circle = n.times.map do |i| | |
[r*Math.cos(i*2*Math::PI/n)+w/2, r*Math.sin(i*2*Math::PI/n)+h/2] | |
end | |
# square | |
square = | |
(n/4).times.map { |i| [ w/2+r , h/2+r - r*8*i/n ] } + | |
(n/4).times.map { |i| [ w/2+r - r*8*i/n , h/2-r ] } + | |
(n/4).times.map { |i| [ w/2-r , h/2-r + r*8*i/n ] } + | |
(n/4).times.map { |i| [ w/2-r + r*8*i/n , h/2+r ] } | |
# note: https://www.mobilefish.com/services/record_mouse_coordinates/record_mouse_coordinates.php | |
note = %w[193,47 140,204 123,193 99,189 74,196 58,213 49,237 52,261 65,279 86,292 113,295 135,282 152,258 201,95 212,127 218,150 213,168 201,185 192,200 203,214 219,205 233,191 242,170 244,149 242,131 233,111] | |
.map { |n| | |
x, y = n.split(?,).map(&:to_i) | |
[x*2, h-y] | |
} | |
# Normalize the points | |
points = note | |
points = points.dup | |
points.push points[0] | |
xmin, xmax = points.map(&:first).minmax | |
ymin, ymax = points.map(&:last).minmax | |
points = points.map do |x, y| | |
[x - xmin + 10, y - ymin + 10] | |
end | |
# Draw it | |
ShowFourier.new(points, w, h, 31).run |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment