Main Page   Class Hierarchy   Compound List   File List   Compound Members  

BeatOnsetDetector.java

00001 /*
00002  *  Copyright 2006 Columbia University.
00003  *
00004  *  This file is part of MEAPsoft.
00005  *
00006  *  MEAPsoft is free software; you can redistribute it and/or modify
00007  *  it under the terms of the GNU General Public License version 2 as
00008  *  published by the Free Software Foundation.
00009  *
00010  *  MEAPsoft is distributed in the hope that it will be useful, but
00011  *  WITHOUT ANY WARRANTY; without even the implied warranty of
00012  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013  *  General Public License for more details.
00014  *
00015  *  You should have received a copy of the GNU General Public License
00016  *  along with MEAPsoft; if not, write to the Free Software
00017  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
00018  *  02110-1301 USA
00019  *
00020  *  See the file "COPYING" for the text of the license.
00021  */
00022 
00023 package com.meapsoft;
00024 
00025 
00026 
00035 public class BeatOnsetDetector extends OnsetDetector {
00036 
00037   double bpmMin = 60;
00038   double bpmMax = 150;
00039   int minDelay = 10;
00040 //   int minOnset = 10;
00041   double entThresh = 10;
00042 
00043   double framesPerSecond;
00044   double[] real, imag;
00045   FFT fft;
00046   long nextOnset;
00047   int zeroFrames;
00048 
00049   public BeatOnsetDetector(STFT stft, double bandThresh, double bandFrac) {
00050     super(stft, bandThresh, bandFrac);
00051 
00052     // calculate frames per second
00053     //...
00054 
00055     real = new double[onsets.length];
00056     imag = new double[onsets.length];
00057     fft = new FFT(onsets.length);
00058 
00059     zeroFrames = 0;
00060     nextOnset = 256;
00061   }
00062 
00063   // Takes the FFT of the number of onsets per frame for the
00064   // last however many frames (256), finds the peak magnitude between
00065   // 60 and 150 bpm, uses the phase to predict where the next onset
00066   // will be.  If that onset is between lastSeen and newestFrame,
00067   // trigger an onset there.  Otherwise, keep quiet.  Need some way to
00068   // make sure predictions don't change too drastically from one call
00069   // to the next...
00070 
00071   // Assumption: this will be called more often than once per beat, so
00072   // we can safely assume that there will only be at most one beat
00073   // present per call.
00074   public void checkOnsets(long lastSeen, long newestFrame) {
00075 
00076 //     // Update calculation of how many frames of silence we've heard
00077 //     for(long fr=lastSeen+1; fr <= newestFrame; fr++) {
00078 //       if(onsets[(int)(fr % onsets.length)] < minOnset) {
00079 //      zeroFrames++;
00080 //       } else {
00081 //      zeroFrames = 0;
00082 //       }
00083 //     }
00084 
00085     if(nextOnset > lastSeen && nextOnset <= newestFrame) {
00086       // If the next beat is here, trigger an onset
00087 //      System.out.print("*");
00088 //       System.out.print("*"+zeroFrames+" ");
00089       notifyListeners(nextOnset, zeroFrames);
00090 
00091     } else if(nextOnset > newestFrame) {
00092       // haven't gotten to the beat yet
00093 
00094     } else if(nextOnset <= lastSeen) {
00095       // need to find the next beat
00096 
00097       // Copy onset array into FFT buffers, unwrap the circular array
00098       for(int i=0; i<onsets.length; i++) {
00099         real[i] = onsets[(int)((newestFrame+1+i) % onsets.length)];
00100         imag[i] = 0;
00101       }
00102       
00103       fft.fft(real, imag);
00104       
00105       double maxMag = 0, mag, meanMag = 0;
00106       int argMaxMag = 0;
00107  
00108       // ignore DC component
00109       for(int i=1; i<real.length/2; i++) {
00110         mag = real[i]*real[i] + imag[i]*imag[i];
00111         meanMag += mag;
00112         if(mag > maxMag) {
00113           maxMag = mag;
00114           argMaxMag = i;
00115         }
00116       }
00117       meanMag /= real.length/2.0 - 1;
00118 
00119       // Use a parabolic model to get a better idea of where the
00120       // frequency peak actually falls.  This is the offset added to
00121       // the integral frequency peak location.
00122       double magP = real[argMaxMag+1]*real[argMaxMag+1] 
00123         + imag[argMaxMag+1]*imag[argMaxMag+1];
00124       double mag0 = real[argMaxMag]*real[argMaxMag] 
00125         + imag[argMaxMag]*imag[argMaxMag];
00126       double magN = real[argMaxMag-1]*real[argMaxMag-1] 
00127         + imag[argMaxMag-1]*imag[argMaxMag-1];
00128       double rem = -(magP - magN) / (magP - 2.0*mag0 + magN);
00129       
00130 //       System.out.print((argMaxMag+rem) + " ");
00131       int period = (int)((double)real.length / (rem + argMaxMag));
00132       double angle = Math.atan2(imag[argMaxMag], real[argMaxMag]);
00133 
00134       int delay = (int)(-angle * period/(2*Math.PI));
00135       delay = (delay+period) % period;
00136       if(delay < minDelay)
00137         delay = delay + period;
00138 
00139 //       System.out.print(period + ":" + delay + " ");
00140       
00141       // Should be an onset when angle = 2pi n = omega t = 2pi k/N t
00142       nextOnset = newestFrame + delay;
00143 
00144 //       System.out.print(newestFrame + ":" + nextOnset + " ");
00145 
00146       // If the max doesn't stand out enough, assume the signal is
00147       // silence
00148 //       System.out.print((maxMag/meanMag) + " ");
00149       if(maxMag < entThresh*meanMag)
00150         zeroFrames += delay;
00151       else
00152         zeroFrames = 0;
00153     }
00154   }
00155 }

Generated on Thu May 11 15:04:10 2006 for MEAPsoft by doxygen1.2.18