高速フーリエ変換からパワースペクトルを求める


みか  2003-12-05 22:44:57  No: 52778  IP: [192.*.*.*]

/*****  FFT & IFFT  *****/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

/* #define PI 3.14159265358979323846 */

/*  sin  table  */
static void make_sintbl(n, sintbl)
int n;
float *sintbl;
{
 int i, n2, n4, n8;
 double c, s, dc, ds, t;
 n2 = n / 2;  n4 = n / 4;  n8 = n / 8;
 t = sin(PI / n);
 dc = 2 * t * t;  ds = sqrt(dc * (2 - dc));
 t = 2 * dc;  
 c = sintbl[n4] = 1;  s = sintbl[0] = 0;
 for (i = 1; i < n8; i++) {
      c -= dc;  dc += t * c;
      s += ds;  ds -= t * s;
      sintbl[i] = s;  sintbl[n4 - i] = c;
      }
 if (n8 != 0) sintbl[n8] = sqrt(0.5);
 for (i = 0; i < n4; i++)
      sintbl[n2 - i] = sintbl[i];
 for (i = 0; i < (n2 + n4); i++)
      sintbl[i + n2] = - sintbl[i];
}

/*  Bit Reversal  */

/*  FFT main routine */
int fft(n, x, y)
int n;
float *x;
float *y;
{
 static int    last_n = 0; 
 static int   *bitrev = NULL; 
 static float *sintbl = NULL; 
 int i, j, k, ik, h, d, k2, n4, inverse;
 float t, s, c, dx, dy;
  /* preparation */
   if (n < 0) {
       n = -n;  inverse = 1;
   } else inverse = 0;
   n4 = n / 4;
   if (n != last_n || n == 0) {
       last_n = n;
   if (sintbl != NULL) free(sintbl);
   if (bitrev != NULL) free(bitrev);
   if (n == 0) return 0;
 sintbl = malloc((n + n4) * sizeof(float));
 bitrev = malloc(n * sizeof(int));
 if (sintbl == NULL || bitrev == NULL) {
    fprintf(stderr, "Insufficient Memory\n");
    return 1;
    }
 make_sintbl(n, sintbl);
 make_bitrev(n, bitrev);
}
for (i = 0; i < n; i++) {
     j = bitrev[i];
     if (i < j) {
  t = x[i];  x[i] = x[j];  x[j] = t;
  t = y[i];  y[i] = y[j];  y[j] = t;
  }
     }
 for (k = 1; k < n; k = k2) {
      h = 0;  k2 = k + k;  d = n / k2;
      for (j = 0; j < k; j++) {
  c = sintbl[h + n4];
  if (inverse) s = - sintbl[h];
  else         s =   sintbl[h];
  for (i = j; i < n; i += k2) {
      ik = i + k;
      dx = s * y[ik] + c * x[ik];
      dy = c * y[ik] - s * x[ik];
      x[ik] = x[i] - dx;  x[i] += dx;
      y[ik] = y[i] - dy;  y[i] += dy;
      }
  h += d;
  }
 }
 if (! inverse)
    for (i = 0; i < n; i++) {
         x[i] /= n;  y[i] /= n;
    }
    return 0;
}



/*** Main Program ***/

#define N 256

int main(argc, argv)
int argc;
char *argv[];
 {
  FILE *fp;
  int i;
  static float x1[N], y1[N], x2[N], y2[N], x3[N], y3[N];
  if ((fp = fopen(argv[1], "r")) ==NULL){
     printf ("File Open Error \"%s \" file.\n", argv[1]);
     exit(1);
      }
  for (i = 0; i < N; i++) {
       fscanf(fp, "%f", &x1[i]);
       x2[i] = x1[i]  ;
       y1[i] = y2[i] = 0;
       }
   if (fft(N, x2, y2)) return EXIT_FAILURE;
   for (i = 0; i < N; i++) {
        x3[i] = x2[i];  y3[i] = y2[i];
        }
   if (fft(-N, x3, y3)) return EXIT_FAILURE;
   printf("      Original data    Fourier Transformed  Inverse Transformed\n");
   for (i = 0; i < N; i++)
        printf("%4d | %6.3f %6.3f | %6.3f %6.3f | %6.3f %6.3f\n",
        i, x1[i], y1[i], x2[i], y2[i],log10(x2[i]*x2[i]+y2[i]y2[i]));
   return EXIT_SUCCESS;
}

