Laser Cut Record

252K704378

Intro: Laser Cut Record


A few months back, I wrote about how I used a 3D printer to transform any mp3 into a physical record.  Though all the documentation for that project is available here, and the 3D models could potentially be printed through an online fabrication service, I knew that the barrier to entry for normal people interested in trying out the process themselves was prohibitively high.  With this project I wanted to try to extend the idea of digitally fabricated records to use relatively common and affordable machines and materials so that (hopefully) more people can participate, experiment, and actually use all this documentation I've been writing.

These records were cut on an Epilog 120 Watt Legend EXT to a theoretical precision of 1200dpi (the kerf of the cut and some tricks I used to avoid crashing the laser cutter dropped the actual precision down by ~1/6).  The audio on the records has a bit depth between 4-5 (typical mp3 audio is 16 bit) and a sampling rate up to about 4.5kHz (mp3 is 44.1kHz).  So far I've successfully cut audio on wood (figs 1-2), acrylic (figs 3-4), and paper (figs 5-6), and I'm sure there are many more materials that would work.  I wrote the Processing sketch that generates the record cutting paths so that it can be modified for any song, material, cutting machine, record size, and turntable speed (skip ahead to download the code and learn how to make your own records).

You should also note that in this Instructable I'll demonstrate specifically how I used a laser cutter for this process, but the cutting files I'm using are standard vector graphics in a PDF format, so they can be extended to many other digital fabrication tools. For example, I'm curious to see if it's possible to use a CNC mill or a CNC razor blade paper cutter with my cutting files (a group of people were able to cut out some sine waves on paper using a Cameo in this Instructable).

Below are some of my final results, read on to see how they were made and how you can make your own.

Joy Division - Love Will Tear Us Apart on clear acrylic (download vector files):


Radiohead - Idioteque on wood (download vector files):


The Velvet Underground and Nico - Femme Fatale on maple (download vector files):


The Velvet Underground - Sunday Morning on maple (download vector files):

STEP 1: How Does a Record Work?

I've explained a bit about how a record works and the scale of vinyl microgrooves in my 3d printed record project.  The main difference between these laser cut records and my 3d printed records is the axis that the grooves are cut on.  Since I can't control the power of the laser while it is cutting a vector path, the laser cut records are cut laterally on the surface of the material.  This means that the needle only vibrates in the plane parallel to platter of the turntable.  The 3d printed records are "cut" vertically, meaning the needle vibrates in the plane perpendicular to the platter.  I chose to modulate the grooves vertically for the 3d printed records because the vertical axis is the most precise axis on the machine (resolution of 16 microns).

Stereo (2 channel) vinyl records are cut both vertically and laterally, this way it's possible for two isolated channels of audio to fit into one groove.  Mono vinyls are cut laterally only, this is because the vertical cuts can become distorted, especially if you try to increase the amplitude of your waveform to increase the dynamic range of the sound.  Although I didn't really have a choice in the matter, it's better to to cut a mono groove laterally.

To give you an idea of the size of the grooves on a modern record, check out the image above from Chris Supranowitz, a researcher at The Institute of Optics at the University of Rochester.  This is a close up image of a vinyl record, taken with an electron microscope.  The dark objects in the grooves are tiny particles of dust.  The laser cutter cannot make such precise cuts because the width of the beam is too large, so the grooves on my records are about 1-2 orders of magnitude larger in every dimension than these grooves.

STEP 2: Laser Cutter Specs

The lasers in our office are Epilog 120 Watt Legend 36EXT.  They have a 36"x24" cutting bed, big enough to cut several 12" records at a time.  They have a max resolution of 1200dpi in the x and y axes and 100 power and speed settings to control cutting depth.

Before I started cutting anything, I used these numbers to calculate the resolution I'd be able to achieve.  First I wanted to make sure that I would be able to get a good sampling rate on my audio.  Sampling rate is the amount of samples per second in a song.  Usually the sampling rate is 44.1kHz (or 44,100 samples a second).  When the sampling rate drops below about 40kHz the higher frequencies of a song start losing their detail, but depending on the song you can go down to 20 or even 10kHz sampling rate without too much of a problem.

To calculate the sampling rate, I used the following relationship:

sampling frequency = (resolution per inch)*(inches per revolution)*(revolutions per second)
(in order to maximize the sampling frequency, we want all of these numbers (res/inch, inch/rev, rev/sec) to be as high as possible)
also notice how the sampling rate will decrease as the needle moves towards the center of the record (smaller inches/revolution)

First I'll start with revolutions per second.  Record players typically play at two different speeds: 33.3 and 45rpm.  (Some record players also have a 78rpm speed, but this is less common and only used for very old records).  If I use the higher, 45rpm speed I can calculate revolutions per second as follows:

revolutions per second = (revolutions per minute)/(seconds per minute)
revolutions per second = 45/60 = 0.75


Next is inches per revolution, this number depends on the circumference of the disk where the needle is hitting it. The largest sized records are 12" in diameter (30cm).  According to the RIAA standards, the outermost groove of a 12" record falls at a radius of 5.75" and the innermost groove falls at about 2.25".  I'll use these numbers to determine the range of sampling rates I can achieve at 33 and 45rpm.  The circumference (the distance in inches traveled by the needle during one revolution of the record) is calculated as follows:

inches per revolution = 2*pi*(radius of needle)
max inches per revolution = 2*pi*5.75 =~ 36
min inches per revolution = 2*pi*2.35 =~ 15


We already know that the resolution per inch of the laser cutter is 1200 (1200 dpi  in the x and y axes).  So combining this all we get:

sampling frequency = (resolution per inch)*(inches per revolution)*(revolutions per second)
max sampling frequency at 45 rpm = 1200*36*0.75 =~ 32400 = 32.4kHz

min sampling frequency at 45 rpm = 1200*15*0.75 =~ 13500 = 13.5kHz

