I’ve been looking for ways to access the sound that is currently being played using Minim or the JavaSound API. The idea was to analyze sound that is generated by any arbitrary program running on the computer. The good thing is: you don’t even need to use these APIs in order to do so in Linux! If you are using the PulseAudio server, what you do by default on later Ubuntu versions, there is a tool called parec that can return raw encoded sound signals. This also includes sink monitors. What does all the magic is the following command:

parec -d alsa_output.pci-0000_00_1b.0.analog-stereo.monitor

You have to define a source device as a parameter. As explained here you can determine the most recently added sink with

pactl list | egrep -A2 '^(\*\*\* )?Source #' | \
    grep 'Name: .*\.monitor$' | awk '{print $NF}' | tail -n1

If you run parec from the console, you can actually see the raw binary PCM signal from you sound card! To get this stuff into Java / Processing, we just need to call it as a process. As a small demo project, I built a very simple spectrum analyzer in Processing:

import ddf.minim.analysis.*;
import java.nio.*;
 
static final int sr = 44100; // sample rate
static final int timeSize = 1<<10; 
 
FFT fft;
 
// this command will return the PCM signal that is currently played through your soundcard
static final String cmd = "parec --format=float32be --rate="+sr+" --channels=1 -d alsa_output.pci-0000_00_1b.0.analog-stereo.monitor";
 
InputStream in; // input stream from parec
byte[] bbuf; // binary PCM data buffer 
float[] fbuf; // float PCM data (the buffer we run FFT on)
 
void setup() {
  size(640, 480);
 
  bbuf = new byte[4*timeSize];
  fbuf = new float[timeSize];
 
  fft = new FFT(timeSize, sr);
 
  try {
    Process p = Runtime.getRuntime().exec(cmd);
    in = p.getInputStream();
  } catch (IOException ioe) {}    
}
 
void draw() {  
  try {
    if (in.available() >= 4*timeSize) { // only update if there is enough data
      background(0);  
      stroke(255);
 
      // read one timeSize
      int nb = in.read(bbuf);
      int nf = nb / 4;
 
      // get floats from byte buffer
      ByteBuffer bb = ByteBuffer.wrap(bbuf, 0, nb);
      FloatBuffer fb = bb.asFloatBuffer();
      fb.get(fbuf, 0, nf);      
 
      fft.forward(fbuf);
 
      for(int i = 0; i < fft.specSize(); i++)
      {
        // draw the line for frequency band i, scaling it by 4 so we can see it a bit better
        line(i, height, i, height - fft.getBand(i)*10);
      }      
    }
    // drop other samples from buffer
    in.skip(in.available());
  } catch (IOException ioe) {}  
}