下から2行目のprintf()にlogの式を入れればパワースペクトルはもとめられますか?????

編集 削除
なーめ  2003-12-06 04:42:49  No: 52779  IP: [192.*.*.*]

このプログラムを Visual C++ で動くようにしたいのですか。
それとも理論として log( v^2 ) でパワーになるかどうかを
質問しているだけなのですか?

後者ならば、
W = I*V = I^2*R; 
power(dB単位) = log(W)    
なんで、
時間軸であろうと、周波数軸であろうと、パワーは
それでいいのでは。
というか、すでに log10(...) が入っているからいいのでは。

前者ならば、コンパイラオプションをざっと眺めたところ、
クラシックなスタイルのC言語を通すオプションが見当たりません。
(見落としていたらすまない)
まず、関数宣言のところから直す必要がありますね。
というか、gcc --traditional でコンパイルしたほうがいいのでは。

見たところ結構問題の多いプログラムです。
fopen() に対する fclose() がありません。
main() からの脱出が
exit() だったり return; だったりで統一していませんね。
make_bitrev()が見当たりません。
N の正負によって振り分ける inverse もやめたほうがよいですね。
ftt() と invftt() にしたほうがよいですよ。
今の時代この程度のルーチンでプログラムサイズをケチる必要ありません。
全体の流れのなかで、
まったく内容の同じ sintbl,bitrev を2度作成しています。
テーブルの作成部分は fft() から呼ぶのではなく、main() で
作成すべきでないでしょうか。
せっかく計算した x3,y3 出力されていません。
最後の fprintf で書式の %6.3f の数と引数の数も合っていません。
使用する変数の精度を変更できるように
#define FTYPE fload
//#define FTYPE double
で FTYPE x1[N] などとしておいた方が便利です。
...というかこのくらいの精度はどうでもいいのかな。

make_sintbl() の計算で
sin() の計算が1回しかない。
私の手元にあるソースでは、
まじめに

  double dTh = - atan(1.0) * 8 / (double)n;
  for( i = 0; i < n; i ++ )
  {
     double dArg = dTh * i;
     g_dTblSin[i] = sin( dArg );
     g_dTblCos[i] = cos( dArg );
  }

やってます。
初期化時に1回実行されるだけだから、
ここは時間をかけてもさして問題にならないです。

これのオリジナルソースっておそらく
http://www.edu.ics.saitama-u.ac.jp/~j9802ah/prog2/4/fft.c
でしょ?
なので、補足。(make_bitrev())
google のキャッシュから拾ってきました。

static void make_bitrev(int n, int bitrev[])
{
    int i, j, k, n2;

    n2 = n / 2;  i = j = 0;
    for ( ; ; ) {
  bitrev[i] = j;
  if (++i >= n) break;
  k = n2;
  while (k <= j) {  
      j -= k;  k /= 2;  
  }
  j += k;
    }
}

ハミング窓とか不要なのかな?

せっかく Visual C++ なんだから 
complex<doble> 型使おうね...って処理が遅くなるかな。

最後にFFT関連でお勧めのサイトを紹介しておきます。
http://momonga.t.u-tokyo.ac.jp/~ooura/fftman/

編集 削除
みか  2003-12-06 10:06:38  No: 52780  IP: [192.*.*.*]

ありがとうございます。fftを求めてパワースペクトルをVisualCでもとめたいんです。ハニングまどが必要ですよね?!パワーはx軸に周波数でもとめたいんです。

編集 削除
なーめ  2003-12-06 14:09:56  No: 52781  IP: [192.*.*.*]

あ゛ー。
>>>> ハミング窓とか不要なのかな?
>> ハニングまどが必要ですよね
こう並ぶと、知らない人は、私が訂正されているように見えちゃうなー。
窓関数はこれら両方存在しますので。
「窓関数」で google ですね。