This is a pretty good starting point.  If I scale this to 33.3rpm instead of 45 (this will allow me to fit more music on the record) the sampling rate becomes:

max sampling frequency at 33 rpm = 1200*36*0.5 =~ 21600 = 21.6kHz
min sampling frequency at 33 rpm = 1200*15*0.5 =~ 9000 = 9kHz


This is still easily enough to reproduce a recognizable song.

The next thing that I needed to think about was the bit depth.  Bit depth is the resolution of the audio data.  Most audio these days in 16 bit, meaning each sample can have one of 65536 (2^16) possible values.  8 bit audio has only 256 (2^8) steps of resolution and still sounds pretty close to the original.  (Music that is commonly referred to as "8-bit" like the music in early Nintendo games is actually 1 bit resolution, this low resolution is what gives it its unique and instantly recognizable sound, but I'm aiming for something that sounds a little more organic).

As I said in the last step, the grooves on these records are cut laterally.  The following equation calculates the horizontal distance that the needle will move as it traces the a wave of a given bit depth:

horizontal displacement of needle = (2^bit depth)*(precision of x/y axes)
where the precision of the x and y axes is 1200dpi or about 21 microns.  I used this to calculate the following table:

bit depth          horizontal displacement                steps of resolution

     2                             84um                                          4
     3                            168um                                         8
     4                            336um                                        16

5                            672um                                        32
     6                          1.344mm                                      64
     7                          2.688mm                                     128
     8                          5.376mm                                     256

The bolded rows in the table are the numbers that I wanted to shoot for with this project.  Although a horizontal displacement of about 0.5mm is quite large compared to a normal record, I think somewhere in that range will work.

STEP 3: Sine Tests

As with my 3d printed records, I started off by printed out some sine wave tests to get an idea of what kind of frequency range I can achieve and to test out some parameters (laser power, cutting speed, material, wave amplitude).  I used Processing to generate sinusoidal paths and cut these first tests on white 3mm acrylic.

Here's the Processing code I used:
//sine test with processing


import processing.pdf.*;

int scaleNum = 72;//scale factor of vectors (default 72 dpi)

float theta;//angle variable
float thetaIter = 88200;//how many values of theta per cycle
float radius;//variable to calculate radius of grooves
float diameter = 11.8;//diameter of record in inches
float innerHole = 0.286;//diameter of center hole in inches
float innerRad = 2.2;//radius of innermost groovein inches
float outerRad = 5.75;//radius of outermost groove in inches
//storage for a given point in the groove
float radCalc;
float xVal;
float yVal;
float dpi = 1200;//dpi of cutter

float minDist = 4.0;//min pixel spacing between points in vector path (to prevent cutter from freaking out) 0.25919998
float xValLast = 0.0;
float yValLast = 0.0;

//parameters to test
float amplitude[] = {4,6,8};//in pixels
int frequency[] = {2000,1000,500,250};//cycles per rotation

void setup(){
  
  size(12*scaleNum,12*scaleNum);
  beginRecord(PDF,"test1.pdf");//save as PDF
  background(255);//white background
  noFill();//don't fill loops
  strokeWeight(0.001);//hairline width
  
  //draw sine waves
  float incrNum = TWO_PI/thetaIter;//calculcate inrementation amount
  radius = outerRad*scaleNum;//calculate outermost radius (at 5.75")
  
  //scale pixel distances
  for (byte i=0;i<3;i++){
    amplitude[i] =  amplitude[i]/dpi*scaleNum;
  }
  minDist =  minDist/dpi*scaleNum;
  
  stroke(255,0,0);//red
  for(byte frequencyi=0;frequencyi<4;frequencyi++){//four sine frequencies
    for(byte amplitudei=0;amplitudei<3;amplitudei++){//three groove lateral amplitudes
      for(byte copies=0;copies<1;copies++){//two copies
        beginShape();
        int numpoints = 0;
        for(theta=0;theta<TWO_PI;theta+=incrNum){//for theta between 0 and 2pi
          //calculate new point
          radCalc = radius+amplitude[amplitudei]*sin(frequency[frequencyi]*theta);
          xVal = width/2+radCalc*cos(theta);
          yVal = height/2+radCalc*sin(theta);
          if((((xValLast-xVal)*(xValLast-xVal)+(yValLast-yVal)*(yValLast-yVal))>(minDist*minDist))||(theta==0)){
            vertex(xVal,yVal);
            //store last coordinates in vector path
            xValLast = xVal;
            yValLast = yVal;
            numpoints++;
          }
        }
        endShape(CLOSE);
        println(numpoints);
        radius -= 2*amplitude[amplitudei]+0.02*scaleNum;//separate each copy
      }
      radius -= 0.02*scaleNum;
    }
    radius -= 0.01*scaleNum;
  }
  
  //draw cut lines (100 units = 1")
  stroke(0);//draw in black
  ellipse(width/2,height/2,innerHole*scaleNum,innerHole*scaleNum);//0.286" center hole
  ellipse(width/2,height/2,diameter*scaleNum,diameter*scaleNum);//12" diameter outer edge 
  
  endRecord();
  exit();
  
  //tell me when it's over
  println("Finished.");
  
}


and here's a video of the results:


(the 139hz sine wave may be too low to hear with laptop speakers)
I was really happy with these first tests.  These is some noise in the background, but it's very consistent and the signal to noise ratio is pretty good.  Amplitudes of 4 and 6 sounds good across the frequencies tested here, as the frequency gets higher, you can hear a good amount of distortion on the amplitude 8 wave.

Different lasers and brands of laser cutter will respond differently, but this record was cut at 5000 freq with 100 speed (although the cutting head was moving very slowly due to the density of points on my vector path) and 12 power (enough to etch the surface but not to cut all the way through).

STEP 4: Audio Tests on Acrylic

I did a ton of sine tests for my 3D printed record, but I was anxious to launch into the audio for this much sooner so I just went for it.  Even though on some level I knew this would be a bit of a disaster, here's my first attempt with audio:


The song is Love Will Tear Us Apart by Joy Division.  My favorite of all the records I 3d printed was definitely the Joy Division one (the song Disorder), I like the creepy vibe the distortion gives it.

This laser cut joy division track is not quite there yet (though decently recognizable).  You can hear a lot of crunchiness on the drum beats, if you could look closely at the record, you would see that these areas of high frequencies were melted into oblivion by the cutter.  In this attempt I didn't make any effort to set a max frequency of the cuts, and the tighter cuts required by these sections apparently caused the laser to linger too long on the material.

I learned some things about the laser cutter from this attempt.  The first couple of tries I made on this cut caused the laser to freeze up almost immediately.  At first I thought I was overloading the machine with data, but then I realized that the machine does not like to receive extremely dense vector paths.  In fact, I found that if two points on a vector path are within about 6 pixels of each other, the laser will quit.  I had to amend my code to account for this.  Here is the Processing code I used:
//audio tests
//by Amanda Ghassaei
//Jan 2013
//https://www.instructables.com/id/Laser-Cut-Record/

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
*/


import processing.pdf.*;

//parameters
String filename = "lovewilltearusapart.txt";
float rpm = 33.3;
float samplingRate = 44100;
float dpi = 1200;//dpi of cutter
float amplitude = 16;//in pixels
int frequency = 500;//cycles per rotation
float spacing = 10;//space between grooves (in pixels)
float minDist = 6.0;//min pixel spacing between points in vector path (to prevent cutter from freaking out) 0.25919998
float diameter = 11.8;//diameter of record in inches
float innerHole = 0.286;//diameter of center hole in inches
float innerRad = 5;//2.2;//radius of innermost groove in inches
float outerRad = 5.75;//radius of outermost groove in inches
boolean cutlines = false;

//constants
float secPerMin = 60;
int scaleNum = 72;//scale factor of vectors (default 72 dpi)

//storage for a given point in the groove
float radCalc;
float xVal;
float yVal;
float theta;//angle variable
float thetaIter = samplingRate/2*secPerMin/rpm;//how many values of theta per cycle
float radius;//variable to calculate radius of grooves

float xValLast = 0.0;
float yValLast = 0.0;

void setup(){
  
  println(thetaIter);
  
  size(36*scaleNum,24*scaleNum);
  beginRecord(PDF, filename + ".pdf");//save as PDF
  background(255);//white background
  noFill();//don't fill loops
  strokeWeight(0.001);//hairline width
  
  //scale pixel distances
  amplitude =  amplitude/dpi*scaleNum;
  minDist =  minDist/dpi*scaleNum;
  spacing =  spacing/dpi*scaleNum;
  
  //draw sine waves
  float incrNum = TWO_PI/thetaIter;//calculcate angular inrementation amount
  float radIncrNum = (2*amplitude+spacing)/thetaIter;//radial incrementation amount
  radius = outerRad*scaleNum;//calculate outermost radius (at 5.75")
  
  stroke(255,0,0);//red
  beginShape();
  
//  //draw silent outer groove
//  for(theta=0;theta<TWO_PI;theta+=incrNum){//for theta between 0 and 2pi
//    //calculate new point
//    radCalc = radius;
//    xVal = width/6+radCalc*cos(theta);
//    yVal = height/4-radCalc*sin(theta);
//    if(((xValLast-xVal)*(xValLast-xVal)+(yValLast-yVal)*(yValLast-yVal))>(minDist*minDist)){
//      vertex(xVal,yVal);
//      //store last coordinates in vector path
//      xValLast = xVal;
//      yValLast = yVal;
//    }
//    radius -= radIncrNum;//decreasing radius forms spiral
//  }
  
  float[] songData = processAudioData();
  
  int numGrooves = 0;
  int index = 0;
  while(radius>innerRad*scaleNum){
    int numpoints = 0;
    for(theta=0;theta<TWO_PI;theta+=incrNum){//for theta between 0 and 2pi
      //calculate new point
      radCalc = radius+songData[index];
      index+=2;
      xVal = width/6+radCalc*cos(theta);
      yVal = height/4-radCalc*sin(theta);
      if(((xValLast-xVal)*(xValLast-xVal)+(yValLast-yVal)*(yValLast-yVal))>(minDist*minDist)){
        vertex(xVal,yVal);
        //store last coordinates in vector path
        xValLast = xVal;
        yValLast = yVal;
        numpoints++;
      }
      radius -= radIncrNum;//decreasing radius forms spiral
    }
    numGrooves++;
    println(numGrooves);
    println(numpoints);
  }
  endShape();
  
  if (cutlines){
    //draw cut lines (100 units = 1")
    stroke(0);//draw in black
    ellipse(width/6,height/4,innerHole*scaleNum,innerHole*scaleNum);//0.286" center hole
    ellipse(width/6,height/4,diameter*scaleNum,diameter*scaleNum);//12" diameter outer edge 
  }
  
  endRecord();
  exit();
  
  //tell me when it's over
  println("Finished.");
  
}

float[] processAudioData(){
  
  //get data out of txt file
  String rawData[] = loadStrings(filename);
  String rawDataString = rawData[0];
  float audioData[] = float(split(rawDataString,','));//separated by commas
  
  //normalize audio data to given bitdepth
  //first find max val
  float maxval = 0;
  for(int i=0;i<audioData.length;i++){
    if (abs(audioData[i])>maxval){
      maxval = abs(audioData[i]);
    }
  }
  //normalize amplitude to max val
  for(int i=0;i<audioData.length;i++){
    audioData[i]*=amplitude/maxval;
  }
  
  return audioData;
}


As with the 3d printed record, I pulled the raw audio data from the original wav file using Python before sending it to Processing, that code can be found here.  And again, if someone knows a way to bypass this step, please feel free to leave a comment, I would much rather keep everything in Processing.

In my next test I set a limit on the angular distance between consecutive points, hoping to minimize melting of the material.  Here's the code:

   if(((xValLast-xVal)*(xValLast-xVal)+(yValLast-yVal)*(yValLast-yVal))>(minDist*minDist) && radCalc*abs(thetaLast-theta)>minAngDist){
        vertex(xVal,yVal);
        //store last coordinates in vector path
        xValLast = xVal;
        yValLast = yVal;
        thetaLast = theta;
        numpoints++;
    }


...and the video:


The cut came out much cleaner, and you can hear significantly less distortion on the audio, but I thought I could still make it better.  In the next test I set the samples per revolution to a constant number (6000) and removed the minimum angular distance logic from my code.

At 6000 samples per cycle the sampling frequency of the audio is:

samples/sec  = samples/rev * rev/min * min/sec
samples/sec  = 6000 * 45 * 1/60 = 4.5kHz


Here's the code:
//audio tests
//by Amanda Ghassaei
//Jan 2013
//https://www.instructables.com/id/Laser-Cut-Record/

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
*/


import processing.pdf.*;

//parameters
String filename = "lovewilltearusapart.txt";
float rpm = 33.3;
float samplingRate = 44100;
float dpi = 1200;//dpi of cutter
float amplitude = 16;//in pixels
float spacing = 10;//space between grooves (in pixels)
float minDist = 6.0;//min pixel spacing between points in vector path (to prevent cutter from freaking out) 0.25919998
float diameter = 11.8;//diameter of record in inches
float innerHole = 0.286;//diameter of center hole in inches
float innerRad = 5;//2.2;//radius of innermost groove in inches
float outerRad = 5.75;//radius of outermost groove in inches
boolean cutlines = false;//cut the inner and outer perimeters

//constants
float secPerMin = 60;
int scaleNum = 72;//scale factor of vectors (default 72 dpi)

//storage for a given point in the groove
float radCalc;
float xVal;
float yVal;
float theta;//angle variable
float thetaIter = 6000;//how many values of theta per cycle
float radius;//variable to calculate radius of grooves

float xValLast = 0.0;
float yValLast = 0.0;

void setup(){
  
  size(36*scaleNum,24*scaleNum);
  beginRecord(PDF, filename + ".pdf");//save as PDF
  background(255);//white background
  noFill();//don't fill loops
  strokeWeight(0.001);//hairline width
  
  //scale pixel distances
  amplitude =  amplitude/dpi*scaleNum;
  minDist =  minDist/dpi*scaleNum;
  spacing =  spacing/dpi*scaleNum;
  
  //draw sine waves
  float incrNum = TWO_PI/thetaIter;//calculcate angular inrementation amount
  float radIncrNum = (2*amplitude+spacing)/thetaIter;//radial incrementation amount
  radius = outerRad*scaleNum;//calculate outermost radius (at 5.75")
  
  stroke(255,0,0);//red
  beginShape();
  
//  //draw silent outer groove
//  for(theta=0;theta<TWO_PI;theta+=incrNum){//for theta between 0 and 2pi
//    //calculate new point
//    radCalc = radius;
//    xVal = width/6+radCalc*cos(theta);
//    yVal = height/4-radCalc*sin(theta);
//    if(((xValLast-xVal)*(xValLast-xVal)+(yValLast-yVal)*(yValLast-yVal))>(minDist*minDist)){
//      vertex(xVal,yVal);
//      //store last coordinates in vector path
//      xValLast = xVal;
//      yValLast = yVal;
//    }
//    radius -= radIncrNum;//decreasing radius forms spiral
//  }
  
  float[] songData = processAudioData();
  
  int numGrooves = 0;
  int index = 0;
  while(radius>innerRad*scaleNum){
    int numpoints = 0;
    for(theta=0;theta<TWO_PI;theta+=incrNum){//for theta between 0 and 2pi
      //calculate new point
      radCalc = radius+songData[index];
      index+=(samplingRate*secPerMin/rpm)/thetaIter;//go to next spot in audio data
      xVal = width/6+radCalc*cos(theta);
      yVal = 3*height/4-radCalc*sin(theta);
      if(((xValLast-xVal)*(xValLast-xVal)+(yValLast-yVal)*(yValLast-yVal))>(minDist*minDist)){
        vertex(xVal,yVal);
        //store last coordinates in vector path
        xValLast = xVal;
        yValLast = yVal;
        numpoints++;
      }
      radius -= radIncrNum;//decreasing radius forms spiral
    }
    numGrooves++;
    println(numGrooves);
    println(numpoints);
  }
  endShape();
  
  if (cutlines){
    //draw cut lines (100 units = 1")
    stroke(0);//draw in black
    ellipse(width/6,3*height/4,innerHole*scaleNum,innerHole*scaleNum);//0.286" center hole
    ellipse(width/6,3*height/4,diameter*scaleNum,diameter*scaleNum);//12" diameter outer edge 
  }
  
  endRecord();
  exit();
  
  //tell me when it's over
  println("Finished.");
  
}

float[] processAudioData(){
  
  //get data out of txt file
  String rawData[] = loadStrings(filename);
  String rawDataString = rawData[0];
  float audioData[] = float(split(rawDataString,','));//separated by commas
  
  //normalize audio data to given bitdepth
  //first find max val
  float maxval = 0;
  for(int i=0;i<audioData.length;i++){
    if (abs(audioData[i])>maxval){
      maxval = abs(audioData[i]);
    }
  }
  //normalize amplitude to max val
  for(int i=0;i<audioData.length;i++){
    audioData[i]*=amplitude/maxval;
  }
  
  return audioData;
}


and the video:


Though it's a little hard to hear because of all the skipping, the cut came out much cleaner on this test.  You can also hear that the audio sounds slowed down, this was a rounding issue in my code that I dealt with later.  In my next test I decreased the amplitude of the wave to 12px to see if I could get the needle to stay in the groove.

    float amplitude = 12;


There are still a few issues.  Most notably, the record is warped from the cutting process.  Also the speed of the audio is still screwed up.  In my final version I fixed the speed issue (it was a rounding problem) and tried taping the acrylic down to the bed to see if that would help with the warping.  The settings I used on the laser cutter are:

laser settings (epilog 120W)
100 speed
5000freq
12 power


In this cut I actually applied the proper RIAA equalization as well and used an anti-aliasing low pass filter of samplingRate/2 = 2.25kHz.

Here is the final code:
//audio tests
//by Amanda Ghassaei
//Jan 2013
//https://www.instructables.com/id/Laser-Cut-Record/

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
*/


import processing.pdf.*;

//parameters
String filename = "idioteque.txt";
float rpm = 45.0;
float samplingRate = 44100;
float dpi = 1200;//dpi of cutter
float amplitude = 10;//in pixels
float spacing = 10;//space between grooves (in pixels)
float minDist = 6.0;//min pixel spacing between points in vector path (to prevent cutter from freaking out) 0.25919998
float diameter = 11.8;//diameter of record in inches
float innerHole = 0.286;//diameter of center hole in inches
float innerRad = 2.2;//2.2;//radius of innermost groove in inches
float outerRad = 5.75;//radius of outermost groove in inches
boolean cutlines = true;//cut the inner and outer perimeters

//constants
float secPerMin = 60;
int scaleNum = 72;//scale factor of vectors (default 72 dpi)

//storage for a given point in the groove
float radCalc;
float xVal;
float yVal;
float theta;//angle variable
float thetaIter = 5880;//how many values of theta per cycle
float radius;//variable to calculate radius of grooves

float xValLast = 0.0;
float yValLast = 0.0;

void setup(){
  
  size(36*scaleNum,24*scaleNum);
  beginRecord(PDF, filename + ".pdf");//save as PDF
  background(255);//white background
  noFill();//don't fill loops
  strokeWeight(0.001);//hairline width
  
  //scale pixel distances
  amplitude =  amplitude/dpi*scaleNum;
  minDist =  minDist/dpi*scaleNum;
  spacing =  spacing/dpi*scaleNum;
  
  //draw sine waves
  float incrNum = TWO_PI/thetaIter;//calculcate angular inrementation amount
  float radIncrNum = (2*amplitude+spacing)/thetaIter;//radial incrementation amount
  radius = outerRad*scaleNum;//calculate outermost radius (at 5.75")
  
  stroke(255,0,0);//red
  beginShape();
  
  //draw silent outer groove
  for(theta=0;theta<TWO_PI;theta+=incrNum){//for theta between 0 and 2pi
    //calculate new point
    radCalc = radius;
    xVal = width/6+radCalc*cos(theta);
    yVal = height/4-radCalc*sin(theta);
    if(((xValLast-xVal)*(xValLast-xVal)+(yValLast-yVal)*(yValLast-yVal))>(minDist*minDist)){
      vertex(xVal,yVal);
      //store last coordinates in vector path
      xValLast = xVal;
      yValLast = yVal;
    }
    radius -= radIncrNum;//decreasing radius forms spiral
  }
  
  float[] songData = processAudioData();
  
  int numGrooves = 0;
  int index = 0;
  int indexIncr = int((samplingRate*secPerMin/rpm)/thetaIter);
  while(radius>innerRad*scaleNum && index < songData.length-6000){
    int numpoints = 0;
    for(theta=0;theta<TWO_PI;theta+=incrNum){//for theta between 0 and 2pi
      //calculate new point
      radCalc = radius+songData[index];
      index+=indexIncr;//go to next spot in audio data
      xVal = width/6+radCalc*cos(theta);
      yVal = height/4-radCalc*sin(theta);
      if(((xValLast-xVal)*(xValLast-xVal)+(yValLast-yVal)*(yValLast-yVal))>(minDist*minDist)){
        vertex(xVal,yVal);
        //store last coordinates in vector path
        xValLast = xVal;
        yValLast = yVal;
        numpoints++;
      }
      radius -= radIncrNum;//decreasing radius forms spiral
    }
    numGrooves++;
    println(numGrooves);
    println(numpoints);
  }
  
  //draw silent inner locked groove
  for(theta=0;theta<TWO_PI;theta+=incrNum){//for theta between 0 and 2pi
    //calculate new point
    radCalc = radius;
    xVal = width/6+radCalc*cos(theta);
    yVal = height/4-radCalc*sin(theta);
    if(((xValLast-xVal)*(xValLast-xVal)+(yValLast-yVal)*(yValLast-yVal))>(minDist*minDist)){
      vertex(xVal,yVal);
      //store last coordinates in vector path
      xValLast = xVal;
      yValLast = yVal;
    }
    radius -= radIncrNum;//decreasing radius forms spiral
  }
  for(theta=0;theta<TWO_PI;theta+=incrNum){//for theta between 0 and 2pi
    //calculate new point
    xVal = width/6+radius*cos(theta);
    yVal = height/4-radius*sin(theta);
    if(((xValLast-xVal)*(xValLast-xVal)+(yValLast-yVal)*(yValLast-yVal))>(minDist*minDist)){
      vertex(xVal,yVal);
      //store last coordinates in vector path
      xValLast = xVal;
      yValLast = yVal;
    }
  }
  
  endShape();
  
  if (cutlines){
    //draw cut lines (100 units = 1")
    stroke(0);//draw in black
    ellipse(width/6,height/4,innerHole*scaleNum,innerHole*scaleNum);//0.286" center hole
    ellipse(width/6,height/4,diameter*scaleNum,diameter*scaleNum);//12" diameter outer edge 
  }
  
  endRecord();
  exit();
  
  //tell me when it's over
  println("Finished.");
  
}

float[] processAudioData(){
  
  //get data out of txt file
  String rawData[] = loadStrings(filename);
  String rawDataString = rawData[0];
  float audioData[] = float(split(rawDataString,','));//separated by commas
  
  //normalize audio data to given bitdepth
  //first find max val
  float maxval = 0;
  for(int i=0;i<audioData.length;i++){
    if (abs(audioData[i])>maxval){
      maxval = abs(audioData[i]);
    }
  }
  //normalize amplitude to max val
  for(int i=0;i<audioData.length;i++){
    audioData[i]*=amplitude/maxval;
  }
  
  return audioData;
}



and the final product:


There was still some warping, but the tempo issues are completely resolved.  It's interesting to hear how much the audio quality degrades from the outside of the disk to the middle of the disk - this is due to lowered surface speed of the record as you move toward the center (explained in step 2).

STEP 5: Audio Tests on Paper

Next I tried this out with paper, first I used a sheet of thicker black (almost cardstock) paper.  I used the song rebel rebel as my test.

The settings on the laser were as follows:
speed 100
power 4
frequency 500


The resulting records looked great (see image above), and you can even hear the song coming through, but it had a lot of trouble with skipping:


So I picked up some new paper, this time it was much thicker, almost like thin cardboard.  I did some sine tests and found that setting the laser power to 7 was the deepest I could cut without going through the paper.  Then I did another test of rebel rebel, this time cut at 7 power and with the amplitude of the wave lowered to 10 (from 12).  The needle still will not stay in the groove by itself:


I also tried defocusing the laser from the paper to widen the cut (still at amp 12).  The inner cut is more defocused than the outer, notice how the needle is more stable, but the audio quality is lower.


Since the defocusing seemed to be the only way to keep the needle in the grooves, I tried defocusing the laser by reproducible distances to find the perfect balance between needle stability and low noise.  To do this I placed anywhere from 4 to 16 pieces of white printer paper on top of the cardstock while I ran the laser's autofocus.  This meant that the focal point of the laser would actually be a fraction of an inch above the surface of the cardstock.  That test is shown below, the outer rings are defocused by 4 sheets, the next 7, 10, 13, and 16 sheets.



I concluded that about 10-12 sheets defocused was the absolute minimum needed to keep the needle stabilized in the groove.

STEP 6: Audio Tests on Wood

Finally, I cut some records on wood.  Eventually I'd like to cut a record on a 12" wood round with a raw edge, kind of like this, you could cut the grooves right onto the rings of the tree.  For now I have some nice maple sheet to cut, but I did my firsts tests on ply.  I started by using similar settings that I used on the acrylic:

power = 12
speed = 100
freq = 500
amp = 12




This cut looked great, but the needle skipped a lot, I had to hold it in place to shoot this video.  I went back and did some sine wave tests and found that 15 power was more stable, so I ran another audio test at 15 power:


This cut was much more stable, but skipping was still an issue, you can also hear the same tempo issues I was having with the acrylic (I was cutting all these records at the same time).  Next I tested lowering the amplitude of the cut to 10:


I also tried defocusing the laser to widen the groove, keeping the amplitude at 12.  The outer groove is slightly defocused, the inner grove is more heavily defocused:


Both defocusing and lowering the amplitude of the cut helped to minimize skipping, but the defocusing introduced more noise into the audio.  For my final plywood test I lowered the amplitude to 10 and kept the laser focused, I also fixed the tempo issue in my code.  Here is the result:


Unfortunately at this point I was using the last of our plywood stock, so I couldn't be too picky about how flat the ply was, but it still (surprisingly) plays fine(ish).  Here's the code I used:
//audio tests
//by Amanda Ghassaei
//Jan 2013
//https://www.instructables.com/id/Laser-Cut-Record/

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
*/

import processing.pdf.*;

//parameters
String filename = "radiohead.txt";
float rpm = 45.0;
float samplingRate = 44100;
float dpi = 1200;//dpi of cutter
float amplitude = 10;//in pixels
float spacing = 10;//space between grooves (in pixels)
float minDist = 6.0;//min pixel spacing between points in vector path (to prevent cutter from freaking out) 0.25919998
float diameter = 11.8;//diameter of record in inches
float innerHole = 0.286;//diameter of center hole in inches
float innerRad = 2.2;//radius of innermost groove in inches
float outerRad = 5.75;//radius of outermost groove in inches
boolean cutlines = false;//cut the inner and outer perimeters

//constants
float secPerMin = 60;
int scaleNum = 72;//scale factor of vectors (default 72 dpi)

//storage for a given point in the groove
float radCalc;
float xVal;
float yVal;
float theta;//angle variable
float thetaIter = 5880;//how many values of theta per cycle
float radius;//variable to calculate radius of grooves

float xValLast = 0.0;
float yValLast = 0.0;

void setup(){
  
  size(36*scaleNum,24*scaleNum);
  beginRecord(PDF, filename + ".pdf");//save as PDF
  background(255);//white background
  noFill();//don't fill loops
  strokeWeight(0.001);//hairline width
  
  //scale pixel distances
  amplitude =  amplitude/dpi*scaleNum;
  minDist =  minDist/dpi*scaleNum;
  spacing =  spacing/dpi*scaleNum;
  
  //draw sine waves
  float incrNum = TWO_PI/thetaIter;//calculcate angular inrementation amount
  float radIncrNum = (2*amplitude+spacing)/thetaIter;//radial incrementation amount
  radius = outerRad*scaleNum;//calculate outermost radius (at 5.75")
  
  stroke(255,0,0);//red
  beginShape();
  
  //draw silent outer groove
  for(theta=0;theta<TWO_PI;theta+=incrNum){//for theta between 0 and 2pi
    //calculate new point
    radCalc = radius;
    xVal = width/6+radCalc*cos(theta);
    yVal = height/4-radCalc*sin(theta);
    if(((xValLast-xVal)*(xValLast-xVal)+(yValLast-yVal)*(yValLast-yVal))>(minDist*minDist)){
      vertex(xVal,yVal);
      //store last coordinates in vector path
      xValLast = xVal;
      yValLast = yVal;
    }
    radius -= radIncrNum;//decreasing radius forms spiral
  }
  
  float[] songData = processAudioData();
  
  int numGrooves = 0;
  int index = 0;
  int indexIncr = int((samplingRate*secPerMin/rpm)/thetaIter);
  while(radius>innerRad*scaleNum && index < songData.length-thetaIter*indexIncr){
    int numpoints = 0;
    for(theta=0;theta<TWO_PI;theta+=incrNum){//for theta between 0 and 2pi
      //calculate new point
      radCalc = radius+songData[index];
      index+=indexIncr;//go to next spot in audio data
      xVal = width/6+radCalc*cos(theta);
      yVal = height/4-radCalc*sin(theta);
      if(((xValLast-xVal)*(xValLast-xVal)+(yValLast-yVal)*(yValLast-yVal))>(minDist*minDist)){
        vertex(xVal,yVal);
        //store last coordinates in vector path
        xValLast = xVal;
        yValLast = yVal;
        numpoints++;
      }
      radius -= radIncrNum;//decreasing radius forms spiral
    }
    numGrooves++;
    println(numGrooves);
    println(numpoints);
  }
  
  //draw silent inner locked groove
  for(theta=0;theta<TWO_PI;theta+=incrNum){//for theta between 0 and 2pi
    //calculate new point
    radCalc = radius;
    xVal = width/6+radCalc*cos(theta);
    yVal = height/4-radCalc*sin(theta);
    if(((xValLast-xVal)*(xValLast-xVal)+(yValLast-yVal)*(yValLast-yVal))>(minDist*minDist)){
      vertex(xVal,yVal);
      //store last coordinates in vector path
      xValLast = xVal;
      yValLast = yVal;
    }
    radius -= radIncrNum;//decreasing radius forms spiral
  }
  for(theta=0;theta<TWO_PI;theta+=incrNum){//for theta between 0 and 2pi
    //calculate new point
    xVal = width/6+radius*cos(theta);
    yVal = height/4-radius*sin(theta);
    if(((xValLast-xVal)*(xValLast-xVal)+(yValLast-yVal)*(yValLast-yVal))>(minDist*minDist)){
      vertex(xVal,yVal);
      //store last coordinates in vector path
      xValLast = xVal;
      yValLast = yVal;
    }
  }
  
  endShape();
  
  if (cutlines){
    //draw cut lines (100 units = 1")
    stroke(0);//draw in black
    ellipse(width/6,height/4,innerHole*scaleNum,innerHole*scaleNum);//0.286" center hole
    ellipse(width/6,height/4,diameter*scaleNum,diameter*scaleNum);//12" diameter outer edge 
  }
  
  endRecord();
  exit();
  
  //tell me when it's over
  println("Finished.");
  
}

float[] processAudioData(){
  
  //get data out of txt file
  String rawData[] = loadStrings(filename);
  String rawDataString = rawData[0];
  float audioData[] = float(split(rawDataString,','));//separated by commas
  
  //normalize audio data to given bitdepth
  //first find max val
  float maxval = 0;
  for(int i=0;i<audioData.length;i++){
    if (abs(audioData[i])>maxval){
      maxval = abs(audioData[i]);
    }
  }
  //normalize amplitude to max val
  for(int i=0;i<audioData.length;i++){
    audioData[i]*=amplitude/maxval;
  }
  
  return audioData;
}



Next I used the maple.  Fortunately, noahw helped me track down a 13" wide curly maple board and cut it into a two flat sheets: one was about the thickness of a real vinyl record at about 1/16" and other a little thicker at 1/8".  I sanded the maple sheets to about 1500grit and finished them before cutting.  I did a few tests on a piece of scrap and found that the power setting I was using on the ply was cutting so deeply into the maple that I would not be able to cut both sides.  I did a few more experiments with defocusing and actually did a full attempt using the song sunday morning by the velvet underground:


In this test I defocused the laser by the width of 11 sheets of normal printer paper.  I used the following laser settings:

power = 4
speed = 100
freq = 500
amp = 10


This cut came out a little noiser than I was hoping.  Since the needle wasn't having any issues staying in the groove, I defocused by only 9 sheets of paper and kept the same power settings.  On the other side of the sunday morning disc, I cut femme fatale:


While I was adjusting some tape to try to keep the wood lying flat in the middle of the cut, I accidentally bumped it to the side slightly.  If you look closely at the video, you'll see where the error is, it caused the needle to skip a groove, but other than that the cut came out great.

This song helped me pin down an error in my code, if you listen closely during the chorus, you'll notice that the backing vocals are missing.  When I looked back at the song, I noticed that the vocals are only found in the left channel of the track, so it seems that I was not combining the two channels before converting to a vector file.  I think I've fixed the problem in my python script, but I'll have to run a test to know for sure.  In the meantime, you can work around this problem by importing your stereo audio into Audacity, right clicking on the track and selecting "split stereo to mono", saving the file as a wav, opening that saved file in Audacity again, copying the track, and right clicking on each of the duplicate tracks to set one to left channel and one to right channel.

STEP 7: Make Your Own

You can convert your own audio files into vector cutting paths in ten easy steps:

1.  Download Processing.

2.  Download Python 2.5.4.

3.  Download Audacity.

4.  Download the code from GitHub (you can download the zip file by clicking on the cloud button).  Unzip and open the folder called LaserCutRecord.

5.  Open an audio file of your choice with Audacity.  Go to Effect>Equalization and select RIAA.  Hit inverse and apply, you should now hear the higher frequencies of your track boosted.

6.  Go to Effect>Low Pass Filter... and apply an anti-aliasing filter (a fancy word for a low pass filter) to your audio.  You will have to choose the cutoff frequency according to the max sampling rate that you can get with your cutter.  For example, my laser cutter melts anything above 2.5kHz at 45rpm, so I set my anti-aliasing filter cutoff to this same frequency.  Set the drop off as high as possible, for me this was 48dB/octave, that way the filter will have a hard cutoff.

7.  Use Effect>Amplify to amplify the signal as much as you can without noticeable effects of clipping (you will be able to get away with some clipping, and remember this is not crystal clear audio anyway). You may also want to mess around with Effect>>Compressor.

8.  Make sure there are 2 sec of blank audio at the end of the track so that nothing gets clipped and keep the audio under 3:10.  File>Export this file and save it in the "LaserCutRecord" folder as a wav file. 

9.  Open the Python file called "wavtotxt".  Copy the file name of the file you just saved in the line:

             fileName = "your_file_name_here.wav"

Hit Run>RunModule, after a minute or two you will have a .txt file saved in the Record Generator folder.

10.  Open the Processing sketch.  Change the name of the import file in the Processing sketch to your txt file name:

             String filename = "your_file_name_here.txt";

Run the Processing sketch Sketch>Run.  The Processing sketch will output several files, none larger than 700KB (I found that larger files were crashing the laser cutter).  The last file will also contain the cut paths for the inner hole and outer edge of the record, you will need to set your laser cutter to cut these lines at a higher power, so that it cuts all the way through the material.  Another very important note about cutting these files - the reason I had to split each song up into five parts is because I found that files larger than 800KB would crash my laser.  When you are cutting out the sequential files, you MUST shut down the laser for a second to clear it's memory and then turn it back on before sending it a new 700KB file to cut, you will have problems if you forget this.

Once you've made cutting files, post them!  You can upload files in the comments by clicking on "Rich Editor."  Enjoy, and let me know if you have questions or need help getting this to work.  I've tested this process Mac OS using the latest version of Processing.  If you actually end up cutting your own record, please post the results in the comments, I'm really curious to see where this code ends up!

In case you are stuck trying to find a machine to cut your files, check this list of worldwide hackerspaces, these are places where anyone can go for little to no money and use tools in a collaborative work environment.  If you are in school, you might ask the engineering or art departments if they have a machine than can cut vector files.  Otherwise, I'd recommend checking out an online fabrication service such as Ponoko.  Some people even build their own laser cutters, there are many builds documented right here on Instructables, we're even giving one away in our Epilog Challenge.

305 Comments

Hello, thank you for your great code.
I have run into one problem and I need help!

I have been trying to make a record with laser cutter.
(My laser cutter only takes ai data only.)
When I open the PDF into the illustrator, it doesn't show 1/3 of the line.
So it never showes the line perfectly.
If somebody knows the solution to this problem, plz help.

Thank you for your kind help.
I ran into this issue as well. I was sending 20 lines per file and was only receiving about 5.5. I adjusted the processing code to only send five lines per file and it worked. My PDF was showing all twenty lines but I think Illustrator got bogged down with all the points when it was reading the file.
A path in Illustrator can have a maximum of 8190 points before it simply and silently stops adding any more. In this case, it happens when Illustrator is opening the SVG or PDF files. In addition to mullerceramics suggestions, it is possible to get around this limit by writing an ExtendScript for Illustrator that takes the points from the Processing output and generates separate paths with 8190 points or less.
Hi thank you for the code. Im having trouble with getting the text file from the linear recolrd module. I'm not getting any errosrs, the code seems to be working fine, but I'm not getting a file in the end. It seems that I've looked everywhere. Have any of you had this issue?
Hey, great instructions. I am having an issue when i run the LinearRecord sketch (and also the LaserCut sketch. It keeps prompting the following:

"When not using the PDE, size() can only be used inside settings().
Remove the size() method from setup(), and add the following:
public void settings() {
size(7200, 360);
}
IllegalStateException: size() cannot be used here, see https://processing.org/reference/size_.html"

Has anyone else successfully done this recently as I know this article is now a few years old etc.


I used the LaserCut sketch this week and It was ok in Processing. Sorry I can't help you more.
Hello to everyone here ! I downloaded the different tracks to make some tests (joy division, radiohead etc) but I can't open them, on inkscape or on illustrator it seems to not work, and I feel frustrated about it ! Has someone had the same problem I did ? Did you find a solution ? :)

Hi, your program works, but my CNC machine reads only image files, not pdf. What can I do? Online converters aren't good Idea.

Can you help me please? Thanks :)

what kind of cnc? I think it's going to be nearly impossible to get this to work without doing it in vector format.

Hi, Amanda
I just got the same problem. Laser cutter in our lab just take SVG. file. It's also a vector format. Would it work if I transfer PDF to SVG?
could also just download GIMP and convert it. It handles PDF conversions quite nicely and is free.

Suppose i wanted to do a lockgroove, like something for solely playing a sine or square wave? How would i modify the code to do so?
Hey Amanda, thank you so much for this documentation! I used your method in a (climate change) data visualization project for school. The record turned out FANTASTIC. I ended up cutting a song I wrote using the data, and without your thorough documentation would never have achieved the result I did.

- Kyle
Love this project. Like someone else I'm getting this error in Python--
TypeError: 'map' object is not subscriptable
But I can't find the solution. Could someone help?

UPDATED: That problem was solved by using an older version of Python. Similarly, an older version of Processing was required. However, Processing only outputs a
Hi Amanda,
I'm trying to use the code to convert the mp3 to an svg, but it keeps telling me that the file i've named doesn't exist, even though it does, is there something wrong?
Thanks in advance
Have you tried adding a .wav file extension to your file name? It may just need the extension.
Hi Amanda,
I've already got the SVG files and arrive at the final step! But when I doing the laser cutting, I found I have to set the stroke to 1pt for our laser cutter ROASTING, when I doing the 0.0001pt, it will cut off the arylic and wood piece. Do you think 1pt will also work for record player?
Thank you so much for your last reply!
Use 0.25 pt or 0.001 inch or hairline. It depends on your laser cutter. I printed from Illustrator and sent it to my ULS laser cutter. The size isn't the actual size of the groove you are cutting. It's simply what your laser recognizes as a vector line rather than a raster.
- Jamie
More Comments