如何过滤FFT数据(用于音频可视化)?

我正在看这个Web Audio API演示 , 这本好书的一部分

如果你看一下演示,fft峰值会顺利下降。 我正在尝试使用minim库在Java模式下处理相同的操作。 我已经看过如何使用doFFTAnalysis()方法中的web音频api完成此操作,并尝试使用minim复制它。 我还尝试移植abs()如何使用复杂类型:

/ 26.2.7/3 abs(__z): Returns the magnitude of __z. 00565 template 00566 inline _Tp 00567 __complex_abs(const complex& __z) 00568 { 00569 _Tp __x = __z.real(); 00570 _Tp __y = __z.imag(); 00571 const _Tp __s = std::max(abs(__x), abs(__y)); 00572 if (__s == _Tp()) // well ... 00573 return __s; 00574 __x /= __s; 00575 __y /= __s; 00576 return __s * sqrt(__x * __x + __y * __y); 00577 } 00578 

我目前正在使用Processing(一个java框架/库)做一个快速原型。 我的代码如下所示:

 import ddf.minim.*; import ddf.minim.analysis.*; private int blockSize = 512; private Minim minim; private AudioInput in; private FFT mfft; private float[] time = new float[blockSize];//time domain private float[] real = new float[blockSize]; private float[] imag = new float[blockSize]; private float[] freq = new float[blockSize];//smoothed freq. domain public void setup() { minim = new Minim(this); in = minim.getLineIn(Minim.STEREO, blockSize); mfft = new FFT( in.bufferSize(), in.sampleRate() ); } public void draw() { background(255); for (int i = 0; i < blockSize; i++) time[i] = in.left.get(i); mfft.forward( time); real = mfft.getSpectrumReal(); imag = mfft.getSpectrumImaginary(); final float magnitudeScale = 1.0 / mfft.specSize(); final float k = (float)mouseX/width; for (int i = 0; i < blockSize; i++) { float creal = real[i]; float cimag = imag[i]; float s = Math.max(creal,cimag); creal /= s; cimag /= s; float absComplex = (float)(s * Math.sqrt(creal*creal + cimag*cimag)); float scalarMagnitude = absComplex * magnitudeScale; freq[i] = (k * mfft.getBand(i) + (1 - k) * scalarMagnitude); line( i, height, i, height - freq[i]*8 ); } fill(0); text("smoothing: " + k,10,10); } 

我没有得到错误,这很好,但我没有得到预期的行为,这是不好的。 我预计当平滑(k)接近1时峰值会下降,但据我所知,我的代码只能缩放波段。

不幸的是数学和声音不是我的强项,所以我在黑暗中刺伤。 如何从Web Audio API演示中复制出漂亮的可视化?

我很想说这可能是语言不可知的,但是使用例如javascript不适用:)。 但是,我很高兴尝试任何其他进行FFT分析的java库。

UPDATE

