web-dev-qa-db-ja.com

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<typename _Tp>
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(a Java framework/library)を使用して簡単なプロトタイプを作成しています。私のコードは次のようになります。

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);
}

エラーが発生しないのは良いことですが、期待どおりの動作が得られないのは悪いことです。 Smoothing(k)が1に近い場合、ピークの低下が遅くなると予想しましたが、コードでわかる限り、バンドをスケーリングするだけです。

残念ながら、数学と音は私の強みではないので、私は暗闇の中で刺している。 Web Audio APIデモからNiceビジュアライゼーションを複製するにはどうすればよいですか?

これは言語にとらわれない可能性があると言いたくなりますが、たとえばjavascriptを使用しても適用されません:)。ただし、FFT分析を行う他のJavaライブラリを試してみてうれしいです。

[〜#〜]更新[〜#〜]

平滑化の簡単な解決策があります(現在の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 smooth

色あせたグラフは平滑化されたグラフであり、完全に飽和したグラフはライブグラフです。

ただし、Web Audio APIデモと比較すると、まだ何かが足りません。

Web Audio API demo

Web Audio APIは、中周波数以上の周波数を私たちが知覚するものに近づけるためにスケーリングする必要があることを考慮に入れていると思いますが、それに取り組む方法がわかりません。

RealtimeAnalyserクラスがWebAudioAPIに対してこれをどのように行うかについてもっと読み込もうとしていましたが、 FFTFrameクラスdoFFTメソッドが対数スケーリングを行う可能性があるようです。 doFFTがどのように機能するかはまだわかりません。

知覚を説明するために、生のFFTグラフを対数目盛でスケーリングするにはどうすればよいですか?私の目標は、見栄えの良い視覚化を行うことであり、次のことを行う必要があると思います。

  • smooth値、それ以外の場合、要素は高速/けいれんにアニメーション化されます
  • scale中/高周波数のより良いデータを取得するためのFFTビン/バンド
  • mapFFT値を視覚要素に処理します(最大値/境界を見つけます)

これを達成する方法についてのヒントはありますか?

UPDATE 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]);
}

ウィンドウ関数の間に大きな違いがないので、ウィンドウ関数を正しく使用しているかどうかはわかりません。複素数値の腹筋は正しいですか?視覚化を目的に近づけるにはどうすればよいですか?

UPDATE 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 smooth second attempt

fft smooth second attempt

しかし、これを私が目指している視覚化と比較すると、私はまだそこにいるとは思いません。

windows MediaPlayerのスペクトル

spectrum WMP

vLCプレーヤーのスペクトル spectrum 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 mod

ドローループでは、スケールが負になっているため、中心から描画しています。値をスケールアップすると、結果はランダムに見え始めます。

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

これは前のバージョンよりもはるかに良く見えますが、スペクトルの下部/左側のいくつかの値は少しずれて見え、形状は非常に速く変化するようです。 (平滑化された値はゼロをプロットします)

20
George Profenza

どのようなスムージングを実行したいかは少しわかりませんが、役立つ情報を提供するように努めます。

表示のためのFFT結果のスケーリング

一般に、フーリエ変換を実行してグラフを表示したいの場合、(おっしゃるように)対数的にスケーリングする必要があります。これは、値の大きさが非常に広い範囲(何桁も)で変化し、これをグラフで観察できる小さなスペースに圧縮すると、メインピークが残りの情報を小さくするためです。

実際にこのスケーリングを行うために、値をデシベルに変換します。デシベルはスケールであり、単位ではないことに注意することが重要です。これは、2つの数値(通常は測定値と参照)の間のratioを表します。デシベルの一般式は次のとおりです。

_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され、結果が何に使用され、どのタイプのサウンドが録音されたかに応じて、各周波数で特定の重みが乗算されます。一般的に使用される2つの重み付けがあります。AとCです。Cは通常、非常に高振幅のサウンドにのみ使用されます。

この種の重み付けは、見栄えの良いグラフを表示するだけの場合は実際には必要ないことに注意してください。これは、世界中のすべての人が同じ基準に従う測定(および測定機器)を実行できるようにするために使用されます。これを含めることにした場合は、乗算beforeデシベルへの変換として(または、数学的に同等の重み付けのデシベル値の加算として)実行する必要があります。

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)3回目の更新も終了ですが、次の行の数式にわずかな誤訳があります

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

fftPrev[i] = fftCurr[i];//
_

FFT係数の以前の値before平滑化の代わりに、値after平滑化を取得します。 (これは、前の値を格納するために実際には別の配列が必要ないことを意味することに注意してください)

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

_smoothing == 0_の場合、この行は、結果にスカラーを乗算する以外にはほとんど効果がありません。

絶対値計算における正規化

絶対値の計算方法を詳しく見ると、正規化が行われているため、2つの複素数値のいずれかが最大になると、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に近い結果が生成されます。

Result example

機能強化:Aウェイトが追加されました

各周波数帯域でA重み付けを適用するコードの更新バージョンがあります(ただし、dBモードがオンの場合のみ、テーブルがdB単位であったため)。結果がWMPに近い場合は、Aウェイトをオンにし、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;
}
_

result 2

26
wakjah

ループ内: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 );
0
TheDoctor