Spectrum Analyzer with NAudio & WPFSoundVisualizationLib

13,400

Solution 1

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!

Solution 2

I have a working spectrum analyzer using the code at another question. It is a very crude version but you will be able to use it with minor modification.

I have no idea what is actually wrong with your code, but at least the provided code is working for me. The problem is somewhere else if you still have an incorrect spectrum when using it.

Share:
13,400
Floyd
Author by

Floyd

Updated on June 13, 2022

Comments

  • Floyd
    Floyd almost 2 years

    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.