/** * PlaySpectrogram_thresh * * Plays the soundfile "sound.mp3", found in the sketch directory. * Draws a real-time scrolling spectrogram as it is playing. * Finds the largest values at each time (above a threshold) and highlights them in green. * * Dan Ellis dpwe@ee.columbia.edu 2010-02-01 */ import ddf.minim.analysis.*; import ddf.minim.*; Minim minim; AudioPlayer sound; FFT fft; // Configuration: spectrogram size (pixels) int colmax = 500; int rowmax = 250; // buffer size (= FFT size, must be power of 2) int bufferSize = 1024; // Variables int[][] sgram = new int[rowmax][colmax]; int col; int leftedge; boolean paused = false; void setup() { size(colmax + 80, rowmax, P3D); textMode(SCREEN); textFont(createFont("SanSerif", 12)); minim = new Minim(this); sound = minim.loadFile("sound.mp3",bufferSize); // make it repeat sound.loop(); fft = new FFT(sound.bufferSize(), sound.sampleRate()); // use a tapered window (to avoid "splatter") fft.window(FFT.HAMMING); } int maxval_thresh = 100; // smallest value that can count as a maximum (range 0..255) int maxval_margin = 5; // how much smaller than the max val you can be and still be highlighted // Common routine used to draw the spectrogram in two parts // (before and after the "leftedge" wrap point) void drawPart(int[][] sgram, int firstcol, int ncols, int xbase) { for (int i = 0; i < ncols; i++) { int maxval = 0; // draw the column of spectrogram cells (and find the maximum value) for (int j = 0; j < rowmax; j++) { int val = sgram[j][i+firstcol]; stroke(val); point(xbase + i,height-j); if (val > maxval) { maxval = val; } } // highlight maxima in green stroke(0,255,0); for (int j = 0; j < rowmax; j++) { int val = sgram[j][i+firstcol]; if (val > maxval_thresh && val > maxval - maxval_margin) { point(xbase + i,height-j); } } } } void draw() { if (!paused) { background(0); stroke(255); // perform a forward FFT on the samples in the input buffer fft.forward(sound.mix); for(int i = 0; i < rowmax /* fft.specSize() */; i++) { // fill in the new column of spectral values 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 drawPart(sgram, leftedge, colmax-leftedge, 0); drawPart(sgram, 0, leftedge, colmax-leftedge); // 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 int x = colmax + 2; // to right of spectrogram display stroke(255); line(x,0,x,height); // vertical line // x,y address of text is immediately to the left of the middle of the letters textAlign(LEFT,CENTER); for (float freq = 0.0; freq < sound.sampleRate()/2; freq += 500.0) { int y = height - fft.freqToIndex(freq); // which bin holds this frequency line(x,y,x+3,y); // tick mark text(Math.round(freq)+" Hz", x+5, y); // add text label } } } void keyPressed() { // spacebar toggles play/pause if (key == ' ') { paused = !paused; if (paused) { sound.pause(); } else { sound.play(); } } } void stop() { // always close Minim audio classes when you finish with them sound.close(); minim.stop(); super.stop(); }