/** * LiveSpectrogram_window * Takes successive FFTs and renders them onto the screen as grayscale, scrolling left. * This version includes variable window length. Drag the mouse left and right to change the * length of the Hanning window applied before the FFT is taken. * * Dan Ellis dpwe@ee.columbia.edu 2010-01-15 */ import ddf.minim.analysis.*; import ddf.minim.*; Minim minim; AudioInput in; FFT fft; // Configuration: spectrogram size (pixels) int colmax = 500; int rowmax = 250; // Sample rate float sampleRate = 22050; // buffer size (= FFT size, must be power of 2) int bufferSize = 1024; // Variables int[][] sgram = new int[rowmax][colmax]; int col; int leftedge; int window_len = bufferSize; void setup() { size(colmax + 80, rowmax, P3D); textMode(SCREEN); textFont(createFont("SanSerif", 12)); minim = new Minim(this); // setup audio input in = minim.getLineIn(Minim.MONO, bufferSize, sampleRate); fft = new FFT(in.bufferSize(), in.sampleRate()); // suppress windowing inside FFT - we'll do it ourselves fft.window(FFT.NONE); } void draw() { background(0); stroke(255); // grab the input samples float[] samples = in.mix.toArray(); // apply windowing for (int i = 0; i < samples.length/2; ++i) { // Calculate & apply window symmetrically around center point // Hanning (raised cosine) window float winval = (float)(0.5+0.5*Math.cos(Math.PI*(float)i/(float)(window_len/2))); if (i > window_len/2) winval = 0; samples[samples.length/2 + i] *= winval; samples[samples.length/2 - i] *= winval; } // zero out first point (not touched by odd-length window) samples[0] = 0; // perform a forward FFT on the samples in the input buffer fft.forward(samples); // fill in the new column of spectral values for(int i = 0; i < rowmax /* fft.specSize() */; i++) { sgram[i][col] = (int)Math.round(Math.max(0,2*20*Math.log10(1000*fft.getBand(i)))); } // next time will be the next column col = col + 1; // wrap back to the first column when we get to the end if (col == colmax) { col = 0; } // Draw points. // leftedge is the column in the ring-filled array that is drawn at the extreme left // start from there, and draw to the end of the array for (int i = 0; i < colmax-leftedge; i++) { for (int j = 0; j < rowmax; j++) { stroke(sgram[j][i+leftedge]); point(i,height-j); } } // Draw the rest of the image as the beginning of the array (up to leftedge) for (int i = 0; i < leftedge; i++) { for (int j = 0; j < rowmax; j++) { stroke(sgram[j][i]); point(i+colmax-leftedge,height-j); } } // Next time around, we move the left edge over by one, to have the whole thing // scroll left leftedge = leftedge + 1; // Make sure it wraps around if (leftedge == colmax) { leftedge = 0; } // Add frequency axis labels int x = colmax + 2; // to right of spectrogram display stroke(255); line(x,0,x,height); // vertical line // Make text appear centered relative to specified x,y point textAlign(LEFT,CENTER); for (float freq = 0.0; freq < in.sampleRate()/2; freq += 500.0) { int y = height - fft.freqToIndex(freq); // which bin holds this frequency? line(x,y,x+3,y); // add tick mark text(Math.round(freq)+" Hz", x+5, y); // add text label } // Report window size text("Window length = "+window_len+" points", 5, 10); } void mousePressed() { mouseDragged(); } void mouseDragged() { // map mouse position within window to -1..0 float proportion = map(mouseX, 0, colmax, -1, 0); // convert to window length, log scale, 2^4 range window_len = (int)Math.round(Math.pow(2.0, 4.0*proportion)*(float)bufferSize); } void stop() { // always close Minim audio classes when you finish with them in.close(); minim.stop(); super.stop(); }