http://laputa.cs.shinshu-u.ac.jp/~yizawa/InfSys1/basic/chap9/
のアプレットみたいなのできたらいいねー。

>> パワーはx軸に周波数

プログラム中、x は複素数計算の実部になっているので、
混乱を招きかねないなー。プログラムの方、
今のうちに rp(実部),ip(虚部) くらいに直しておいたほうがいいな。

>> VisualCでもとめたい
ここがいいかも。
http://www.digitalfilter.com/jpvcclass.html

編集 削除
みか  2003-12-06 15:26:15  No: 52782  IP: [192.*.*.*]

なーめさん。大変失礼いたしました。
ちょっと混乱してしまい、一度手順というか、プログラムの流れをおしえていただきたいのですが。お願いします。しきとしてはlog10|fft|^2なんです。

編集 削除
なーめ  2003-12-06 16:14:59  No: 52783  IP: [192.*.*.*]

>> なーめさん。大変失礼いたしました。

>>>>>>>> ハミング窓とか不要なのかな?
>>>>>> ハニングまどが必要ですよね
>>>>こう並ぶと、知らない人は、私が訂正されているように見えちゃうなー。
これ、ギャラリー向けなので気にしないでください。

>> しきとしてはlog10|fft|^2なんです

log10(|fft|^2) でしょ?

        printf("%4d | %6.3f %6.3f | %6.3f %6.3f | %6.3f %6.3f\n",
        i, x1[i], y1[i], x2[i], y2[i],log10(x2[i]*x2[i]+y2[i]y2[i]));

最後の引数で、

 log10( x2[i]*x2[i] + y2[i]*y2[i] )

ってあるじゃないですか。
これですよ。

|fft|

ってノルムなんですが

x,y の座標でいうと原点からの距離なんで、

 sqrt( x*x + y*y )
っていうか
 sqrt( x2[i] * x2[i] + y2[i] * y2[i] )
 
でもって

|fft|^2 

ってあるから

sqrt()

する必要がなくなり、
元のプログラムでいうところの、

x2[i]*x2[i] + y2[i]*y2[i]

これを log10()
に渡す。
よって、

 log10( x2[i]*x2[i] + y2[i]*y2[i] )

これでいいのでは。

1フレーム分の平均パワーにするなら

  log10( Σ<n=0,N>( |fft|^2 )) で、

  #define N 256
  int n;
  double sum = 0.0;
  double mean_power; // 結果格納
  for( n = 0; n < N; n ++ )
  {
     sum += ( x2[i]*x2[i] + y2[i]*y2[i] )
  }
  mean_power = log10( sum / N );

ですかね。

>> 手順というか
Visual Studio のプロジェクトに上記ソースを取り込む方法について
質問しているように受け取れますが。
それでいいのかな。
ならば、どのようなものを作りたいかによります。
  1. DOS窓でコマンドラインから実行
  2. ダイアログでデータファイルをドラッグ&ドロップして実行
     結果をグラフィカルに表示する
  などといったアプリケーションの形態の指定が必要です。

>> プログラムの流れ
こういわれるとアルゴリズムについて問われていると思われますが。
上記 log10 のことでなければ、fft のアルゴリズムそのものの質問ですか。
ならばこれ↓がいいかも。このアルゴリズムは図で示す必要がありますね。
http://fan.aist-nara.ac.jp/~kounoe/lecture/compII/compII_03/compII6_23.pdf

編集 削除
みか  2003-12-06 18:17:06  No: 52784  IP: [192.*.*.*]

得られた値をエクセルで書きたいです。
fft ハニング窓  ソースを教えてもらうことはできませんか??
振幅スペクトルの2乗がパワースペクトルになるはずなんですよ・・

編集 削除
なーめ  2003-12-06 20:15:58  No: 52785  IP: [192.*.*.*]

>> 振幅スペクトルの2乗がパワースペクトルになるはずなんですよ・・

通じてなかったかな?
x2[i]*x2[i] + y2[i]*y2[i]
実部と虚部をそれぞれ2乗して加える。
複素数の2乗でなく、複素数のノルムの2乗です。
複素数の2乗(の実部)だと x*x - y*y になってしまうが、これは関係ない。

