1
$\begingroup$

I need to calculate some simple spectrums of some audio and I've used FFTW successfully in the past so it was my go-to. In this project, I'm using the SharpFFTW C# wrapper library.

I'm not sure how to describe it, but the problem I'm having is the when testing the routine with some generated test tones, the resulting spectrum contains a peak at the expected test frequency and another peak at double the test frequency. I've recreated it it with multiple simultaneous frequencies, and they each have their "ghost" counterpart. Once the test tone goes above ~Fs/4 (so, >~250kHz in the example below), the "extra" peak moves beyond the Nyquist.

This seems like a Hello World of FFT's but, alas I can't see what I'm doing wrong. Any clues would be much appreciated.

Here is the method in question:

public static void ExamplePerformFFT(ChannelBufferMessage bufferMessage, bool print) { int FS = 1_041_666; // FS is determined by hardware DAQ int inputLength = 1_048_576; // 2^20 samples. int fftLength = inputLength / 2 + 1; double[] windowFunction = MathNet.Numerics.Window.HannPeriodic(inputLength); double[] finalSpectrum = new double[fftLength]; var input = new RealArray(inputLength); var output = new ComplexArray(fftLength); var plan = Plan.Create1(inputLength, input, output, Options.Estimate); // generate 50kHz signal input.Set(Enumerable.Range(0,inputLength).Select((v, _i) => Math.Sin(2 * Math.PI * 50_000 * _i / FS)).ToArray()); plan.Execute(); // the complex output is stored as an array of floats with alternating real/complex values // Length = 2 * (2/N + 1) double[] complexOutput = output.ToArray(); float scalingFactor = 1.0f / (float)inputLength; for (int bin = 0; bin < output.Length - 1; bin++) { finalSpectrum[bin] = (float)Math.Sqrt(complexOutput[bin] * complexOutput[bin] + complexOutput[bin * 2 + 1] * complexOutput[bin * 2 + 1]) * scalingFactor; } // finalSpectrum now contains 2 peaks: one at 50kHz, and one at 100Khz. } 

I'm generating the bins using:

public static float[] GetFrequencyBins(int sampleRate_Hz, int buffer_length) { var bins = new float[buffer_length / 2 + 1]; for (int bin = 0; bin < bins.Length; bin++) { bins[bin] = (float)sampleRate_Hz * bin / buffer_length; } return bins; } 

And how I'm visualising the spectrum. Extra spectrum peak

$\endgroup$
4
  • 3
    $\begingroup$ I believe there is a bug in your code. While you get the imaginary parts correctly indexed as 2 * bin + 1, for the real parts you index by just bin. You should instead index the real parts by 2 * bin. The way you do it now, in the first iteration (bin = 0) you’ll get the real part of the first bin but in the second iteration (bin = 1) you’ll get the imaginary part of the first bin instead of the real part of the second bin. Since you use a for-loop, I would just change the increment/step to bin += 2 and take the indices as bin for real and bin + 1 for the imaginary. $\endgroup$ Commented Jan 31 at 11:17
  • 1
    $\begingroup$ @ZaellixA: good catch. This sounds like a good answer to me. $\endgroup$ Commented Jan 31 at 13:48
  • $\begingroup$ @Hilmar, like with many bugs, I just got lucky to notice ;D. I’ll go ahead and post it as an answer so in case this is the issue the OP can “approve” a solution for future reference. $\endgroup$ Commented Feb 1 at 14:57
  • $\begingroup$ Ahh, thanks @ZaellixA! That's exactly it! I was stuck looking in a local minima.. I'll approve your (very extensive!) answer $\endgroup$ Commented Feb 2 at 21:09

1 Answer 1

4
$\begingroup$

Intro

I am just posting the comment as an answer so in case this is the issue you can accept it and have a solution for future reference.

Confusion

After taking a closer look at your code there seems to be some kind of inconsistency, most probably stemming from bad understanding of the workings of FFTW (either mine or yours), or just missing information. Your fftLength seems to be half the length of the input signal, which I assume you set it like that seeking to discard the negative frequencies. However, it is not clear whether the output variable/array holds the whole spectrum or the positive frequencies only. I assume this depends on the way FFTW calculates things (possibly depending on the options passed to the “plan”).

Below I’ll discuss the bug in your code related to the calculation of the magnitude of the frequency bins, but clarifying this issue (what does the output array actually holds/contains?) is crucial to get correct results (as it is related to the maximum value of your index).

Bug

There seems to be a bug in your code in the last for-loop in your ExamplePerformFFT() function. There is also an error at the comment that gives the length of the array (reads Length = 2 * (2/N - 1) while it should be Length = 2 * (N/2 - 1) but since this does not affect the results won’t be discussed further). I replicate here the for-loop for completeness

for (int bin = 0; bin < output.length - 1; bin++) { finalSpectrum[bin] = (float)Math.Sqrt(complexOutput[bin] * complexOutput[bin] + complexOutput[bin * 2 + 1] * complexOutput[bin * 2 + 1]) * scalingFactor; } 

The bug is in the way you index things. Your bin index goes from 0 to the length of the spectrum array (as mentioned above 2 * (N/2 - 1)) which here I will “bypass” the issue mentiond in “Confusion” above and assume that it goes through all the bins one by one. This means that the length of the complexOutput array must be twice the length of your finalSpectrum variable (since it is supposed to hold real and imaginary values separately). However, your actual bin complex values are intertwined with the real and imaginary parts being interleaved in the complexOutput variable. This means that the real parts will “reside” at indices 2 * bin, which are the even numbered/indexed elements of the complexOutput array and the imaginary parts at indices 2 * bin + 1.

You get the imaginary parts of the bin complex values correctly by indexing with 2 * bin + 1, but the index for the real part is incorrect since you index with just bin. I will demonstrate the first 3 iterations as a demo.

  1. Iteration $1$ - bin = 0
    • $\texttt{finalSpectrum[0]} = \sqrt{\texttt{complexOutput[0]}^{2} + \texttt{complexOutput[1]}^{2}}$
  2. Iteration $2$ - bin = 1
    • $\texttt{finalSpectrum[1]} = \sqrt{\texttt{complexOutput[1]}^{2} + \texttt{complexOutput[3]}^{2}} \rightarrow$ The indices for the complexOutput should be $2$ (real part) and $3$ (imaginary part) respectively, however you get $1$ (actually the imaginary part of the $1\textrm{st}$ bin complex value) and $3$ (the correct imaginary part of the $2\textrm{nd}$ bin).
  3. Iteration $3$ - bin = 2
    • $\texttt{finalSpectrum[2]} = \sqrt{\texttt{complexOutput[2]}^{2} + \texttt{complexOutput[5]}^{2}} \rightarrow$ The indices for the complexOutput should be $4$ (real part) and $5$ (imaginary part) respectively, however you get $3$ (actually the real part of the $2 \textrm{nd}$ bin complex value) and $5$ (the correct imaginary part of the $3\textrm{nd}$ bin).

I hope you get the pattern, which definitely gives you wrong results.

Now, there is a couple of ways you could “solve” the issue. In fact all of them boil down to picking the correct indices, however you could either set the bin indices in such a way as to make the indexing of the complexOutput easier or the finalSpectrum straight forward. I believe they shouldn’t have any impact on performance, but I am not a software engineer (or a skilled DSP engineer either) so if this is not the case, someone hopefully will shed some light on it in the comments.

The two different ways to index are below (well there may be more but those two come to mind that are convenient one way or the other).

  1. Index for the finalSpectrum. This entails that you keep the indexing as it is and you correct the indices for the real part, as suggested above. This would make your for-loop look like
for (int bin = 0; bin < output.length - 1; bin++) { finalSpectrum[bin] = (float)Math.Sqrt(complexOutput[2 * bin] * complexOutput[bin] + complexOutput[2 * bin + 1] * complexOutput[2 * bin + 1]) * scalingFactor; } 

Note that the only difference here compared to your original for-loop is the index for the real part of each bin becoming 2 * bin instead of bin. This is an easy tweak that you could test very easily and fast.

  1. Alternatively, you could index in a way that will make the indexing of the real and imaginary parts of the bins easy to express. However you have to “compensate” for the index of the finalSpectrum. This would look like
for (int bin = 0; bin < complexOutput.length - 1; bin += 2) { finalSpectrum[bin/2] = (float)Math.Sqrt(complexOutput[bin] * complexOutput[bin] + complexOutput[bin + 1] * complexOutput[bin + 1]) * scalingFactor; } 

Here you change the range of your index to cover the length of the complexOutput array and you increment the index by 2 in each step, effectively moving from one bin to the next (the indices will point to the real parts of the bins). Now the real part you need is exactly at bin and the corresponding imaginary part at bin + 1; and these are the indices you use for the calculation of the magnitude of the bins. However, the index corresponding to the bin in the finalSpectrum is always half the bin value, since the latter increases by $2$ in each step. So the index for the finalSpectrum variable is bin/2. Since bin will always be even (zero inclusive) bin/2 will be an integer so you are good to go.

As you can see, both methods achieve the same results and it is a matter of preference which one you are going to use. However, I would urge you to double-check the actual lengths of the output, finalSpectrum and complexOutput arrays and make sure they are “compatible” and what you want them to be. Furthermore, do make sure that the output array does hold the part of the spectrum you want, or else (if it holds both positive and negative frequencies) you are iterating over the whole spectrum (and not only the right/left part of it). If this is intended behaviour that’s fine, but if not you should act accordingly.

I hope this helps and please don’t hesitate to post comments and ask for clarifications (or suggest corrections if I missed something).

$\endgroup$
1
  • 1
    $\begingroup$ Yes, I was incorrectly indexing the real part..! A ` * 2` did the job. $\endgroup$ Commented Feb 2 at 21:13

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.