拙网论坛

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 108|回复: 0

C# : NAudio と 高速フーリエ変換(FFT)

[复制链接]

949

主题

1001

帖子

3736

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
3736
发表于 2019-1-9 21:04:12 | 显示全部楼层 |阅读模式

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
によると
WaveInEventクラスはコールバックイベントを用いてwaveIn [color=inherit !important]apiを利用します.
非[color=inherit !important]GUI
アプリケーションを作る際に用いてください.
とのこと
https://github.com/naudio/NAudio/blob/master/NAudio/Wave/WaveInputs/WaveInEvent.cs
バッファリングは自作クラスを用います.
ここは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&#x00D7;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でした.


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|抱朴守拙BBS

GMT+8, 2025-5-26 05:35 , Processed in 0.184392 second(s), 18 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表