log(|fft|)   ... 振幅スペクトル
log(|fft|^2) ... パワースペクトル

結局 log() だから値は2倍になるだけだけどね。
↓ここをクリック。
http://www.onosokki.co.jp/HP-WK/c_support/faq/cf5200/CF5200_time_spect.htm

>> 得られた値をエクセルで書きたいです。
コンソールアプリを作成して、
データの出力をタブで区切ればいいということですね。
で出力を data.txt としたら、
これを1度ノートパッドで開き、
全選択=>エクセルに貼り付けです。
あなたのプログラムでは

 printf("%4d | %6.3f %6.3f | %6.3f %6.3f | %6.3f %6.3f\n",
        i, x1[i], y1[i], x2[i], y2[i],log10(x2[i]*x2[i]+y2[i]y2[i]));


 printf("%4d\t%6.3f\t%6.3f\t%6.3f\t%6.3f\t%6.3f\n",
        i, x1[i], y1[i], x2[i], y2[i],log10(x2[i]*x2[i]+y2[i]*y2[i]));

と直せばよいですね。
ファイルに出力するなら

FILE * fout;
if( fout = fopen( "data.txt", "w" ))
{
  for( i = 0; i < N; i ++ )
  {
     fprintf( fout, "%4d\t%6.3f\t%6.3f\t%6.3f\t%6.3f\t%6.3f\n",
        i, x1[i], y1[i], x2[i], y2[i],log10(x2[i]*x2[i]+y2[i]*y2[i]));
  }
  fclose( fout );
}

とすればよい。

>> ソースを教えてもらうこと
はできないこともないと思いますが、
あなた自身、どこまでできているのかな。
...って、じゃあ、こうしよう。

一番最初のソースを作ったところまではできているとして、

まずは、[なーめ 2003/12/06(土) 04:42:49] のところで示した

make_bitrev()
の関数を

static void make_sintbl(n, sintbl)

の上に貼り付けてください。
次に

/* #define PI 3.14159265358979323846 */



#define PI 3.14159265358979323846

に直してください。
次にクラシックなスタイルのC言語を
Visual C++ で使える ANSIスタイルにします。

