This project has moved. For the latest updates, please go here.

Spectrum Analyzer FFT

Feb 20, 2014 at 9:58 AM
Edited Feb 20, 2014 at 11:07 AM
I'm trying to get my spectrum analyzer to display a more realistic curve.

At the moment most of the frequency band is at the bass end. Here is a 20hz to 20000hz sine wave clip (its only 620KB M4V format)
http://www.filedropper.com/fft

I calculate the FFT 2048 complex samples at a time
FastFourierTransform.FFT(true, m, complexClone);


Rather than post the code I have right now I'd rather just know the 'proper' way of converting the FFT data to 16 bars.

For example, do I need to use FastFourierTransform.HammingWindow?

Do I mix L and R this way? float fLandR = (float)Math.Sqrt(fL * fL [PLUS] fR * fR);
Feb 20, 2014 at 12:23 PM
I suppose in its current form I could hack the values or lookup table so as to move the frequency range to the right in the bar range of something like 1-1-1-1-1-2-2-3-3-4-4-4-5-6-8-8 but I'd rather do it properly in the first place rather than patch existing code.
Coordinator
Feb 24, 2014 at 8:41 AM
using a windowing function should remove some false positives from your FFT result due to simply chopping the audio up at arbitrary points. I'm not an expert, but I did write a bit about FFT here
Feb 26, 2014 at 10:11 PM
I was under the impression that the Windows simply removed 'radiation' rather than actually change the frequency range but I'll give it a try and have a look at the article.
Thanks.
Feb 27, 2014 at 5:43 AM
Hi K24A3, are you trying to convert audio into frequency graph?
Feb 27, 2014 at 7:14 AM
Hi no it's just a 16 bar spectrum analyser as shown in the video link in the first post.

It works fine but most of the freq range is pushed to the first few bars with the high 18000hz-20000hz range covering the other 12 or so bars.
Feb 27, 2014 at 8:28 AM
I see..Do you know anyway to convert audio of waveform to frequency?
Feb 27, 2014 at 9:02 AM
Edited Feb 27, 2014 at 9:03 AM
Audio data to a graph? I'm no expert but I'd start with using an FFT and dumping the data to a canvas/graph.
Feb 27, 2014 at 9:41 AM
FFT is not something related to graph? How you get the frequency of the audio?
Feb 27, 2014 at 9:47 AM
You need an FFT to calculate the frequencies.

First you implement a Sample Aggregator to collect the audio samples, then process some of those samples (say every 100ms) using an FFT into frequency data, then draw that data to the graph.

The NAudio WPF demo app has all you need except for the graph which you need to create yourself.
Feb 27, 2014 at 11:52 AM
I see.. In the sample aggregator, does this following code calculate the frequency?
        if (PerformFFT && FftCalculated != null)
        {
            fftBuffer[fftPos].X = (float)(value * FastFourierTransform.HammingWindow(fftPos, fftLength));
            fftBuffer[fftPos].Y = 0;
            fftPos++;
            if (fftPos >= fftBuffer.Length)
            {
                fftPos = 0;
                // 1024 = 2^10
                FastFourierTransform.FFT(true, m, fftBuffer);
                FftCalculated(this, fftArgs);
            }
        }
and what does this code mean:
        maxValue = Math.Max(maxValue, value);
        minValue = Math.Min(minValue, value);
        count++;
        if (count >= NotificationCount && NotificationCount > 0)
        {
            if (MaximumCalculated != null)
            {
                MaximumCalculated(this, new MaxSampleEventArgs(minValue, maxValue));
            }
            Reset();
        }
Sorry if I asked something stupid but I really do not know which one is calculate the frequency of audio. As I not so familiar with FFT. First time use it. Thank you.
Feb 27, 2014 at 12:48 PM
Edited Feb 27, 2014 at 12:51 PM
  1. Yes, FastFourierTransform.FFT() calculates the FFT
  2. You probably dont need the MaximumCalculated event. The FftCalculated event should be used to send the frequency data to the graph control.
