Fastness logo - art programming projects
Project: 3D Printed Music Box Records
2nd April 2012

Some time ago we bought a Fisher-Price music box record player for the kids and I wondered if it would be possible to make new records for it.

3D printing offered a way but I needed to put in quite some work and develop quite a few bits of code to get the model.  This post will cover how I did it.

First: a disclaimer...

Most of the code that I wrote in this project is really, really bad.  It was designed to work once and, in some cases, didn't even really manage that.  If you try to run this code and it doesn't work or does work but deletes something important I won't be surprised at all.  Emailing me will mean I can sympathise with you, I probably won't be able to help.  If you want proof you should see my generally confused and very possibly confusing comments in the code below.

The Fisher-Price record player that we've got is not a real record player (although they did make one), it's a music box and the "records" are patterns that control the sounding of the notes on a music box comb. 

Before I could make any records I needed to work out what the notes were.  I spent some time with a tone generator (written in Processing) trying to work out the frequencies of each note in the comb.  Once I had this I would be able to check these notes by playing back the notes from an existing record.

The frequency finder (named "freq_finder2") is a (slightly) modified piece of code from the Beads library examples:

import beads.*;
AudioContext ac;
Envelope freqEnv;
PFont p;
float off=0;
void setup() {
  size(1800,300);
  frameRate(10);
  
  p = createFont("Arial",20);
  textFont(p);
  
  
  ac = new AudioContext();
  /*
   * This is an Envelope. It can be used to modify
   * the behaviour of other UGen object. We need to
   * do this to get precise control of certain parameters
   * at an audio rate.
   */
  freqEnv = new Envelope(ac, 500);
  /*
   * This is a WavePlayer. Here we've set it up using 
   * the above Envelope, and a SineBuffer. We'll use
   * the Envelope to modify the freqency below.
   */
  WavePlayer wp = new WavePlayer(ac, freqEnv, Buffer.SINE);
  /*
   * So now that the WavePlayer is set up with the 
   * frequency Envelope, do stuff with the frequency
   * envelope. This command tells the Envelope to change
   * to 1000 in 1 second. Note that when we made the Envelope
   * it was set to 500, so the transition goes from 500 to
   * 1000. These control the frequency of the WavePlayer
   * in Hz.
   */
  //freqEnv.addSegment(1000, 1000);
  /*
   * Connect it all together as before.
   */
  Gain g = new Gain(ac, 1, 0.1);
  g.addInput(wp);
  ac.out.addInput(g);
  ac.start();
}
/*
 * Here's the code to draw a scatterplot waveform.
 * The code draws the current buffer of audio across the
 * width of the window. To find out what a buffer of audio
 * is, read on.
 * 
 * Start with some spunky colors.
 */
color fore = color(255, 102, 204);
color back = color(0,0,0);
/*
 * Just do the work straight into Processing's draw() method.
 */
void draw() {
  loadPixels();
  //set the background
  Arrays.fill(pixels, back);
  //scan across the pixels
  for(int i = 0; i < width; i++) {
    //for each pixel work out where in the current audio buffer we are
    int buffIndex = i * ac.getBufferSize() / width;
    //then work out the pixel height of the audio data at that point
    int vOffset = (int)((1 + ac.out.getValue(0, buffIndex)) * height / 2);
    //draw into Processing's convenient 1-D array of pixels
    pixels[vOffset * height + i] = fore;
  }
  updatePixels();
  
  freqEnv.addSegment(freq(),20);
  
  text(str(freq()),width/2,height*0.9);
  text("("+off+")",width/2-250,height*0.9);
  
}
float freq() {
  return map(mouseX,0,width,80,2000) + off;
}
void keyPressed() {
  if (key == 'f' || key == 'F') {
    println(freq());
  }
  if (key == 'a' || key == 'A') {
    off+=0.5;
  }
  if (key == 'z' || key == 'Z') {
    off-=0.5;
  }
}

So you don't need to spend as long as I did finding the notes the set of frequencies (Hz) in the comb are:

315.73334
426.66666
475.73334 
536.53330
625.06665
702.93335
792.53340
831.16670
831.16670
942.93335
942.93335
1061.3334
1061.3334
1113.6001
1113.6001
1252.2666
1252.2666
1403.7333
1403.7333
1564.7999
1681.0667
1881.6000

The thing to see here is that some of the notes (in red) are repeated.  This was a surprise initially but when I was making the model (later on) I think I worked out why.

Once I had the notes I could work out a tune.  I don't have a keyboard or other instrument so I used another Processing sketch (with the name "player") and the keys on the PC:

import beads.*;
Gain g;
 
AudioContext ac;
WavePlayer[] wp;    //should this be an array, added to the ac and then controlled individually?
//Envelope env;
float[] freqs = new float[22];
void setup() {
  size(200,200);
  frameRate(200);
  
  loadFreqs();
  ac = new AudioContext();
  wp = new WavePlayer[22];
  for (int i=0;i<22;i++) {
    wp[i] = new WavePlayer(ac, 0, Buffer.SINE);
  }
  g = new Gain(ac, 1, 0.1);
  for (int i=0;i<22;i++) {
    g.addInput(wp[i]);
  }
  ac.out.addInput(g);
  ac.start();
}
void draw() {
}
void loadFreqs() {
  freqs[0] = 315.73334;
  freqs[1] = 426.66666;
  freqs[2] = 475.73334;
  freqs[3] = 536.53330;
  freqs[4] = 625.06665;
  freqs[5] = 702.93335;
  freqs[6] = 792.53340;
  freqs[7] = 831.16670;
  freqs[8] = 831.16670;
  freqs[9] = 942.93335;
  freqs[10] = 942.93335;
  freqs[11] = 1061.3334;
  freqs[12] = 1061.3334;
  freqs[13] = 1113.6001;
  freqs[14] = 1113.6001;
  freqs[15] = 1252.2666;
  freqs[16] = 1252.2666;
  freqs[17] = 1403.7333;
  freqs[18] = 1403.7333;
  freqs[19] = 1564.7999;
  freqs[20] = 1681.0667;
  freqs[21] = 1881.6000;
}
 