static void make_sintbl(n, sintbl)
int n;
float *sintbl;
{



static void make_sintbl( int n, float * sintbl)
{

に直してください。

int fft(n, x, y)
int n;
float *x;
float *y;
{



int fft( int n, float * x, float * y )
{

に直してください。


int main(argc, argv)
int argc;
char *argv[];
{



int main( int argc, char ** argv )
{

に直してください。


   for (i = 0; i < N; i++)
        printf("%4d | %6.3f %6.3f | %6.3f %6.3f | %6.3f %6.3f\n",
        i, x1[i], y1[i], x2[i], y2[i],log10(x2[i]*x2[i]+y2[i]y2[i]));




FILE * fout;
if( fout = fopen( "data.txt", "w" ))
{
  for( i = 0; i < N; i ++ )
  {
     fprintf( fout, "%4d\t%6.3f\t%6.3f\t%6.3f\t%6.3f\t%6.3f\n",
        i, x1[i], y1[i], x2[i], y2[i],log10(x2[i]*x2[i]+y2[i]*y2[i]));
  }
  fclose( fout );
}

に直してください。
次もクラシックCでは型チェックが甘かったものが
ANSIで厳しくなったものです。
修正:

 sintbl = malloc((n + n4) * sizeof(double));
 bitrev = malloc(n * sizeof(int));



 sintbl = (double *)malloc((n + n4) * sizeof(double));
 bitrev = (int *)malloc(n * sizeof(int));

に直してください。
main() の中の

  if (fft(N, x2, y2)) return EXIT_FAILURE;

  fclose( fp );
  if (fft(N, x2, y2)) return EXIT_FAILURE;

に直してください。

これでコンパイルすると、

'double' から 'float' に変換しました。

というメッセージがいっぱいでるので、
全ての float を double に直してください
ソースコードで float を検索し、もれなく行ってください。

これでとりあえずコンパイルできます。
コンパイルは Visual C++ の標準的な設定では F7 キーを押します。

まだ、直すべきところはありますが、
とりあえずここまでやってください。

ここまでで、エラーが出たら、

--------------------構成: m04 - Win32 Debug--------------------
コンパイル中...
m04.cpp
A:\Projects\test\math\m04\m04.cpp(134) : error C2146: 構文エラー : ')' が、識別子 'y2' の前に必要です。
I:\Projects\test\math\m04\m04.cpp(134) : error C2059: 構文エラー : ')'
cl.exe の実行エラー

のように貼り付けてください。
では、また「後」で。(^^;;

編集 削除
みか  2003-12-06 21:46:05  No: 52786  IP: [192.*.*.*]

エラーはありませんでした。ありがとうございます。家にVCがないのですが無料でインストールできるサイトがあるというのを聞いたことがあります。知ってたら教えてください。あと窓関数の適応も教えてもらいたいです。

編集 削除
なーめ  2003-12-06 22:55:25  No: 52787  IP: [192.*.*.*]

>> エラーはありませんでした。
今のままでは、
実行時にファイル名を指定しないと、致命的エラーになる予定です。
(^^;;
main() の
  static double x1[N], y1[N], x2[N], y2[N], x3[N], y3[N];

  static double x1[N], y1[N], x2[N], y2[N], x3[N], y3[N];
  if( argc < 2 )
  {
    fprintf( stderr, "入力ファイルを指定してください" );
    return( 0 );
  }
にしてください。

# 入力ファイル指定を忘れないならそのままで問題なしですが。

C:>fftpower input.txt
   ^^^^^^^^ <-- あなたの作成したプログラム名と仮定。
ですかね。

>> 家にVCがないのですが無料でインストールできるサイトがあるというの
>> を聞いたことがあります。

そんな違法なことは Microsoft が許さないと思います。
あったとしても、私は知りませんし、知っていても、このような
ところで公表するわけにもいかないでしょう。

もし、学生さんなら、今のうちにアカデミック版を購入したらよいのでは。

あるいは、Linux にしてしまうとか。
今までのソースコードの範囲なら、無料で手に入る gcc (GNU C Compiler)
でそのまま使えるはずですが。(Linux も基本は無料です)
Excel はありませんが.....

>> あと窓関数の適応も教えてもらいたいです。

タイトル「オーディオ信号のパワースペクトルを求めるには?」の
タケさんが示していますね。

  入力 s(n) に窓関数 h(n) をかけています。

下に示した中から、好みの窓関数を選んで、あるいは全部を、
make_bitrev()
関数の上に貼り付けてください。

そして、main() の
fclose(fp);
の下で呼び出してください。

do_hanning_window( N, x1, x2 );

x1 から窓かけを行いつつ x2 にコピーしますので
fscanf(fp, "%f", &x1[i])の次の行、

   x2[i] = x1[i]  ;

が不要になりますね。
もし、
error C2065: 'memcpy' : 定義されていない識別子です。
が出たら、
#include <memory.h>

#include <math.h>
の次にいれてください。

なお、下のルーチンは cos テーブルを使用していないので、
「遅い」です。cos テーブルを追加するのは課題にしましょう。

窓関数:
http://www.viste.com/MeasuringSystem/DSP/windows.html
↑式中 T はフレーム長 N, t はフレーム位置 n と考えられます。
下の窓関数ルーチンははテストしていません。
結果を見て(バグがないかどうか)判断してください。
ローゼンフィールド窓はあなたが作成してください。

今のプログラムは1フレームを処理するだけだから、「遅い」ことは
おそらく気になりません。
フレームシフトしながら音楽再生と同時にスペクトル表示したいなら
追加する必要があります。でも、この目的ならメディアプレイヤーを
使う方がいいですね。

ここまででは、最初に指摘した全てを修正したわけで
ありませんが、とりあえずあなたの目的を達成できているとは
思われます。

「下のルーチン」はここから
//------------------------------------------------------
// q_pi は π/4
static double q_pi = atan(1.0);


//  方形窓
//  支配的な周波数成分が明確であって、そのピークレベルの正確さを希望する分析。
//  設定した周波数範囲内で高周波数成分が多い騒音を分析する場合。
//
static void do_rectangle_window( int n, double * rp_in, double * rp_out )
{
  memcpy( rp_out, rp_in, n * sizeof( double ));
}

//  ハニング窓
//  空調設備の音のように低周波音が支配的だが高周波数成分まで分析する場合。
//  また風切り音のようにピーク成分が不明で広帯域周波数の場合。 
//
static void do_hanning_window( int n, double * rp_in, double * rp_out )
{
  int c;
  for( c = 0; c < n; c ++ )
  {
    *rp_out ++ = *rp_in * ( 0.5 - 0.5 * cos( q_pi * 8 * c / n ));
  }
}

//  ハミング窓
//  エンジン音のように複数のピーク成分が予想され、スペクトルの裾
//  の部分の形状よりもピークの値に精度を求める場合。
//
static void do_hamming_window( int n, double * rp_in, double * rp_out )
{
  int c;
  for( c = 0; c < n; c ++ )
  {
    *rp_out ++ = *rp_in * ( 0.54 - 0.46 * cos( q_pi * 8 * c / n ));
  }
}

static void do_blackmann_hariss_window( int n, double * rp_in, double * rp_out )
{
  int c;
  for( c = 0; c < n; c ++ )
  {
    *rp_out ++ = *rp_in * ( 0.3875 - 0.48829 * cos( q_pi * 8 * c / n ) + 0.35875 * cos( q_pi * 16 * c / n ) - 0.001168 * cos( q_pi * 24 * c / n ));
  }
}

//---------------------------------------------------------------
ここまで

編集 削除
なーめ  2003-12-06 23:30:14  No: 52788  IP: [192.*.*.*]

ちょっと不正確だったかな。
各窓関数の
 cos( .../n ) の部分
 cos( .../(n-1)) にしないと
最後の要素の計算で、cos() 値が0にならないね。
まあ、瑣末ではありますが。
気になるなら修正してください。

各、ルーチンの

  for( c = 0; c < n; c ++ )

  n --;
  for( c = 0; c <= n; c ++ )
とします。
ループ内で毎回 n-1 を計算させないためでした。
nが1で呼び出すと0除算になります。

編集 削除
みか  2003-12-07 13:02:04  No: 52789  IP: [192.*.*.*]

プログラムとはちょっとかんけいないのですが・・・x軸に周波数y軸に電圧振幅になっているグラフがあるのですが・・これをx軸周波数y軸にパワースペクトルにしたい場合はどうすればよろしいでしょうか??

編集 削除
なーめ  2003-12-07 15:49:49  No: 52790  IP: [192.*.*.*]

グラフの高さを2倍すればよろしい。

パワーというのは
電圧といった表現に合わせると電力に相当します。

電力(W)=電圧(V)×電流(A)

オームの法則を適用すると、
電力=電圧×電圧÷抵抗=電流×電流×抵抗

などが導かれますよね。
で、スペクトルで表現するとき、log10() するじゃないですか。
高校の数学で、

  log(x*x) = 2 * log( x ) 

でしたよね。
だから、グラフは高さ2倍になるだけなんです。
グラフ全体の形が大きく変わったりしません。

だから、周波数特性を見るときは、電圧スペクトルであろうと、
パワースペクトルであろうと特長(グラフの形状)は同じなのです。
やはり、数学の基礎、物理の基礎、そのあとで信号処理の基礎を
固めてから実際のプログラミングにかかるべきではないかな。
でないと、プログラムの説明をしても意味がわからないよ。

って、板的にぜんぜん場違いなQAだなっと。

編集 削除
みか  2003-12-08 00:24:46  No: 52791  IP: [192.*.*.*]

掲示板に乗ってるタケさんのもパワースペクトルなんですよね?!

編集 削除
なーめ  2003-12-08 02:40:51  No: 52792  IP: [192.*.*.*]

ん゛ー
以上をざっと眺めてみると、
なんか話がかみあってないような気もがしますが、
これで解決ボタンかな。
(GG)/

編集 削除