How to get an octave based frequency spectrum from the FFT in Minim

edited December 2016 in Library Questions

I want to create audio visuals like the bars visual that is commonly used in music videos. Minim has an FFT that gets the amplitude of each frequency band, but each band is evenly spaced. So the result is that the bars are mostly showing only the high frequencies, and the mid and low frequencies are bunched up on the left. I tried this, but I'm not good with logarithms and have no clue what the correct math to calculate this should be, and it didn't really work:

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

Minim minim;
AudioPlayer song;
FFT fft;

// the number of lines/bands to draw
int bands = 512;

float a = (log(20000 - 20) / log(bands));

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

  minim = new Minim(this);

  song = minim.loadFile("test.mp3", 2048);
  song.loop();

  fft = new FFT(song.bufferSize(), song.sampleRate());
}

void draw()
{
  background(0);
  stroke(255);

  fft.forward(song.mix);

  for(int i = 0; i < bands; i++)
  {
    // calculate the frequency for the current band on a logamorithmic scale
    float freq = pow(i, a) + 20;

    // get the amplitude at that frequency
    float amplitude = fft.getFreq(freq);

    // convert the amplitude to a DB value. 
    // this means values will range roughly from 0 for the loudest
    // bands to some negative value.
    float bandDB = 20 * log(2 * amplitude / fft.timeSize());
    // so then we want to map our DB value to the height of the window
    // given some reasonable range
    float bandHeight = map( bandDB, 0, -150, 0, height );

    // draw the band as a line
    line(i, height, i, bandHeight);
  }
}

I don't even know if it can be done using the FFT in Minim because there might not be enough bands calculated for the lower frequencies. If not, what can I use to accomplish this?

Answers

  • Answer ✓

    There's a logaverages in minim, does that do what you want?

    http://code.compartmental.net/minim/fft_method_logaverages.html

  • Aha! This post answered my question:

    https://forum.processing.org/two/discussion/comment/17292/#Comment_17292

    My new code is:

    import ddf.minim.analysis.*;
    import ddf.minim.*;
    
    Minim minim;
    AudioPlayer song;
    FFT fft;
    
    // the number of bands per octave
    int bandsPerOctave = 4;
    
    // the spacing between bars
    int barSpacing = 3;
    
    void setup()
    {
      size(512, 200);
    
      minim = new Minim(this);
    
      song = minim.loadFile("test.mp3", 1024);
      song.loop();
    
      fft = new FFT(song.bufferSize(), song.sampleRate());
    
      // calculate averages based on a miminum octave width of 22 Hz
      // split each octave into a number of bands
      fft.logAverages(22, bandsPerOctave);
    
      rectMode(CORNERS);
    }
    
    void draw()
    {
      background(0);
      stroke(255);
    
      // perform a forward FFT on the samples in song's mix buffer
      fft.forward(song.mix);
    
      // avgWidth() returns the number of frequency bands each average represents
      // we'll use it as the width of our rectangles
      int w = int(width/fft.avgSize());
    
      for(int i = 0; i < fft.avgSize(); i++)
      {
        // get the amplitude of the frequency band
        float amplitude = fft.getAvg(i);
    
        // convert the amplitude to a DB value. 
        // this means values will range roughly from 0 for the loudest
        // bands to some negative value.
        float bandDB = 20 * log(2 * amplitude / fft.timeSize());
        // so then we want to map our DB value to the height of the window
        // given some reasonable range
        float bandHeight = map(bandDB, 0, -150, 0, height);
    
        // draw a rectangle for the band
        rect(i*w, height, i*w + w - barSpacing, bandHeight);
      }
    }
    
  • I also want to mention that converting the amplitude to decibels gives a much better result for anyone else who wants to do this effect.

Sign In or Register to comment.