I read about Neil Fraser's audio-based oscilloscope art on the blog, and I got hooked. This time it's a rotating cube done in python. I've made a video of the result, and synchronized the wav file as the soundtrack, so you can hook an oscilloscope to your headphone jack and "sing-along" at home.
The cube is a bit jumpy because the low-frequency response of my soundcard doesn't extend down to DC, and the youtube audio compression takes its toll as well (odd wiggles), but it still works.
The output is best viewed on an analog scope, but you may have luck with some digital models, too. For instance, I found that on a Rigol DS1054Z, setting the Mem Depth (Acquire menu) to 6k, and turning the horizontal to 1ms works well.
Here's the python code, for those interested. The ScopeDisplay class is probably the most interesting part. The rest of the code just draws the cube, using backface culling to avoid drawing the hidden lines, but that's easily removed if you want to see them. I really enjoyed remembering all this stuff. Kids today are spoiled with their fancy depth-buffers!
If x- and y- seem reversed, just swap them on the scope. You've got a 50-50 shot of getting it right the first time :-)
#!/usr/bin/env python
# this code is in the public domain
import wave
import struct
import math
class ScopeDisplay():
def __init__(self, sample_rate, filename):
self.sample_rate = sample_rate
self.wav = wave.open(filename, 'w')
self.wav.setnchannels(2)
self.wav.setsampwidth(1)
self.wav.setframerate(sample_rate)
self.wav.setnframes(1)
self.data = []
def point(self, x, y):
left = int(min(max(128+127*x, 0), 255))
right = int(min(max(128+127*y, 0), 255))
self.data.append(struct.pack('B', left))
self.data.append(struct.pack('B', right))
def line(self, x0, y0, x1, y1, step = 1):
d = math.sqrt((x0-x1)*(x0-x1) + (y0-y1)*(y0-y1))
n_pts = max(2, int(step * d))
for i in range(0, n_pts):
x = x0 + (x1 - x0) * i / (n_pts-1)
y = y0 + (y1 - y0) * i / (n_pts-1)
self.point(x, y)
def close(self):
self.wav.writeframes(''.join(self.data))
self.wav.close()
display = ScopeDisplay(48000, 'oscilloscope_cube.wav')
# cube points
x = [-1, -1, -1, -1, +1, +1, +1, +1]
y = [-1, -1, +1, +1, -1, -1, +1, +1]
z = [-1, +1, +1, -1, -1, +1, +1, -1]
# cube faces (indexes into points)
faces = [[1,2,6,5],[0,4,7,3],[4,7,6,5],[0,1,2,3],[2,6,7,3],[0,1,5,4]]
# face normals (for hidden line-removal)
nx = [0, 0, 1,-1, 0, 0]
ny = [0, 0, 0, 0, 1,-1]
nz = [1,-1, 0, 0, 0, 0]
# viewpoint pe{x,y,z}
pex = 0
pey = 0
pez = 3
# cube size
scale = 1/math.sqrt(2)
# rotation speeds
x_speed = 0.002
y_speed = 0.0001
z_speed = 0.00003
for i in range(0, 100000):
# draw cube
for f_idx in range(0, len(faces)):
face = faces[f_idx]
nx0 = nx[f_idx]
ny0 = ny[f_idx]
nz0 = nz[f_idx]
for pair in [[face[0], face[1]],
[face[1], face[2]],
[face[2], face[3]],
[face[3], face[0]]]:
x0 = scale*x[pair[0]]
y0 = scale*y[pair[0]]
z0 = scale*z[pair[0]]
x1 = scale*x[pair[1]]
y1 = scale*y[pair[1]]
z1 = scale*z[pair[1]]
# rotate cube around y-axis
theta = 2*math.pi * i * y_speed
cs = math.cos(theta)
sn = math.sin(theta)
xq0 = x0 * cs - z0 * sn
yq0 = y0
zq0 = x0 * sn + z0 * cs
xq1 = x1 * cs - z1 * sn
yq1 = y1
zq1 = x1 * sn + z1 * cs
nx1 = nx0 * cs - nz0 * sn
ny1 = ny0
nz1 = nx0 * sn + nz0 * cs
# rotate cube around x-axis
theta = 2*math.pi * i * x_speed
cs = math.cos(theta)
sn = math.sin(theta)
xw0 = xq0
yw0 = yq0 * cs - zq0 * sn
zw0 = yq0 * sn + zq0 * cs
xw1 = xq1
yw1 = yq1 * cs - zq1 * sn
zw1 = yq1 * sn + zq1 * cs
nx2 = nx1
ny2 = ny1 * cs - nz1 * sn
nz2 = ny1 * sn + nz1 * cs
# rotate cube around z-axis
theta = 2*math.pi * i * z_speed
cs = math.cos(theta)
sn = math.sin(theta)
xr0 = xw0 * cs - yw0 * sn
yr0 = xw0 * sn + yw0 * cs
zr0 = zw0
xr1 = xw1 * cs - yw1 * sn
yr1 = xw1 * sn + yw1 * cs
zr1 = zw1
nx3 = nx2 * cs - ny2 * sn
ny3 = nx2 * sn + ny2 * cs
nz3 = nz2
# project points into screen space
d0 = (1 - pez) / (zr0 - pez)
xs0 = pex + d0*(xr0 - pex)
ys0 = pey + d0*(yr0 - pey)
d1 = (1 - pez) / (zr1 - pez)
xs1 = pex + d1*(xr1 - pex)
ys1 = pey + d1*(yr1 - pey)
# cull back-faces based
# on dot(normal, view vector)
dot0 = nx3*(xr0 - pex) + ny3*(yr0 - pey) + nz3*(zr0 - pez)
dot1 = nx3*(xr1 - pex) + ny3*(yr1 - pey) + nz3*(zr1 - pez)
if dot0 < 0 and dot1 < 0:
display.line(xs0, ys0, xs1, ys1, 20)
display.close()
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.
See also "Oscilloscope Music Kickstarter (June 2015): https://www.youtube.com/watch?v=qnL40CbuodU There are many more like this.
Are you sure? yes | no