I was asked to design a soundwave tattoo that references a specific part of a single song. It turned out pretty cool. Buildings are often designed for a 50-year lifespan. Female life expectancy in New York is pegged at 83 years, so as a design problem the time scale on this exceeds architecture.
Building off of previous work that looked at real-time sound visualization, the intention of this exercise was to create a series of physical objects that legibly conveyed the transformation of sound into a landscape. Four specific indicative moments of recorded sound were rendered as a topographic form in Processing, then 3d printed. Any piece of real-time or recorded sound would work, however, these prototypes were chosen because they highlight special snippets or short moments during signature songs that could warrant further observation of the ordered or chaotic underlying sound structure. Once printed, each piece creates a striking object that allows for ease of visual comparison.
The four selections shown here include:
1) “Young Americans” – David Bowie. The brief pause at 4:19. (youtube link) Also, per Jennifer Egan in A Visit to the Goon Squad: “This is a lost opportunity. Hell, it would’ve been so easy to draw out the pause after ‘…break down and cry…’ to a full second, or 2, or 3, but Bowie must’ve chickened out for some reason.”
2) “Ride of the Valkyries” – Richard Wagner. The introduction of the main theme including the arrival of the brass instruments. (youtube link)
3) “Mood Indigo” – Duke Ellington. Jimmy Hamilton’s introduction on the clarinet. (youtube link)
4) “Sonified Starlight” – NASA. Translation of light waves emanating from star KIC 7671081B into an audible pattern via NASA’s Kepler Input Catalog. (soundcloud link)
Lastly, drop me a line if you’d be interested in your own 3D printed soundwave.
“Sonified Starlight” – NASA. Translation of light waves emanating from star KIC 7671081B into an audible pattern via NASA’s Kepler Input Catalog. (soundcloud link)
“Young Americans” – David Bowie. The brief pause at 4:19. (youtube link)
“Ride of the Valkyries” – Richard Wagner. The introduction of the main theme including the arrival of the brass instruments. (youtube link)
“Mood Indigo” – Duke Ellington. Jimmy Hamilton’s introduction on the clarinet. (youtube link)
Working on a series of comparative analysis of New York groups with Jackie.
]]>

