Skip to Content

star trails

2016

Star Trails is a generative art project that transforms songs into visuals reminiscent of long exposure, star trail photography. Using FFT audio analysis, the script maps frequency bands to rotating points around a central axis, with each point’s brightness responding to the song’s energy in real time. As the track plays, the points orbit and fade, creating a radial visual “fingerprint” of the music.


import ddf.minim.analysis.*;
import ddf.minim.*;

Minim minim;
//AudioInput in;  //uncomment to use Line in, live input
AudioPlayer in; // uncomment to use a song
FFT fft;

float value;
float newValue = 1;
float maxValue = 1;
int stroke;
int vLoc = 50;
float rate;
float songLength;
float first=0;
float add=0;
//cutoff and multiplication

int cutOff=5000;
//int cutOff=600;


float inc;
float pos=0.0;
int avg=100;
float [] averages = new float [avg];
float [] oldAverages = new float [avg];
float [] degree = new float [avg];
int [] dist = new int [avg];
IntList distances;

//stroke variables
float r=0;
float g=10;
float b=50;

float count=0;

color c = color(r, g, b);

//setup
void setup()
{
  size(1000, 1000);
  background (c);

  //Minim setup for audio
  minim = new Minim(this);
  in = minim.loadFile("song4.mp3", 2048);  //uncomment to use a song
  in.play();                             //uncomment to use a song
  //in = minim.getLineIn(Minim.STEREO, 2048);  //uncomment to use Line In, live audio

  //start that FFTing
  fft = new FFT(in.bufferSize(), in.sampleRate());
  fft.window(FFT.HAMMING);
  fft.linAverages(avg);

  smooth();
  songLength=(in.length()/1000);
  rate=30*songLength;
  inc=TWO_PI/rate;
  frameRate(30);

  distances = new IntList();
  for (int i=0; i < avg; i++){
   distances.append(i);
  }
  
  distances.shuffle();
  
  for (int i = 0; i < avg; i++) {
    oldAverages[i]=0;
    degree[i] = radians(random(0.0, 360.0));
    dist[i] = distances.get(i);
  }

  for (int y=0;y<height;y++){
   stroke(map(y, 0, height, 0, 10), map(y, 0, height, 0, 20), map(y, 0, height, 20, 105),100);
   line(0,y,width,y);
  }

  in.play();                             //uncomment to use a song
}

//ready, set, go!
void draw()
{
  if (in.isPlaying()) {

    //draw updated screen

    // perform a FFT on the samples in the input buffer
    fft.forward(in.mix);

    //pushMatrix();
    //fill(map(count, 0, rate, 100, 0), map(count, 0, rate, 150, 10), map(count, 0, rate, 255, 50));
    //translate(0, 0, -10);
    //rect(-5, -5, width+5, height+5);
    //popMatrix();
    //noFill();
    //count++;
    
    strokeWeight(1);
    stroke(255);
    //for (int i = 0; i < height; i++)
    for (int i = 0; i < fft.avgSize(); i++)
    {
      // fill in the new column of spectral values
      averages[i]=Math.round(Math.max(0, 4*20*Math.log10(1000*fft.getAvg(i))));

      if (maxValue < averages[i]) {
        maxValue = averages[i];
        println(maxValue);
      }
      //averages[i] = (int)Math.max(0, (averages[i] - map(averages[i], 0, maxValue, cutOff, 0)));
      averages[i] = (int)Math.max(0, (averages[i] - map(averages[i], 0, maxValue, cutOff/Math.min((i+1), 25), 0)));

      averages[i]=map(averages[i], 0, maxValue, 0, 255);

      if (oldAverages[i] > averages[i]) {
        averages[i]=oldAverages[i]-3;
      }
    }

    for (int i = 0; i < fft.avgSize(); i++) {
      stroke(255, averages[i]);
      //stroke(constrain(averages[i], 0, 255));
      strokeWeight(Math.max(0, map(averages[i], 0, 255, 0, 5)));
      pushMatrix();
      translate(width/2, height/2);
      rotate(degree[i]);
      translate(0, 0, 10);
      point(0, dist[i]*10+10);
      popMatrix();
      oldAverages[i]=averages[i];
      degree[i]+=inc;

      if (averages[i]==0) {
        degree[i]+=inc*5;
      }
    }
  } else {
    saveFrame("output.png");
    stop();
  }
}
void stop()
{
  // always close Minim audio classes when you finish with them
  in.close();
  minim.stop();

  super.stop();
}

void keyPressed() {
  if (key == CODED) {
    if (keyCode == UP) {
      cutOff+=10;
      println("cutoff: " + cutOff);
    } else if (keyCode == DOWN) {
      cutOff-=10;
      println("cutoff: " + cutOff);
    }
  }
}