00001 /* 00002 * Copyright 2006-2007 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 00024 package com.meapsoft.disgraced; 00025 00026 import com.meapsoft.STFT; 00027 import com.meapsoft.FFT; 00028 00037 /* 00038 public class BeatOnsetDetector extends com.meapsoft.OnsetDetector { 00039 00040 double bpmMin = 60; 00041 double bpmMax = 150; 00042 int minDelay = 10; 00043 // int minOnset = 10; 00044 double entThresh = 10; 00045 00046 double framesPerSecond; 00047 double[] real, imag; 00048 FFT fft; 00049 long nextOnset; 00050 int zeroFrames; 00051 00052 public BeatOnsetDetector(STFT stft, double bandThresh, double bandFrac) { 00053 super(stft, bandThresh, bandFrac); 00054 00055 // calculate frames per second 00056 //... 00057 00058 real = new double[onsets.length]; 00059 imag = new double[onsets.length]; 00060 fft = new FFT(onsets.length); 00061 00062 zeroFrames = 0; 00063 nextOnset = 256; 00064 } 00065 00066 // Takes the FFT of the number of onsets per frame for the 00067 // last however many frames (256), finds the peak magnitude between 00068 // 60 and 150 bpm, uses the phase to predict where the next onset 00069 // will be. If that onset is between lastSeen and newestFrame, 00070 // trigger an onset there. Otherwise, keep quiet. Need some way to 00071 // make sure predictions don't change too drastically from one call 00072 // to the next... 00073 00074 // Assumption: this will be called more often than once per beat, so 00075 // we can safely assume that there will only be at most one beat 00076 // present per call. 00077 public void checkOnsets(long lastSeen, long newestFrame) { 00078 00079 // // Update calculation of how many frames of silence we've heard 00080 // for(long fr=lastSeen+1; fr <= newestFrame; fr++) { 00081 // if(onsets[(int)(fr % onsets.length)] < minOnset) { 00082 // zeroFrames++; 00083 // } else { 00084 // zeroFrames = 0; 00085 // } 00086 // } 00087 00088 if(nextOnset > lastSeen && nextOnset <= newestFrame) { 00089 // If the next beat is here, trigger an onset 00090 // System.out.print("*"); 00091 // System.out.print("*"+zeroFrames+" "); 00092 notifyListeners(nextOnset, zeroFrames); 00093 00094 } else if(nextOnset > newestFrame) { 00095 // haven't gotten to the beat yet 00096 00097 } else if(nextOnset <= lastSeen) { 00098 // need to find the next beat 00099 00100 // Copy onset array into FFT buffers, unwrap the circular array 00101 for(int i=0; i<onsets.length; i++) { 00102 real[i] = onsets[(int)((newestFrame+1+i) % onsets.length)]; 00103 imag[i] = 0; 00104 } 00105 00106 fft.fft(real, imag); 00107 00108 double maxMag = 0, mag, meanMag = 0; 00109 int argMaxMag = 0; 00110 00111 // ignore DC component 00112 for(int i=1; i<real.length/2; i++) { 00113 mag = real[i]*real[i] + imag[i]*imag[i]; 00114 meanMag += mag; 00115 if(mag > maxMag) { 00116 maxMag = mag; 00117 argMaxMag = i; 00118 } 00119 } 00120 meanMag /= real.length/2.0 - 1; 00121 00122 // Use a parabolic model to get a better idea of where the 00123 // frequency peak actually falls. This is the offset added to 00124 // the integral frequency peak location. 00125 double magP = real[argMaxMag+1]*real[argMaxMag+1] 00126 + imag[argMaxMag+1]*imag[argMaxMag+1]; 00127 double mag0 = real[argMaxMag]*real[argMaxMag] 00128 + imag[argMaxMag]*imag[argMaxMag]; 00129 double magN = real[argMaxMag-1]*real[argMaxMag-1] 00130 + imag[argMaxMag-1]*imag[argMaxMag-1]; 00131 double rem = -(magP - magN) / (magP - 2.0*mag0 + magN); 00132 00133 // System.out.print((argMaxMag+rem) + " "); 00134 int period = (int)((double)real.length / (rem + argMaxMag)); 00135 double angle = Math.atan2(imag[argMaxMag], real[argMaxMag]); 00136 00137 int delay = (int)(-angle * period/(2*Math.PI)); 00138 delay = (delay+period) % period; 00139 if(delay < minDelay) 00140 delay = delay + period; 00141 00142 // System.out.print(period + ":" + delay + " "); 00143 00144 // Should be an onset when angle = 2pi n = omega t = 2pi k/N t 00145 nextOnset = newestFrame + delay; 00146 00147 // System.out.print(newestFrame + ":" + nextOnset + " "); 00148 00149 // If the max doesn't stand out enough, assume the signal is 00150 // silence 00151 // System.out.print((maxMag/meanMag) + " "); 00152 if(maxMag < entThresh*meanMag) 00153 zeroFrames += delay; 00154 else 00155 zeroFrames = 0; 00156 } 00157 } 00158 } 00159 */