我有一个简单的平滑解决方案(如果当前的fft波段不高,则会不断减小每个前一个fft波段的值:

 import ddf.minim.analysis.*; import ddf.minim.*; Minim minim; AudioInput in; FFT fft; float smoothing = 0; float[] fftReal; float[] fftImag; float[] fftSmooth; int specSize; void setup(){ size(640, 360, P3D); minim = new Minim(this); in = minim.getLineIn(Minim.STEREO, 512); fft = new FFT(in.bufferSize(), in.sampleRate()); specSize = fft.specSize(); fftSmooth = new float[specSize]; fftReal = new float[specSize]; colorMode(HSB,specSize,100,100); } void draw(){ background(0); stroke(255); fft.forward( in.left); fftReal = fft.getSpectrumReal(); fftImag = fft.getSpectrumImaginary(); for(int i = 0; i < specSize; i++) { float band = fft.getBand(i); fftSmooth[i] *= smoothing; if(fftSmooth[i] < band) fftSmooth[i] = band; stroke(i,100,50); line( i, height, i, height - fftSmooth[i]*8 ); stroke(i,100,100); line( i, height, i, height - band*8 ); } text("smoothing: " + (int)(smoothing*100),10,10); } void keyPressed(){ float inc = 0.01; if(keyCode == UP && smoothing  inc) smoothing -= inc; } 

FFT平滑

褪色的图形是平滑的图形,完全饱和的图形是活动图形。

然而,与Web Audio API演示相比,我仍然遗漏了一些东西:

Web Audio API演示

我认为Web Audio API可能会考虑到需要缩放中高频率以接近我们所感知的频率,但我不确定如何解决这个问题。

我试图阅读更多有关RealtimeAnalyser类如何为WebAudioAPI执行此操作的信息,但似乎FFTFrame类的doFFT方法可能会进行对数缩放。 我还没弄明白doFFT是如何工作的。

如何使用对数刻度来缩放原始FFT图以说明感知? 我的目标是做一个体面的可视化,我猜我需要:

  • 平滑的值,否则元素将动画快速/抽搐
  • 缩放 FFT频段/频段以获得更好的中/高频数据
  • FFT值映射到可视元素(找到最大值/边界)

有关如何实现这一目标的任何提示?

更新2

我猜测这部分是我在Web Audio API中进行的平滑和缩放://规范化,而不是0dBfs寄存器的输入正弦波为0dBfs(撤消FFT缩放因子)。 const double magnitudeScale = 1.0 / DefaultFFTSize;

 // A value of 0 does no averaging with the previous result. Larger values produce slower, but smoother changes. double k = m_smoothingTimeConstant; k = max(0.0, k); k = min(1.0, k); // Convert the analysis data from complex to magnitude and average with the previous result. float* destination = magnitudeBuffer().data(); size_t n = magnitudeBuffer().size(); for (size_t i = 0; i < n; ++i) { Complex c(realP[i], imagP[i]); double scalarMagnitude = abs(c) * magnitudeScale; destination[i] = float(k * destination[i] + (1 - k) * scalarMagnitude); } 

看起来缩放是通过取复数值的绝对值来完成的。 这篇文章指向同一个方向。 我尝试使用Minim并使用各种窗口函数来使用复数的abs,但它仍然看起来不像我的目标( Web Audio API演示 ):

 import ddf.minim.analysis.*; import ddf.minim.*; Minim minim; AudioInput in; FFT fft; float smoothing = 0; float[] fftReal; float[] fftImag; float[] fftSmooth; int specSize; WindowFunction[] window = {FFT.NONE,FFT.HAMMING,FFT.HANN,FFT.COSINE,FFT.TRIANGULAR,FFT.BARTLETT,FFT.BARTLETTHANN,FFT.LANCZOS,FFT.BLACKMAN,FFT.GAUSS}; String[] wlabel = {"NONE","HAMMING","HANN","COSINE","TRIANGULAR","BARTLETT","BARTLETTHANN","LANCZOS","BLACKMAN","GAUSS"}; int windex = 0; void setup(){ size(640, 360, P3D); minim = new Minim(this); in = minim.getLineIn(Minim.STEREO, 512); fft = new FFT(in.bufferSize(), in.sampleRate()); fft.window(window[windex]); specSize = fft.specSize(); fftSmooth = new float[specSize]; fftReal = new float[specSize]; colorMode(HSB,specSize,100,100); } void draw(){ background(0); stroke(255); fft.forward( in.mix); fftReal = fft.getSpectrumReal(); fftImag = fft.getSpectrumImaginary(); for(int i = 0; i < specSize; i++) { float band = fft.getBand(i); //Sw = abs(Sw(1:(1+N/2))); %# abs is sqrt(real^2 + imag^2) float abs = sqrt(fftReal[i]*fftReal[i] + fftImag[i]*fftImag[i]); fftSmooth[i] *= smoothing; if(fftSmooth[i] < abs) fftSmooth[i] = abs; stroke(i,100,50); line( i, height, i, height - fftSmooth[i]*8 ); stroke(i,100,100); line( i, height, i, height - band*8 ); } text("smoothing: " + (int)(smoothing*100)+"\nwindow:"+wlabel[windex],10,10); } void keyPressed(){ float inc = 0.01; if(keyCode == UP && smoothing  inc) smoothing -= inc; if(key == 'W' && windex  0) windex--; if(key == 'w' || key == 'W') fft.window(window[windex]); } 

我不确定我是否正确使用窗口function,因为我没有注意到它们之间存在巨大差异。 复数值的绝对值是否正确? 如何让我的目标更接近我的目标?

更新3

我试过申请@ wakjah这样有用的提示:

 import ddf.minim.analysis.*; import ddf.minim.*; Minim minim; AudioInput in; FFT fft; float smoothing = 0; float[] fftReal; float[] fftImag; float[] fftSmooth; float[] fftPrev; float[] fftCurr; int specSize; WindowFunction[] window = {FFT.NONE,FFT.HAMMING,FFT.HANN,FFT.COSINE,FFT.TRIANGULAR,FFT.BARTLETT,FFT.BARTLETTHANN,FFT.LANCZOS,FFT.BLACKMAN,FFT.GAUSS}; String[] wlabel = {"NONE","HAMMING","HANN","COSINE","TRIANGULAR","BARTLETT","BARTLETTHANN","LANCZOS","BLACKMAN","GAUSS"}; int windex = 0; int scale = 10; void setup(){ minim = new Minim(this); in = minim.getLineIn(Minim.STEREO, 512); fft = new FFT(in.bufferSize(), in.sampleRate()); fft.window(window[windex]); specSize = fft.specSize(); fftSmooth = new float[specSize]; fftPrev = new float[specSize]; fftCurr = new float[specSize]; size(specSize, specSize/2); colorMode(HSB,specSize,100,100); } void draw(){ background(0); stroke(255); fft.forward( in.mix); fftReal = fft.getSpectrumReal(); fftImag = fft.getSpectrumImaginary(); for(int i = 0; i < specSize; i++) { //float band = fft.getBand(i); //Sw = abs(Sw(1:(1+N/2))); %# abs is sqrt(real^2 + imag^2) //float abs = sqrt(fftReal[i]*fftReal[i] + fftImag[i]*fftImag[i]); //fftSmooth[i] *= smoothing; //if(fftSmooth[i] < abs) fftSmooth[i] = abs; //x_dB = 10 * log10(real(x) ^ 2 + imag(x) ^ 2); fftCurr[i] = scale * (float)Math.log10(fftReal[i]*fftReal[i] + fftImag[i]*fftImag[i]); //Y[k] = alpha * Y_(t-1)[k] + (1 - alpha) * X[k] fftSmooth[i] = smoothing * fftPrev[i] + ((1 - smoothing) * fftCurr[i]); fftPrev[i] = fftCurr[i];// stroke(i,100,100); line( i, height, i, height - fftSmooth[i]); } text("smoothing: " + (int)(smoothing*100)+"\nwindow:"+wlabel[windex]+"\nscale:"+scale,10,10); } void keyPressed(){ float inc = 0.01; if(keyCode == UP && smoothing  inc) smoothing -= inc; if(key == 'W' && windex  0) windex--; if(key == 'w' || key == 'W') fft.window(window[windex]); if(keyCode == LEFT && scale > 1) scale--; if(keyCode == RIGHT) scale++; } 

我不确定我是否按预期应用了提示。 这是我的输出的样子:

fft顺利第二次尝试

fft顺利第二次尝试

但是如果我把它与我想要的可视化进行比较,我认为我还没有到达那里:

Windows媒体播放器中的频谱

频谱WMP

VLC播放器中的频谱 频谱VLC

我不确定我是否正确应用了日志比例。 我的假设是,我会在使用log10之后绘制类似于我的目标(暂时忽略平滑)。

更新4:

 import ddf.minim.analysis.*; import ddf.minim.*; Minim minim; AudioInput in; FFT fft; float smoothing = 0; float[] fftReal; float[] fftImag; float[] fftSmooth; float[] fftPrev; float[] fftCurr; int specSize; WindowFunction[] window = {FFT.NONE,FFT.HAMMING,FFT.HANN,FFT.COSINE,FFT.TRIANGULAR,FFT.BARTLETT,FFT.BARTLETTHANN,FFT.LANCZOS,FFT.BLACKMAN,FFT.GAUSS}; String[] wlabel = {"NONE","HAMMING","HANN","COSINE","TRIANGULAR","BARTLETT","BARTLETTHANN","LANCZOS","BLACKMAN","GAUSS"}; int windex = 0; int scale = 10; void setup(){ minim = new Minim(this); in = minim.getLineIn(Minim.STEREO, 512); fft = new FFT(in.bufferSize(), in.sampleRate()); fft.window(window[windex]); specSize = fft.specSize(); fftSmooth = new float[specSize]; fftPrev = new float[specSize]; fftCurr = new float[specSize]; size(specSize, specSize/2); colorMode(HSB,specSize,100,100); } void draw(){ background(0); stroke(255); fft.forward( in.mix); fftReal = fft.getSpectrumReal(); fftImag = fft.getSpectrumImaginary(); for(int i = 0; i < specSize; i++) { float maxVal = Math.max(Math.abs(fftReal[i]), Math.abs(fftImag[i])); if (maxVal != 0.0f) { // prevent divide-by-zero // Normalize fftReal[i] = fftReal[i] / maxVal; fftImag[i] = fftImag[i] / maxVal; } fftCurr[i] = -scale * (float)Math.log10(fftReal[i]*fftReal[i] + fftImag[i]*fftImag[i]); fftSmooth[i] = smoothing * fftSmooth[i] + ((1 - smoothing) * fftCurr[i]); stroke(i,100,100); line( i, height/2, i, height/2 - (mousePressed ? fftSmooth[i] : fftCurr[i])); } text("smoothing: " + (int)(smoothing*100)+"\nwindow:"+wlabel[windex]+"\nscale:"+scale,10,10); } void keyPressed(){ float inc = 0.01; if(keyCode == UP && smoothing  inc) smoothing -= inc; if(key == 'W' && windex  0) windex--; if(key == 'w' || key == 'W') fft.window(window[windex]); if(keyCode == LEFT && scale > 1) scale--; if(keyCode == RIGHT) scale++; } 

产生这个:

FFT模型

在绘制循环中,我从中心绘制,因为刻度现在为负。 如果我向上缩放值,结果开始看起来是随机的。

UPDATE6

 import ddf.minim.analysis.*; import ddf.minim.*; Minim minim; AudioInput in; FFT fft; float smoothing = 0; float[] fftReal; float[] fftImag; float[] fftSmooth; float[] fftPrev; float[] fftCurr; int specSize; WindowFunction[] window = {FFT.NONE,FFT.HAMMING,FFT.HANN,FFT.COSINE,FFT.TRIANGULAR,FFT.BARTLETT,FFT.BARTLETTHANN,FFT.LANCZOS,FFT.BLACKMAN,FFT.GAUSS}; String[] wlabel = {"NONE","HAMMING","HANN","COSINE","TRIANGULAR","BARTLETT","BARTLETTHANN","LANCZOS","BLACKMAN","GAUSS"}; int windex = 0; int scale = 10; void setup(){ minim = new Minim(this); in = minim.getLineIn(Minim.STEREO, 512); fft = new FFT(in.bufferSize(), in.sampleRate()); fft.window(window[windex]); specSize = fft.specSize(); fftSmooth = new float[specSize]; fftPrev = new float[specSize]; fftCurr = new float[specSize]; size(specSize, specSize/2); colorMode(HSB,specSize,100,100); } void draw(){ background(0); stroke(255); fft.forward( in.mix); fftReal = fft.getSpectrumReal(); fftImag = fft.getSpectrumImaginary(); for(int i = 0; i < specSize; i++) { fftCurr[i] = scale * (float)Math.log10(fftReal[i]*fftReal[i] + fftImag[i]*fftImag[i]); fftSmooth[i] = smoothing * fftSmooth[i] + ((1 - smoothing) * fftCurr[i]); stroke(i,100,100); line( i, height/2, i, height/2 - (mousePressed ? fftSmooth[i] : fftCurr[i])); } text("smoothing: " + (int)(smoothing*100)+"\nwindow:"+wlabel[windex]+"\nscale:"+scale,10,10); } void keyPressed(){ float inc = 0.01; if(keyCode == UP && smoothing  inc) smoothing -= inc; if(key == 'W' && windex  0) windex--; if(key == 'w' || key == 'W') fft.window(window[windex]); if(keyCode == LEFT && scale > 1) scale--; if(keyCode == RIGHT) scale++; if(key == 's') saveFrame("fftmod.png"); } 

这感觉非常接近:

FFT mod2

这看起来比以前的版本要好得多,但是光谱左下角的某些值看起来有点偏,形状似乎变化得非常快。 (平滑值绘制零)

我有点不清楚你想要做什么样的平滑,但我会尝试提供一些可能对你有帮助的信息。

缩放FFT结果以进行显示

通常,当您进行傅里叶变换并且想要显示它的图形时 ,您需要(如您所述)以对数方式对其进行缩放。 这是因为值的大小将在很大的范围内变化 – 很多个数量级 – 并且将其压缩到图表上可观察到的小空间中将导致主峰使其余信息相形见绌。

要实际进行此缩放,我们将值转换为分贝。 重要的是要注意分贝是一个尺度而不是一个单位 – 它代表两个数字之间的比率 :通常是一个测量值和一些参考。 分贝的通用公式是

 x_dB = 10 * log10((x ^ 2) / (ref ^ 2)) 

其中log10是以10为底的对数, ^是幂运算符, x_ref是您选择的参考值。 由于来自音频文件的FFT值不(通常)具有任何有意义的单位, x_ref对于该应用,通常选择x_ref1 。 此外,由于x很复杂,您需要取绝对值。 那么公式就是

 x_dB = 10 * log10(abs(x) ^ 2) 

这里有一个小的(数值和速度)优化,因为你正方形根的结果:

 x_dB = 10 * log10(real(x) ^ 2 + imag(x) ^ 2) 

感知加权

频域测量的缩放通常在测量声压和功率水平时进行:为给定的应用选择特定的测量类型(我不会在这里进入类型),并根据此测量进行声音记录类型。 结果是FFT’d,然后乘以每个频率的给定加权,具体取决于结果将用于什么以及记录了什么类型的声音。 通常使用两种权重:A和C.C通常仅用于极高振幅的声音。

请注意,如果您只想显示一个漂亮的图表,这种加权并不是必需的:它用于确保世界上的每个人都可以进行符合相同标准的测量(和测量设备)。 如果你决定包含它,它必须转换为分贝之前作为乘法执行(或者作为加权的分贝值的加法 – 在数学上是等价的)。

关于A加权的信息在维基百科上 。

进行加窗主要是为了减少吉布斯现象的影响 。 我们永远不能完全摆脱它,但窗口确实有帮助。 不幸的是,它有其他影响:尖峰变宽,引入“旁瓣”; 峰值锐度和旁瓣高度之间始终存在折衷。 除非你特别要求,否则我不会详细介绍这里的所有细节; 在这本免费的在线书籍中有一个相当冗长的窗口解释。

各个频率仓的时域平滑

至于使每个频率仓中的线路缓慢衰减,这里有一个简单的想法,可以解决这个问题:在每个频率仓中,应用一个简单的指数移动平均线。 假设您的FFT结果存储在X[k] ,其中k是频率索引。 让你的显示值为Y[k]

 Y[k] = alpha * Y_(t-1)[k] + (1 - alpha) * X[k] 

其中0 < alpha < 1是您的平滑因子, Y_(t-1)[k]最后一个时间步t-1 )的Y[k]的值。 这实际上是一个简单的低通IIR(无限脉冲响应)滤波器,希望基本上可以做你想要的(也许稍微调整一下)。 α越接近于零,新观察(输入X[k] )将越快地影响结果。 它越接近1,结果衰减得越慢,但输入也会更慢地影响结果,因此可能看起来“迟钝”。 如果它高于当前值,您可能希望在其周围添加条件以立即获取新值。

请注意,再次,这应该在转换为分贝之前执行。

(编辑)看了你发布的代码更清楚一点,这似乎是你试图重现的例子中使用的方法。 您的初始尝试已接近,但请注意第一项是平滑系数乘以最后一个显示值 ,而不是当前输入。

(编辑2)您的第三次更新再次关闭,但以下行中的公式略有误译

 fftSmooth[i] = smoothing * fftPrev[i] + ((1 - smoothing) * fftCurr[i]); fftPrev[i] = fftCurr[i];// 

平滑之前 ,您需要取值,而不是平滑的FFT系数的先前值。 (请注意,这意味着您实际上不需要另一个数组来存储以前的值)

 fftSmooth[i] = smoothing * fftSmooth[i] + ((1 - smoothing) * fftCurr[i]); 

如果smoothing == 0 ,除了将结果乘以标量之外,该行应该没什么影响。

绝对值计算中的标准化

仔细观察它们计算绝对值的方式,它们在那里有一个归一化,因此两个复数值中的任何一个都是最大值,变为1,另一个相应地缩放。 这意味着您将始终获得介于0和1之间的绝对值,并且可能是分贝转换的替代方法。 真的,这不是他们的absfunction的文档所暗示的,这有点烦人......但无论如何,如果你复制它,它将保证你的值总是在合理的范围内。

要在代码中执行此操作,您可以执行类似的操作

 float maxVal = Math.max(Math.abs(fftReal[i]), Math.abs(fftImag[i])); if (maxVal != 0.0f) { // prevent divide-by-zero // Normalize fftReal[i] = fftReal[i] / maxVal; fftImag[i] = fftImag[i] / maxVal; } fftCurr[i] = scale * (float)Math.log10(fftReal[i]*fftReal[i] + fftImag[i]*fftImag[i]); // ... 

把它们放在一起:一些代码

在Processing 2.1中已经搞砸了一段时间,我有一个解决方案,我相信你会满意:

 import ddf.minim.analysis.*; import ddf.minim.*; Minim minim; //AudioInput in; AudioPlayer in; FFT fft; float smoothing = 0.60; final boolean useDB = true; final int minBandwidthPerOctave = 200; final int bandsPerOctave = 10; float[] fftSmooth; int avgSize; float minVal = 0.0; float maxVal = 0.0; boolean firstMinDone = false; void setup(){ minim = new Minim(this); //in = minim.getLineIn(Minim.STEREO, 512); in = minim.loadFile("C:\\path\\to\\some\\audio\\file.ext", 2048); in.loop(); fft = new FFT(in.bufferSize(), in.sampleRate()); // Use logarithmically-spaced averaging fft.logAverages(minBandwidthPerOctave, bandsPerOctave); avgSize = fft.avgSize(); fftSmooth = new float[avgSize]; int myWidth = 500; int myHeight = 250; size(myWidth, myHeight); colorMode(HSB,avgSize,100,100); } float dB(float x) { if (x == 0) { return 0; } else { return 10 * (float)Math.log10(x); } } void draw(){ background(0); stroke(255); fft.forward( in.mix); final int weight = width / avgSize; final float maxHeight = (height / 2) * 0.75; for (int i = 0; i < avgSize; i++) { // Get spectrum value (using dB conversion or not, as desired) float fftCurr; if (useDB) { fftCurr = dB(fft.getAvg(i)); } else { fftCurr = fft.getAvg(i); } // Smooth using exponential moving average fftSmooth[i] = (smoothing) * fftSmooth[i] + ((1 - smoothing) * fftCurr); // Find max and min values ever displayed across whole spectrum if (fftSmooth[i] > maxVal) { maxVal = fftSmooth[i]; } if (!firstMinDone || (fftSmooth[i] < minVal)) { minVal = fftSmooth[i]; } } // Calculate the total range of smoothed spectrum; this will be used to scale all values to range 0...1 final float range = maxVal - minVal; final float scaleFactor = range + 0.00001; // avoid div. by zero for(int i = 0; i < avgSize; i++) { stroke(i,100,100); strokeWeight(weight); // Y-coord of display line; fftSmooth is scaled to range 0...1; this is then multiplied by maxHeight // to make it within display port range float fftSmoothDisplay = maxHeight * ((fftSmooth[i] - minVal) / scaleFactor); // X-coord of display line float x = i * weight; line(x, height / 2, x, height / 2 - fftSmoothDisplay); } text("smoothing: " + (int)(smoothing*100)+"\n",10,10); } void keyPressed(){ float inc = 0.01; if(keyCode == UP && smoothing < 1-inc) smoothing += inc; if(keyCode == DOWN && smoothing > inc) smoothing -= inc; } 

上面使用了一种稍微不同的方法 - 平均一系列小于总光谱大小的区域中的光谱 - 产生的结果比原始条件更接近WMP。

结果示例

增强function:现在使用A加权

我有一个更新版本的代码,在每个频段应用A加权(虽然只有当dB模式打开时,因为我的表格是以dB为单位:)。 将A加权打开以获得更接近WMP的结果,或关闭更接近VLC的结果。

它的显示方式也有一些细微的调整:它现在位于显示屏的中心位置,只显示最大的频段中心频率。

这是代码 - 享受!

 import ddf.minim.analysis.*; import ddf.minim.*; Minim minim; //AudioInput in; AudioPlayer in; FFT fft; float smoothing = 0.73; final boolean useDB = true; final boolean useAWeighting = true; // only used in dB mode, because the table I found was in dB final boolean resetBoundsAtEachStep = false; final float maxViewportUsage = 0.85; final int minBandwidthPerOctave = 200; final int bandsPerOctave = 10; final float maxCentreFrequency = 18000; float[] fftSmooth; int avgSize; float minVal = 0.0; float maxVal = 0.0; boolean firstMinDone = false; final float[] aWeightFrequency = { 10, 12.5, 16, 20, 25, 31.5, 40, 50, 63, 80, 100, 125, 160, 200, 250, 315, 400, 500, 630, 800, 1000, 1250, 1600, 2000, 2500, 3150, 4000, 5000, 6300, 8000, 10000, 12500, 16000, 20000 }; final float[] aWeightDecibels = { -70.4, -63.4, -56.7, -50.5, -44.7, -39.4, -34.6, -30.2, -26.2, -22.5, -19.1, -16.1, -13.4, -10.9, -8.6, -6.6, -4.8, -3.2, -1.9, -0.8, 0.0, 0.6, 1.0, 1.2, 1.3, 1.2, 1.0, 0.5, -0.1, -1.1, -2.5, -4.3, -6.6, -9.3 }; float[] aWeightDBAtBandCentreFreqs; void setup(){ minim = new Minim(this); //in = minim.getLineIn(Minim.STEREO, 512); in = minim.loadFile("D:\\Music\\Arthur Brown\\The Crazy World Of Arthur Brown\\1-09 Fire.mp3", 2048); in.loop(); fft = new FFT(in.bufferSize(), in.sampleRate()); // Use logarithmically-spaced averaging fft.logAverages(minBandwidthPerOctave, bandsPerOctave); aWeightDBAtBandCentreFreqs = calculateAWeightingDBForFFTAverages(fft); avgSize = fft.avgSize(); // Only use freqs up to maxCentreFrequency - ones above this may have // values too small that will skew our range calculation for all time while (fft.getAverageCenterFrequency(avgSize-1) > maxCentreFrequency) { avgSize--; } fftSmooth = new float[avgSize]; int myWidth = 500; int myHeight = 250; size(myWidth, myHeight); colorMode(HSB,avgSize,100,100); } float[] calculateAWeightingDBForFFTAverages(FFT fft) { float[] result = new float[fft.avgSize()]; for (int i = 0; i < result.length; i++) { result[i] = calculateAWeightingDBAtFrequency(fft.getAverageCenterFrequency(i)); } return result; } float calculateAWeightingDBAtFrequency(float frequency) { return linterp(aWeightFrequency, aWeightDecibels, frequency); } float dB(float x) { if (x == 0) { return 0; } else { return 10 * (float)Math.log10(x); } } float linterp(float[] x, float[] y, float xx) { assert(x.length > 1); assert(x.length == y.length); float result = 0.0; boolean found = false; if (x[0] > xx) { result = y[0]; found = true; } if (!found) { for (int i = 1; i < x.length; i++) { if (x[i] > xx) { result = y[i-1] + ((xx - x[i-1]) / (x[i] - x[i-1])) * (y[i] - y[i-1]); found = true; break; } } } if (!found) { result = y[y.length-1]; } return result; } void draw(){ background(0); stroke(255); fft.forward( in.mix); final int weight = width / avgSize; final float maxHeight = height * maxViewportUsage; final float xOffset = weight / 2 + (width - avgSize * weight) / 2; if (resetBoundsAtEachStep) { minVal = 0.0; maxVal = 0.0; firstMinDone = false; } for (int i = 0; i < avgSize; i++) { // Get spectrum value (using dB conversion or not, as desired) float fftCurr; if (useDB) { fftCurr = dB(fft.getAvg(i)); if (useAWeighting) { fftCurr += aWeightDBAtBandCentreFreqs[i]; } } else { fftCurr = fft.getAvg(i); } // Smooth using exponential moving average fftSmooth[i] = (smoothing) * fftSmooth[i] + ((1 - smoothing) * fftCurr); // Find max and min values ever displayed across whole spectrum if (fftSmooth[i] > maxVal) { maxVal = fftSmooth[i]; } if (!firstMinDone || (fftSmooth[i] < minVal)) { minVal = fftSmooth[i]; } } // Calculate the total range of smoothed spectrum; this will be used to scale all values to range 0...1 final float range = maxVal - minVal; final float scaleFactor = range + 0.00001; // avoid div. by zero for(int i = 0; i < avgSize; i++) { stroke(i,100,100); strokeWeight(weight); // Y-coord of display line; fftSmooth is scaled to range 0...1; this is then multiplied by maxHeight // to make it within display port range float fftSmoothDisplay = maxHeight * ((fftSmooth[i] - minVal) / scaleFactor); // Artificially impose a minimum of zero (this is mathematically bogus, but whatever) fftSmoothDisplay = max(0.0, fftSmoothDisplay); // X-coord of display line float x = xOffset + i * weight; line(x, height, x, height - fftSmoothDisplay); } text("smoothing: " + (int)(smoothing*100)+"\n",10,10); } void keyPressed(){ float inc = 0.01; if(keyCode == UP && smoothing < 1-inc) smoothing += inc; if(keyCode == DOWN && smoothing > inc) smoothing -= inc; } 

结果2

在你的循环中:你需要为lg标度添加对数计算:

 stroke(i,100,50); line( i, height, i, height - fftSmooth[i]*8 ); stroke(i,100,100); line( i, height, i, height - band*8 ); 

应改为:

 int l = map(log(map(i ,0 ,specSize,0,100),0,2,0,width). // an estimation, may have to calibrate stroke(i,100,50); line( l, height, l, height - fftSmooth[i]*8 ); stroke(i,100,100); line( l, height, l, height - band*8 );