import processing.dxf.*;
import ddf.minim.analysis.*;
import ddf.minim.*;
FFT fftLog1;
FFT fftLog2;
Waveform myRects;
Minim minim;
AudioPlayer groove1;
AudioPlayer groove2;
boolean record;
PFont font;
PFont fontoutline;
void setup(){
size(1200,600,P3D);
noStroke();
minim = new Minim(this);
groove1 = minim.loadFile(“groove_iggy.mp3”);
groove2 = minim.loadFile(“groove_wagner.mp3”);
groove1.loop();//repeat each song
groove2.loop();
font = loadFont(“HelveticaNeueLT-Bold-18.vlw”);
fontoutline = loadFont(“HelveticaNeueLT-Bold-18.vlw”);
fftLog1 = new FFT(groove1.bufferSize(),groove1.sampleRate()); //create the FFT logarithmic scale
fftLog2 = new FFT(groove2.bufferSize(),groove2.sampleRate());
fftLog1.logAverages(22,4); //adjust numbers to adjust spacing
fftLog2.logAverages(22,4);
float w1 = float ((fftLog1.avgSize()+fftLog2.avgSize())/2);
float x = w1;
float y = 0;
float z = 50;
myRects = new Waveform(x,y,z);
}
void draw(){
background(15);
directionalLight(126,126,126,sin(radians(frameCount)),cos(radians(frameCount)),1);
ambientLight(152,152,152);
for(int i = 0; i < fftLog1.avgSize(); i++){
int w = int(width/fftLog1.avgSize());
float zoom = 1;
float jitter = (max(fftLog1.getAvg(i)*200,fftLog2.getAvg(i)*200 )); //jitter in camera influenced by waveform
PVector foc = new PVector((myRects.x*.5+jitter*.5), myRects.y+jitter, 0);
PVector cam = new PVector(zoom, zoom, -zoom);
if (frameCount < 260){
camera(foc.x+cam.x,foc.y+(cam.y-1500*(cos(radians(-frameCount+60)))),foc.z+cam.z-400,
foc.x,foc.y,foc.z-100,
0,0,1);
//println(-1500*(cos(radians(-frameCount+60))));
}
else {
camera(foc.x+cam.x,foc.y+(cam.y+1418.278),foc.z+cam.z-400,foc.x,foc.y,foc.z-100,0,0,1);
}
}
fftLog1.forward(groove1.mix); //play each song
fftLog2.forward(groove2.mix);
myRects.update1(); //update each waveform+boolean
myRects.update2();
myRects.update3();
myRects.textdraw1(); //draw z height for song waveforms
myRects.textdraw2();
if(record){
beginRaw(DXF, "output.dxf");
}
// DXF will export the stuff drawn between here.
myRects.plotBoolean(); //create surfaces
myRects.plotTrace1();
myRects.plotTrace2();
if(record){
endRaw();
record = false;
println("Done DXF~!");
}
}
void stop() {
groove1.close(); // always close Minim audio classes when you finish with them
groove2.close();
minim.stop(); // always stop Minim before exiting
super.stop();
}
class Waveform{
float x,y,z;
PVector[] pts1 = new PVector[fftLog1.avgSize()];
PVector[] pts2 = new PVector[fftLog2.avgSize()];
PVector[] pts3 = new PVector[fftLog1.avgSize()]; //needed for boolean waveform
PVector[] trace1 = new PVector[0];
PVector[] trace2 = new PVector[0];
PVector[] trace3 = new PVector[0]; //needed for boolean waveform
Waveform(float incomingX, float incomingY, float incomingZ){
x = incomingX;
y = incomingY;
z = incomingZ;
}
void update1(){ //plot boolean waveform
plotB();
}
void plotB(){
for(int i = 0; i < fftLog1.avgSize(); i++){
int w = int(width/fftLog1.avgSize());
x = i*w-1050; //adjust the x position of the waveform here
y = frameCount*5;
z = height/4-fftLog1.getAvg(i)*10;
stroke(0);
point(x, y, z);
pts1[i] = new PVector(x, y, z);
//increase size of array trace by length+1
trace1 = (PVector[]) expand(trace1, trace1.length+1);
//always get the next to last
trace1[trace1.length-1] = new PVector(pts1[i].x, pts1[i].y, pts1[i].z);
}
}
void plotBoolean(){
stroke(255,80);
int inc = (fftLog1.avgSize()+fftLog2.avgSize())/2;
for(int i=1; i<(trace1.length+trace2.length)/2-inc; i++){
if(i%inc != 0){
beginShape(TRIANGLE_STRIP);
float value = (trace1[i].z*100);
float m = map(value, -500, 20000, 0, 255);
fill(m*2, 125, -m*2, 140);
int threshold = 15;
if (trace1[i].z
textFont(fontoutline, 24);
fill(155);
text(“wagner”,200,500,0);
text(“iggy”,900,500,0);
text(“max(iggy-wagner)”,500,500,0);
}
}
}
void plotTrace1(){
stroke(255,80);
int inc = fftLog1.avgSize();
for(int i=1; i

EDIT:
Added link to processing app, see it in action (loud rock music will begin playing…so turn it up!)
http://gracefulspoon.com/processingapps/singlewave/index.html
import processing.dxf.*;
import ddf.minim.analysis.*;
import ddf.minim.*;
FFT fftLin;
FFT fftLog;
Waveform myRects;
Minim minim;
AudioPlayer groove;
boolean record;
void setup(){
size(1000, 500, P3D);
noStroke();
minim = new Minim(this);
groove = minim.loadFile(“groove.mp3”);
groove.loop();
background(255);
fftLog = new FFT(groove.bufferSize(), groove.sampleRate());
fftLog.logAverages(22, 4); //adjust spacing here
float w = float(width/fftLog.avgSize());
float x = w;
float y = 0;
float z = 50;
float radius = 10;
myRects = new Waveform(x,y,z,radius);
}
void draw(){
background(0);
directionalLight(126,126,126,sin(radians(frameCount)),cos(radians(frameCount)),1);
ambientLight(102,102,102);
float zoom = 1000;
PVector foc = new PVector(myRects.x*0.5, myRects.y*0.5, 0);
PVector cam = new PVector(zoom*sin(radians(frameCount)), zoom*cos(radians(frameCount)), -zoom);
camera(foc.x+cam.x,foc.y+cam.y,foc.z+cam.z,foc.x,foc.y,foc.z,0,0,1);
//play the song
fftLog.forward(groove.mix);
myRects.update();
if(record){
beginRaw(DXF, “output.dxf”);
}
// DXF will export the stuff drawn between here.
myRects.plotTrace();
if(record){
endRaw();
record = false;
println(“Done DXF~!”);
}
}
void stop() {
// always close Minim audio classes when you finish with them
groove.close();
// always stop Minim before exiting
minim.stop();
super.stop();
}
class Waveform{
float x,y,z;
float radius;
PVector[] pts = new PVector[fftLog.avgSize()];
PVector[] trace = new PVector[0];
Waveform(float incomingX, float incomingY, float incomingZ, float incomingRadius){
x = incomingX;
y = incomingY;
z = incomingZ;
radius = incomingRadius;
}
void update(){
plot();
}
void plot(){
for(int i = 0; i < fftLog.avgSize(); i++){
int w = int(width/fftLog.avgSize());
x = i*w;
y = frameCount*5;
z = height/4-fftLog.getAvg(i)*10;
stroke(0);
point(x, y, z);
pts[i] = new PVector(x, y, z);
trace = (PVector[]) expand(trace, trace.length+1);
trace[trace.length-1] = new PVector(pts[i].x, pts[i].y, pts[i].z);
}
}
void plotTrace(){
/*
//drawing points
for(int i=0; i