c#でマイク音声をFFTする
音声入力
処理の対象物をどうしようかと悩んだんですが、いつも画像ばかり触っているので、たまには次元減らして音声処理をやってみます。ここで悩むのがどうやって音声を入力するかです。画像ならSystem.Drawing.Image.FromFile()とかcv::imread()が使えるんですが、音声はどうするんでしょう。
調べてみるとXNAか.NET Voice Recorderを使えとStack over flowに書いてあったのでとりあえず後者を使います。
.NET Voice RecorderはNAudioを使っているのでNugetでNAudioをインストールします。
NAudioで音声取得
USBカメラにマイクがついていたのでそれを使います。
ちゃんとつながっているかは以下で確認できます。.NET Voice Recorderを参考にしてます。
for (int i = 0; i < WaveIn.DeviceCount; i++) { var deviceInfo = WaveIn.GetCapabilities(i); this.label1.Text = String.Format("Device {0}: {1}, {2} channels", i, deviceInfo.ProductName, deviceInfo.Channels) ; }
音声の取得はイベントを使用して行います。サンプリングレートが8kHzでモノラルとしてます。
waveIn = new WaveIn() { DeviceNumber = 0, // Default }; waveIn.DataAvailable += WaveIn_DataAvailable; waveIn.WaveFormat = new WaveFormat(sampleRate:8000, channels:1); waveIn.StartRecording();
private void WaveIn_DataAvailable(object sender, WaveInEventArgs e) { // 32bitで最大値1.0fにする for (int index = 0; index < e.BytesRecorded; index += 2) { short sample = (short) ((e.Buffer[index + 1] << 8) | e.Buffer[index + 0]); float sample32 = sample / 32768f; ProcessSample(sample32); } }
とりあえずここまでで音声データを波形表示してみます。グラフはいけてるライブラリOxyplotを使いましょう。
private PlotModel _plotmodel = new PlotModel(); private LinearAxis _linearaxis1 = new LinearAxis { Position = AxisPosition.Bottom }; private LinearAxis _linearaxis2 = new LinearAxis { Minimux = -1.0, Maximum = 1.0, Position = AxisPosition.Left }; private LineSeries _lineSeries = new LineSeries(); List<float> _recorded = new List<float>(); // 音声データ private void InitPlot() { _plotmodel.Axes.Add(_linearaxis1); _plotmodel.Axes.Add(_linearaxis2); _plotmodel.Series.Add(_lineSeries); this.plotView1.Model = _plotmodel; } private void ProcessSample(float sample) { _recorded.Add(sample); if (_recorded.Count == 1024) { var points = _recorded.Select((v, index) => new DataPoint((double) index, v) ).ToList(); _lineSeries.Points.Clear(); _lineSeries.Points.AddRange(points); this.plotView1.InvalidatePlot(true); _recorded.Clear(); } }
実行するとこんな感じでいいですねー。
FFT
FFTはMath.NET NumericsをNugetでインストールして実行します。
窓関数もラッキーなことに使えるので、ハミング窓をかけておきましょう。
var window = Window.Hamming(windowsize);
_recorded = _recorded.Select((v, i) => v * (float)window[i]).ToList();
FFTはComplex型の配列を入力します。_recordedはfloat型なので変換が必要です。Radix2Forward()は2のべき乗じゃないと動かないです。
Complex[] complexData = _recorded.Select(v => new Complex(v, 0.0)).ToArray(); Fourier.Forward(complexData, FourierOptions.Matlab); // arbitrary length //Fourier.Radix2Forward(complexData.ToArray(), FourierOptions.Default);
表示は周波数表示用のグラフエリアを別に作って表示しましょう。対数軸でもいいけどそのままで。
var s = windowsize*(1.0/8000.0); var point = complexData.Take(complexData.Count()/2).Select((v, index) => new DataPoint((double)index/s, Math.Sqrt(v.Real * v.Real + v.Imaginary * v.Imaginary)) ).ToList(); _lineSeries2.Points.Clear(); _lineSeries2.Points.AddRange(point); this.plotView2.InvalidatePlot(true);
できたのが下のソフトです。一番大きい値の周波数を表示して、それに最も近い音階を表示しています。