Feb 27, 2014 at 1:24 PM
So the fftBuffer is the result of frequency of the audio file input?
Feb 27, 2014 at 9:16 PM
Yes
Feb 28, 2014 at 2:58 AM
Thx a lot.. Thx for the help.. So I have to call FftCalculated to get the frequency of the audio?
Feb 28, 2014 at 3:17 AM
Correct
Feb 28, 2014 at 5:04 AM
Thx for the help.. But the fftBuffer is set to the private. So if I want to use the fftBuffer is it ok that I set it to public?
Feb 28, 2014 at 5:46 AM
Edited Feb 28, 2014 at 5:46 AM
No leave the class as it is, you dont need to change anything. You need to set up the FFT Event callback
            var inputStream = new AudioFileReader(fileName);
            fileStream = inputStream;
            var aggregator = new SampleAggregator(inputStream);
            aggregator.NotificationCount = inputStream.WaveFormat.SampleRate / 100;
            aggregator.PerformFFT = true;
            aggregator.FftCalculated += (s, a) => OnFftCalculated(a); // <<< EVENT

            //aggregator.MaximumCalculated += (s, a) => OnMaximumCalculated(a);
            playbackDevice.Init(aggregator);


    protected virtual void OnFftCalculated(FftEventArgs e)
    {
        // Send the complex array (paired float array) e.Result[] to your graph
    }
Feb 28, 2014 at 9:13 AM
Sorry, I dumb. When only call the OnFftCalculated?
Feb 28, 2014 at 10:59 AM
You probably dont need the MaximumCalculated volume for a freq graph.
Feb 28, 2014 at 1:05 PM
ya.. You have told me.. But how do I call OnFftCalculated? since it requires a FftEventArgs as parameter. What I doing is create a line chart that will add all the x and y points to draw out a line graph. So how do I call OnFftCalculated to do that? Sorry keep troubling you.
Feb 28, 2014 at 2:50 PM
When you create the aggregator, use this line to create the event

aggregator.FftCalculated += (s, a) => OnFftCalculated(a); // <<< EVENT


fftEventArgs is handled by Linq using =>
If you dont understand Linq, just copy and paste the code, it should work fine.

Then add this function
protected virtual void OnFftCalculated(FftEventArgs e)
{
    // Send the complex array (paired float array) e.Result[] to your graph

}
Mar 1, 2014 at 6:53 AM
Thx for the reply. I done what you have told me. But I still fail to call out the event. When I debug I found that it does not call that function out. I look through the code few hours already but still cant find out where I missed. Could you help me check it? Here is my code in the Form1.cs. Thanks a lot.

public Form1()
    {
        InitializeComponent();
    }

    public event EventHandler<FftEventArgs> FftCalculated;

    protected virtual void OnFftCalculated(FftEventArgs e)
    {
        var result = e.Result;
        var lines = new Series("lines");

        lines.ChartType = SeriesChartType.Line;

        for (int i = 0; i < result.Count(); i++)
        {
            lines.Points.Add(new DataPoint(result[i].X, result[i].Y));
        }
        lines.YAxisType = AxisType.Primary;
        chart1.Series.Add(lines);
    }

    string fileName = null;
    private WaveStream fileStream;

    private void button1_Click(object sender, EventArgs e)
    {
        OpenFileDialog open = new OpenFileDialog();
        //open.InitialDirectory = path;
        open.Filter = "Wave File (*.wav)|*.wav|MP3 File (*.mp3)|*mp3;";
        if (open.ShowDialog() != DialogResult.OK)
            return;

        fileName = open.FileName;

        var inputStream = new AudioFileReader(fileName);
        fileStream = inputStream;
        var aggregator = new SampleAggregator(inputStream);
        aggregator.NotificationCount = inputStream.WaveFormat.SampleRate / 100;
        aggregator.PerformFFT = true;
        aggregator.FftCalculated += (s, a) => OnFftCalculated(a);
    }
Mar 1, 2014 at 1:58 PM
Edited Mar 1, 2014 at 1:59 PM
I found out I didnt added in playbackDevice.Init(aggregator); does it is the reason that the event cant called out? But it come another error after I added in the code.

Argument 1: cannot convert from 'test2.SampleAggregator' to 'NAudio.Wave.IWaveProvider'
The best overloaded method match for 'NAudio.Wave.IWavePlayer.Init(NAudio.Wave.IWaveProvider)' has some invalid arguments

I do not what is wrong..
Mar 1, 2014 at 3:18 PM
Not sure why that is happening, make sure you have not changed SampleAggregator.cs

