http://raptorcafeterrace.hatenablog.com/entry/2017/05/08/191704
NAudioについての日本語の記事が少ないので備忘録も兼ねてNAudioでの[color=inherit !important]FFTのやり方を書きます. 今回の目標はマイクから取得した音を[color=inherit !important]フーリエ変換することです. いかんせん自分が信号について素人なので、記事の対象読者は次の通りです。 - 理屈に詳しくないが、[color=inherit !important]フーリエ変換を活用したい。
- NAudioの日本語での解説が見たい。
読者がゼロにならないことを願います。
[color=inherit !important]こちらの記事を参考にさせていただきました. 1.使うライブラリ郡適宜Nugetなどで取得してください.
DxLibDLLは可視化に使うだけですので, 自分の好きなライブラリに置き換えて結構です. using System;using System.Collections.Generic;using NAudio.CoreAudioAPI;using NAudio.Dsp;using NAudio.Wave;
2.マイク関連マイク入力はWaveInEventクラスを使います.
WaveInクラスとの違いは[color=inherit !important] GitHubによると バッファリングは自作クラスを用います.
ここはListやBufferedWaveProviderでもいいかと思います. (BufferedWaveProviderの挙動がいまいちわからなかったので...) //リングバッファですpublic class WaveBuffer{ float[] buffer; int headIndex; //一番新しいデータのインデックス int tailIndex; //一番古いデータのインデックス public int BufferedLength { get { return headIndex - tailIndex; } } public WaveBuffer(int size = 2048) { buffer = new float[size]; headIndex = 0; tailIndex = 0; } //バッファにデータを追加します public void Add(float data) { buffer[headIndex++ % buffer.Length] = data; } //countだけバッファを消費します public float[] Read(int count) { var rv = new float[count]; for (int i = 0; i < count; i++) { rv = buffer[tailIndex++]; } return rv; }}Mainメソッドを実装していきます.
DoFourier(float[])は[color=inherit !important] フーリエ変換をしてその結果を返します. //フィールド宣言ですreadonly int FFTLength = 512;//Mainメソッド内ですusing (var waveIn = new WaveInEvent()) { var waveBuffer = new WaveBuffer(); //WaveInEventのイベントの追加 //呼び出しタイミングはバッファが利用可能になった時 waveIn.DataAvailable += (object sender, WaveInEventArgs e) => { //バイト列を合成 //waveIn.WaveFormat.BlockAlignは1サンプルが何バイトかを示す //普通2バイトとされる(16bit = shortと同等) for (int i = 0; i < e.BytesRecorded; i += waveIn.WaveFormat.BlockAlign) { //リトルエンディアンの並びで合成 short sample = e.Buffer[i + 1] << 8 | e.Buffer[i + 0]; //最大値が1.0fになるようにする float data = sample / 32768f; //記録 waveBuffer.Add (data); } //バッファが十分溜まった if (waveBuffer.BufferedLength >= FFTLength) { //バッファから読みだしてフーリエ変換 var fftsample = waveBuffer.Read (FFTLength); var result = DoFourier(fftsample); //(お好みで)結果を描画 RenderSpectrum (result); } }; //マイクから音を取得します waveIn.StartRecording (); //ここにお好みの処理を書きます waveIn.StopRecording (); }
3.[color=inherit !important][size=23.504px]フーリエ変換FastFourierTranform.[color=inherit !important] FFTを使えばすぐにできます.
引数の解説がほぼないので解説 FastFourierTransform.FFT (bool forward, int m, Complex[] data)forward : よくわかりませんがtrueにしておきます. (正変換のことかと. コメントお待ちしています)
m : サンプル数が2のm乗の時, そのmの値. (Math.log (sample.Length, 2)などとして求めます)
data : 変換したいデータを入れます. 結果もこれに入ります. (参照型だからrefとかはいらない?) で, 結果はどう扱うかというと, 詳しくは解説書や[color=inherit !important] Wikipediaなどを当たると良いのですが, ([color=inherit !important] そもそも知っているという人が大多数?)
ざっくり言うと要素のインデックスをkとすると
<span class="MathJax" id="MathJax-Element-1-Frame" tabindex="0" data-mathml="f=k×fsamplingNsample" role="presentation" style="display: inline; line-height: normal; font-size: 18.08px; word-spacing: normal; overflow-wrap: normal; white-space: nowrap; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0px; min-height: 0px; border: 0px; position: relative;">f=k×fsamplingNsamplef=k×fsamplingNsample
が成り立ち, その要素は周波数成分fに関する情報を持ちます.(ただしk < N /2)
その[color=inherit !important] 複素数の大きさは振幅の半分に相当します.
k < N / 2 なのは標本化定理からだそうです。 以下コードです public static float[] DoFourier(float[] sample){ var fftsample = new Complex[FFTLength]; //ハミング窓をかける for (int i = 0; i < FFTLength; i++) { fftsample.X = (float)(sample * FastFourierTransform.HammingWindow (i, FFTLength)); fftsample.Y = 0.0f; } //サンプル数のlogを取る var m = (int) Math.Log(FFTSamplenum, 2); //FFT FastFourierTransform.FFT(true, m, fftbuffer); //結果を出力 //FFTSamplenum / 2なのは標本化定理から半分は冗長だから var result = new float[FFTSamplenum / 2]; for (int k = 0; k < FFTSamplenum / 2; k++) { //複素数の大きさを計算 double diagonal = Math.Sqrt (fftbuffer [k].X * fftbuffer [k].X + fftbuffer [k].Y * fftbuffer [k].Y); result [k] = (float) diagonal; } return result;}以上でRenderSpectrum以外のメソッドは実装し終えました.
なるべくコメントを多く書いたのですが,理解の助けになれば幸いです.(というより,自分の理解がまだ未熟なので書いておかないと...)
[color=inherit !important] フーリエ変換をするよりも,その下準備が大変という印象でした.
それと, Nugetで取得したNAudioには説明が皆無なので,適宜[color=inherit !important] GitHubのソースを読まないと辛いです. 以上Raptorでした.
|