Spectrum Analyzer with NAudio & WPFSoundVisualizationLib
Asked Answered
A

3

6

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.

Antione answered 15/11, 2012 at 13:47 Comment(8)
What's not working? Any error messages? If so, which and where do they occur? You need to describe the error(s) in more detail.Berke
You really need to say what you expected to get vs what you actually got or no one will be able to help.Tenant
Do you know what the type of the data is before you convert it to float? If it is not float already, I don't know if your conversion function will work.Footpoundsecond
I don't have any errors, as I said, "I have a functionnal Spectrum, but informations are not rights". I compare my Spectrum with Equalify (a Spectrum/equaliser) for Spotify) and I don't have the same behavior.Antione
Oups, Enter send immediatly the comment... I receive a byte array in the DataAvailable method, and I convert it in float[] with the ConvertByteToFloat method. The byte array represents the last buffer readed in Wasapi (PCM 16bits / 44.1KHhz I think)Antione
Correction : my wave format is 32 bits PCM (44.1kHz, 2 channels, 32 bits per sample)Antione
BTW, surely you can come up with a better title! How many questions are there which fit the title "Spectrum Analyzer"? Please be a little more specific.Deontology
is your incoming wave format definitely IEEE float? what if it is 32 bit int?Registry
V
3

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!

Vulgar answered 16/7, 2014 at 16:36 Comment(0)
A
1

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.

Ancier answered 27/12, 2013 at 8:2 Comment(0)
A
0

is your incoming wave format definitely IEEE float? what if it is 32 bit int?

I guess it's an IEEE float... this is not explained in MSDN Here

I've tried to convert my byte array in a Int32 array (casted in float) but the result is worst :

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] = (float)BitConverter.ToInt32(array, i * 4);
    }

    return floatArr;
}
Antione answered 15/11, 2012 at 16:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.