Perhaps you could upload the whole project if you can, it would be much easier that way.
Mar 1, 2014 at 3:44 PM
I have created testing project almost same like my main project. Here is the link. I guess I have missed out something.

https://www.dropbox.com/s/99ls1eymh9m74w0/test2.rar

Hope you can help. Thanks a lot
Mar 1, 2014 at 11:35 PM
That compile error is happening because you are using NAudio 1.5 in the bin\debug folder. Use the NAudio 1.7 dll instead.

If you dont need to play the audio and just want to process the audio data, do the following:
    private void button1_Click(object sender, EventArgs e)
    {
        OpenFileDialog open = new OpenFileDialog();
        open.Filter = "Wave File (*.wav)|*.wav|MP3 File (*.mp3)|*mp3;";
        if (open.ShowDialog() != DialogResult.OK)
            return;

        fileName = open.FileName;

        // if(playbackDevice == null) playbackDevice = new WaveOut {DesiredLatency = 200};

        var inputStream = new AudioFileReader(fileName);
        fileStream = inputStream;
        SampleAggregator aggregator = new SampleAggregator(inputStream);
        aggregator.NotificationCount = inputStream.WaveFormat.SampleRate / 100;
        aggregator.PerformFFT = true;
        aggregator.FftCalculated += (s, a) => OnFftCalculated(a);
        //aggregator.MaximumCalculated += (s, a) => OnMaximumCalculated(a);
        //playbackDevice.Init(aggregator);

        System.Diagnostics.Debug.WriteLine("Init done");

        int read = 0;
        float[] buffer = new float[1024];

        do
        {
            read = aggregator.Read(buffer,0,buffer.Length);
        }while(read > 0);

        //playbackDevice.Play();
    }

The FFT data is now being sent to OnFftCalculated but it is throwing an exception. I'm not familiar with form charts but you probably just need to change the name of lines every time OnFftCalculated is called.
stem.ArgumentException: A chart element with the name 'lines' already exists in the 'SeriesCollection'.
Mar 2, 2014 at 4:49 AM
Thx for the help. You are so pro in it. Can I know what is the buffer and read used for? I will try to solve the chart part. The System.Diagnostics.Debug.WriteLine("Init done"); is just for testing? Thx a lot.
Mar 2, 2014 at 6:03 AM
The buffer that you created each time store different value when it is created. Can explain to me why it will like that?
Mar 2, 2014 at 12:51 PM
It is used just to send the samples to OnFftCalculated(). The buffer itself is not used.

WriteLine is just for debug output.
Mar 2, 2014 at 1:33 PM
I see..But why each time it send value to OnFftCalculated, the value also not the same one? When i debug it, I notice that each time the samples sent to OnFftCalculated FftEventArgs e has different value and end up with my graph no longer a line graph.
Mar 2, 2014 at 9:35 PM
Each call to OnFftCalculated is a snapshot of the frequency range of the current audio sample being played (or being sent), the audio length of the sample is about 0.1 seconds. I dont know what kind of graph you are creating but the FFT data array in e.result are values from -1 to 1, starting from the bass end to the treble end (about 20hz to 20000hz).

If you are creating a wafeform have a look at the WPF demo app in NAudio.
Mar 3, 2014 at 2:53 AM
So what you mean is the fftbuffer is store the sample of 1024 type of frequency each second? Is that anyway to get the summarized frequency of each second?
Mar 3, 2014 at 10:41 AM
Edited Mar 3, 2014 at 10:45 AM
You currently have:
aggregator.NotificationCount = inputStream.WaveFormat.SampleRate / 100;

So it will send 100ms of data every time. Change it to below if you want 1 second invervals:
aggregator.NotificationCount = inputStream.WaveFormat.SampleRate / 10;

The e.Result array stores the frequency ranges of that 100ms.

If you want to plot the frequency you want to do something like below (assuming your graph is 1024 pixels horizontal):
int hCount = 0;
foreach(Complex c in e.Result)
{
    DrawLine(hCount, yourGraphHeight/2, hCount, yourGraphHeight/2+c.X*(yourGraphHeight/2)); // Example function
    hCount++;
}
...+ is the plus symbol
Mar 3, 2014 at 4:41 PM
Thx a lot. I have get what you mean. I try to implement it. If any problem I post it here. Sorry for the inconvenience caused. Thx for your help.