void noteOn(char pressed) {
  // Receive a noteOn from your midi device
  int index = findKey(pressed);
  println(index);
  Envelope env = new Envelope(ac, freqs[index]);
  wp[index].setFrequencyEnvelope(env);
}
void noteOff(char pressed) {
  // Receive a noteOff - or releasing the note from your midi device
  int index = findKey(pressed);
  Envelope env = new Envelope(ac, 0);
  wp[index].setFrequencyEnvelope(env);
}
int findKey(char pressed) {
  switch(pressed) {
    case 'q': return 0;
    case 'a': return 1;
    case 'w': return 2;
    case 's': return 3;
    case 'e': return 4;
    case 'd': return 5;
    case 'r': return 6;
    case 'f': return 7;
    case 't': return 9;
    case 'g': return 11;
    case 'y': return 13;
    case 'h': return 15;
    case 'u': return 17;
    case 'j': return 19;
    case 'i': return 20;
    case 'k': return 21;
  } 
  return 0;
}
void keyPressed() {
  noteOn(key);
}
void keyReleased() {
  noteOff(key);
}

I tried a few tunes, including "Cavatina" and "My Lovely Horse" (both versions) before I settled on "Still Alive" by Jonathan Coulton from the game Portal.  This was partly because I thought it might be fun and partly because, as I discovered, it's really hard to get tunes that fit in this restricted range of notes. Someone with more musical training than I might be able to work out why...  even so I couldn't get all of the tune to work so I settled for a repeated section that could play continuously around the disc.

Once I'd worked out what tune was possible I needed to arrange it.  I spent a bit more time writing a bigger sketch that would allow me to set, move and erase notes on a 'stave' formed by all of possible notes, including the repeats.  This didn't work that well (the sound tends to come and go for no reason I could work out) but worked well enough to give me an initial tune.  If you want to have a look at it you can go to the sketch "fisherPrice_linearTunes".  This sketch gives me a text file with the series of notes that I've set up.

Once this is ready I wrote another sketch to transform the notes from linear to polar coordinates; I could have done this in one go but didn't.

I was now faced with the problem of making a model of the disc.  This proved not to be easy.

I tried a few options:

  • Autodesk 123D: this was OK but I couldn't get on with it, possibly due to conflicting reflexes from other CAD systems.  It also didn't export an accurate enough STL file when I tried, this may be fixable but by this point I'd already decided not to use it
  • Blender: as I soon discovered I really don't know how to do precise shapes in Blender
  • CATIA: I was getting desperate at this point, I came close to asking for help to set up the model in this industrial strength CAD system at work over lunchtimes but in the end didn't even start because I ended with;
  • Processing (you may be noticing a theme emerging) with the unlekker library for STL export
I ended up "lathing" the outline of the disc around the required axis vertex-by-vertex based on measurements for each track or other feature taken with vernier calipers (no digital here, analog technology FTW).  The sketch called "sketch_Dec29a_loftCurve" gave me a blank disc (STL file download from Shapeways here) that I could put notes on.

The unlekker library let me bring in the blank disc and join it with the notes in the sketch "sketch_Dec29b_modelCombine" before exporting back to STL, fixing a few normals in Blender and uploading at Shapeways.

I finally was able to order the print after over a year (on and off) of messing around with this project.  It pretty much worked out of the box (I added a few foam stickers to make it level, you could avoid that by re-doing the model with smaller drive blocks near the centre - and no, I'm not going to do that) and I was able to record (badly) this clip after using warm water to flatten the model a bit:

Which now has over 120,000 views (!).  This was a surprise but the interest after BoingBoing picked it up was quite amazing to see.

So, now you should have enough information to make your own discs (if you want to), let me know if you do and try not to get sued by the RIAA...



fastness - Iain Banks Graphics
Fastness - Iain Banks Graphics
All of the content from my Iain M Banks website, now shifted to be a section in this one

fastness - Links & Resources:
Processing:
An open source programming tool aimed at artists, engineers and designers.  Simple, light and Java-based with a wealth of libraries and a strong user community

Shapeways:
3D printing for the masses - plastics and metal to your design or team up with a desigenr to personalise a design with a 'co-creator'.  Visit my Shapeways shop for some things I've designed.

Meshlab:
MeshLab is an open source, portable, and extensible system for the processing and editing of unstructured 3D triangular meshes

Blender:
Blender is the free open source 3D content creation suite, available for all major operating systems under the GNU General Public License

Gimp:
GIMP is the GNU Image Manipulation Program. It is a freely distributed piece of software for such tasks as photo retouching, image composition and image authoring. It works on many operating systems, in many languages

Inkscape:
An Open Source vector graphics editor, with capabilities similar to Illustrator, CorelDraw, or Xara X, using the W3C standard Scalable Vector Graphics (SVG) file format

Ponoko:
Retail laser cutting outlet with centres in New Zealand, USA, Germany, Italy and the UK (if not more by now)

Eclipse:
Java development environment