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

 祝图net 发布于 2023-02-12 16:25

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

如果你看一下演示,fft峰值会顺利下降.我正在尝试使用minim库来处理Java模式下的Processing.我已经看过如何使用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<_Tp>& __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 < 1-inc) smoothing += inc;
  if(keyCode == DOWN && 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 < 1-inc) smoothing += inc;
  if(keyCode == DOWN && smoothing > inc) smoothing -= inc;
  if(key == 'W' && windex < window.length-1) windex++;
  if(key == 'w' && windex > 0) windex--;
  if(key == 'w' || key == 'W') fft.window(window[windex]);
}

我不确定我是否正确使用窗口功能,因为我没有发现它们之间存在巨大差异.复数值的绝对值是否正确?如何才能更接近我的目标?

更新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 < 1-inc) smoothing += inc;
  if(keyCode == DOWN && smoothing > inc) smoothing -= inc;
  if(key == 'W' && windex < window.length-1) windex++;
  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 < 1-inc) smoothing += inc;
  if(keyCode == DOWN && smoothing > inc) smoothing -= inc;
  if(key == 'W' && windex < window.length-1) windex++;
  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 < 1-inc) smoothing += inc;
  if(keyCode == DOWN && smoothing > inc) smoothing -= inc;
  if(key == 'W' && windex < window.length-1) windex++;
  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

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

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

    缩放FFT结果以进行显示

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

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

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

    其中log10对数为10,^是幂运算符,x_ref是您选择的参考值.由于来自音频文件的FFT值不(通常)具有任何有意义的单位,x_ref因此通常选择仅1用于该应用.此外,由于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]是价值Y[k]最后的时间步长(t-1).这实际上是一个简单的低通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之间的绝对值,并且可能是分贝转换的替代方法.真的,这并不是他们的abs功能文件所暗示的,这有点令人烦恼......但无论如何,如果你复制它,它将保证你的价值总是在一个合理的范围内.

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

    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.

    结果示例

    增强功能:现在使用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

    2023-02-12 16:27 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有