×

Day 5

Monday September 06, 2010
Category: Creative Pact 2010

Today I was working with analysis. This patch doesn’t have a lot of good looks but I have implemented a zero crossing rate function, an rms function for time domain analysis, spectral centroid, spectral rolloff, and spectral compactness. I coded up a spectral flatness measure but I keep getting zero as the output. I haven’t done thorough debugging job because I was trying to make sure I have something done for today!

I started out hoping that I could implement both the spectral features and time domain features as an AudioListener object but the FFT object doesn’t work that way. So, I made a method that is called every draw loop. This isn’t the nicest solution but it works. I am dreaming up a link between jAudio and MINIM. If I’m not careful I might be putting another thing on my plate :)

Today’s patch is really just an extension of MINIM’s spectral display patch but an overlay of the feature extraction output. My hope is to eventually do a MINIM version of sndpeek. Maybe tomorrow there will be text and waterfall plots... maybe.

Screenshot of software.
Screenshot of software.


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

Minim minim;
AudioPlayer jingle;
FFT fft;
DrawTime dt;

int w;
float[]  myspectrum;

void setup()
{
  size(512, 200, P3D);

  minim = new Minim(this);
  jingle = minim.loadFile("jingle.mp3", 2048);
  jingle.loop();
  
  fft = new FFT(jingle.bufferSize(), jingle.sampleRate());
  // calculate averages based on a miminum octave width of 22 Hz
  // split each octave into three bands
  fft.logAverages(22, 3);
  
  dt = new DrawTime();
  jingle.addListener(dt);

  myspectrum = new float[ jingle.bufferSize() ];
  
  rectMode(CORNERS);
}

void draw()
{
  background(0);
  fill(255);
  stroke(0);
  
  fft.forward(jingle.mix);
  // avgWidth() returns the number of frequency bands each average represents
  // we'll use it as the width of our rectangles
  w = int(width/fft.avgSize());

  for(int i = 0; i < fft.avgSize(); i++)
  {
    rect(i*w, height, i*w + w, height - fft.getAvg(i)*5);
  }
  
  for(int i= 0; i < jingle.bufferSize(); i++)
    myspectrum[i] = fft.getBand(i);
  
  dt.draw();
  drawSpectral(myspectrum);
}

void keyPressed(){
    if (key == ' ')
        saveFrame();
}

void stop()
{
  jingle.close();
  minim.stop();
  super.stop();
}

View this code on GitHub

void drawSpectral(float[] spec)
{
  //==============================
  // SPECTRAL CENTROID
  //==============================
  float m1 = 0;
  float m0 = 0;

  for (int i = 0; i < spec.length; i++){
    m1 += i * spec[i];
    m0 += spec[i]; 
  }

  float centroid = 0.5;
  if (m0 != 0.0)
  {
    centroid = (m1 / m0) / (float) spec.length ;  
  }
  pushStyle();
    stroke(90,255,90);
    strokeWeight(3);
    line(centroid * width, 0 , centroid * width, height);
  popStyle();

  //==============================
  // SPECTRAL ROLLOFF
  //==============================
  float perc = 0.8;
  float[] sumWindow = new float [ spec.length ];
  float total = 0;	
  float sum = 0;
  float specRolloff = 0;
  boolean specdone = false;
  for( int i = 0; i< spec.length; i++)
  {
    sum += spec[i];
    sumWindow[i] = sum;
  }
  total = sumWindow[spec.length-1];

  for ( int i = spec.length-1; i > 1 ; i--){
    if (sumWindow[i] < 0.8 * total)
    {
      specRolloff = (float) i;
      specdone = true;
      break;
    }
  }
  if ( !specdone )
    specRolloff = float(spec.length - 1);
  specRolloff /= spec.length;

  pushStyle();
    stroke(89, 169, 210);
    line( specRolloff * width, 0, specRolloff * width, height);
  popStyle();

  //==============================
  // SPECTRAL COMPATCTNESS
  // code snippet from jAUDIO  
  //==============================
  float[] mag_spec = new float[ spec.length ];
  for (int i = 0; i < spec.length; i++)
  {
    mag_spec[i] = abs( spec[i] );
  }

  double compactness = 0.0;
  for (int i = 1; i < mag_spec.length - 1; i++) {
    if ((mag_spec[i - 1] > 0.0) && (mag_spec[i] > 0.0) && (mag_spec[i + 1] > 0.0)) 
    {
      compactness += Math .abs(
          20.0 * Math.log(mag_spec[i])
          - 20.0 * (Math.log(mag_spec[i - 1]) 
            + Math.log(mag_spec[i]) 
            + Math .log(mag_spec[i + 1])) 
          / 3.0);
    }
  }
  pushStyle();
    stroke(12,48, 96);
    // seems to evaluate between 2k and 9k so divide by 10000 as hack to normalize
    line ( (float)compactness / 10000 * width, 0, (float)compactness / 10000 * width , height);
  popStyle();

  //==============================
  // SPECTRAL FLATNESS
  // DOESN'T WORK -- geometric mean always evaluates to zero :(
  //==============================
  float flatness = 0;
  double geometricMean = spec[0];
  float arithmeticMean = sum;

  for ( int i = 1; i < spec.length; i++)
  {
        geometricMean *= (double) spec[i];
        if (spec[i] == 0.0)
        {
          println("WARNING: ZERO DETECTED " +frameCount);

        }
  }
  geometricMean = Math.pow(geometricMean , (1.0f / spec.length) );
  arithmeticMean /=  spec.length;
  
  flatness = (float)geometricMean / arithmeticMean ;
  println(flatness);
}

View this code on GitHub

class DrawTime implements AudioListener
{
  private float[] left;
  private float[] right;

  synchronized void samples(float[] samp)
  {
    left = samp;
  }

  synchronized void samples(float[] sampL, float[] sampR)
  {
    left = sampL;
    right = sampR;
  }

  synchronized void draw()
  {
    if (left != null && left.length > 0)
    {
      //RMS
      float rms = 0.0;

      for (int i = 0; i < left.length; i++)
        rms += (left[i] * left[i] );

      if (rms!= 0.0)
      {
        rms /= left.length;
        rms = sqrt(rms);
      }
      pushStyle();
          noFill();
          stroke (255, 90,90);
          line(0, height - rms*height, width, height - rms*height);
      popStyle();  

      // ZERO-CROSSING RATE (normalized)
      // detected / buffsize

      float zcr = 0;

      for (int i = 0; i < left.length - 1; i++)
      {
        if (left[i] > 0.0 && left[i + 1] < 0.0)
          zcr++;
        else if (left[i] < 0.0 && left[i + 1] > 0.0)
          zcr++;
        else if (left[i] == 0.0 && left[i + 1] != 0.0)
          zcr++;
      }
      zcr = zcr / (float) left.length;
      pushStyle();
          noFill();
          stroke (90, 90,255);
          line(0, height - zcr*height, width, height - zcr*height); 
      popStyle();  
    }
  }
}

View this code on GitHub


←   newer :: older   →