Spectrum Analyzer with NAudio & WPFSoundVisualizationLib
audio, c#, fft, naudio
Solution
I might be a little late to answer this, but here we are.
You are almost there. You just need to provide the amplitude of the complex numbers that FFT returns instead of the X value.
So in the for loop, instead of this:
this._lastFftBuffer[c] = this._fftBuffer[c].X;
do this:
float amplitude = (float)Math.Sqrt(this._fftBuffer[c].X * this._fftBuffer[c].X + this._fftBuffer[c].Y * this._fftBuffer[c].Y);
this._lastFftBuffer[c] = amplitude;
Cheers!
Problem
I'm working on a C#4.0/WPF Real time Spectrum Analyser (as a base of another project). I use NAudio last version to get real time audio output on sound card, and WPFSoundVisualizationLib (http://wpfsvl.codeplex.com/) for the Spectrum Analyser WPF Control. With this amazing tools, the work is almost done, but it doesn't work right :-( I have a functional Spectrum, but information are not rights, and I don't understand where the problem come from... (I have compare my Spectrum with Equalify, a Spectrum/equaliser for Spotify, and I don't have the same behavior) This is my main class : ``` using System; using System.Windows; using WPFSoundVisualizationLib; namespace MySpectrumAnalyser { public partial class MainWindow : Window { private RealTimePlayback _playback; private bool _record; public MainWindow() { InitializeComponent(); this.Topmost = true; this.Closing += MainWindow_Closing; this.spectrum.FFTComplexity = FFTDataSize.FFT2048; this.spectrum.RefreshInterval = 60; } private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) { if (this._record) { this._playback.Stop(); } } private void Button_Click_1(object sender, RoutedEventArgs e) { if (this._playback == null) { this._playback = new RealTimePlayback(); this.spectrum.RegisterSoundPlayer(this._playback); } if (!this._record) { this._playback.Start(); this.Dispatcher.Invoke(new Action(delegate { this.btnRecord.Content = "Stop"; })); } else { this._playback.Stop(); this.Dispatcher.Invoke(new Action(delegate { this.btnRecord.Content = "Start"; })); } this._record = !this._record; } } } ``` And my loopback analyser (which implements ISpectrumPlayer for using with the WPFSoundVisualizationLib Spectrum control). LoopbackCapture inherits NAudio.CoreAudioApi.WasapiCapture. Received data from Wasapi is a byte array (32 bits PCM, 44.1kHz, 2 channels, 32 bits per sample) ``` using NAudio.Dsp; using NAudio.Wave; using System; using WPFSoundVisualizationLib; namespace MySpectrumAnalyser { public class RealTimePlayback : ISpectrumPlayer { private LoopbackCapture _capture; private object _lock; private int _fftPos; private int _fftLength; private Complex[] _fftBuffer; private float[] _lastFftBuffer; private bool _fftBufferAvailable; private int _m; public RealTimePlayback() { this._lock = new object(); this._capture = new LoopbackCapture(); this._capture.DataAvailable += this.DataAvailable; this._m = (int)Math.Log(this._fftLength, 2.0); this._fftLength = 2048; // 44.1kHz. this._fftBuffer = new Complex[this._fftLength]; this._lastFftBuffer = new float[this._fftLength]; } public WaveFormat Format { get { return this._capture.WaveFormat; } } private float[] ConvertByteToFloat(byte[] array, int length) { int samplesNeeded = length / 4; float[] floatArr = new float[samplesNeeded]; for (int i = 0; i < samplesNeeded; i++) { floatArr[i] = BitConverter.ToSingle(array, i * 4); } return floatArr; } private void DataAvailable(object sender, WaveInEventArgs e) { // Convert byte[] to float[]. float[] data = ConvertByteToFloat(e.Buffer, e.BytesRecorded); // For all data. Skip right channel on stereo (i += this.Format.Channels). for (int i = 0; i < data.Length; i += this.Format.Channels) { this._fftBuffer[_fftPos].X = (float)(data[i] * FastFourierTransform.HannWindow(_fftPos, _fftLength)); this._fftBuffer[_fftPos].Y = 0; this._fftPos++; if (this._fftPos >= this._fftLength) { this._fftPos = 0; // NAudio FFT implementation. FastFourierTransform.FFT(true, this._m, this._fftBuffer); // Copy to buffer. lock (this._lock) { for (int c = 0; c < this._fftLength; c++) { this._lastFftBuffer[c] = this._fftBuffer[c].X; } this._fftBufferAvailable = true; } } } } public void Start() { this._capture.StartRecording(); } public void Stop() { this._capture.StopRecording(); } public bool GetFFTData(float[] fftDataBuffer) { lock (this._lock) { // Use last available buffer. if (this._fftBufferAvailable) { this._lastFftBuffer.CopyTo(fftDataBuffer, 0); this._fftBufferAvailable = false; return true; } else { return false; } } } public int GetFFTFrequencyIndex(int frequency) { int index = (int)(frequency / (this.Format.SampleRate / this._fftLength / this.Format.Channels)); return index; } public bool IsPlaying { get { return true; } } public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; } } ``` GetFFTData is called by the WPF control every 60ms for updating Spectrum.