0
$\begingroup$

In my question yesterday I said that I have a self-written Fourier transform and have to get to the data of the sound card. I have the latter now. I've already answered my question from yesterday. (Self-praise stinks, I know).

I'll show you the relevant code. Can you see where the problem is? I don't understand what's wrong with that.

With my circumstances and settings (sample rate of my sound card, size of the buffer, ...) the result is a double array with a length of 4097 and a queried time (used_milliseconds) of 43 milliseconds. The variable Time_ is therefore incremented by (usedMilliseconds / DataArray.Length), i.e. by 0.010495 milliseconds, while the index for the DataArraycnt – is incremented by 1. I'm sure that's okay.

However, the while loops only run until the cycle time T has passed. So I can guarantee (at least that's the plan) that I "get the figure (360°)" exactly once, if you now know what I mean, in order to find out the x-part of the center of mass.

The goal is to store magnitude of these freqs in the class-wide variables, f.e. x_F_0200, which is queried by another procedure in order to draw a few bars.

getLatestData() fills a newly-declared double Array with, as mentioned, 4097 doubles. Got from the soundcard.

The FFT() procedure is run through by a system.threading.thread. So it shouldn't be a speed issue. The procedure that draws runs approximately every 25 milliseconds. FFT() is finished long ago and doesn't run unnecessarily ;)

The Problem

My problem is that my code is very imprecise. When I turn on a Youtube song like this, the bars look partially accurate (from left (Bass) to the right (treble)), but when I turn on a test tone, you can see that it is not going well. All bars rise, just not the one right one which should. Damned. 😕

Edit 19.12.2021 14:45 Uhr:

For this test tone (500Hz) I get this picture: enter image description here

For this test tone (700 Hz) I get this picture: enter image description here

For this test tone (1000Hz) I get this picture: enter image description here

While a song is playing, it looks reasonably accurate. The rectangles for the bass rise while the beat is coming, with light voices the right rectangles rise. You could almost believe that it is correct. But yes, from the test tones, I know it's still wrong. (I assume that these test tones are correct :D ).

I only use a BufferSize of 1024 bytes, so my DataArray is 513 bytes in size. The usedMilliseconds are 5.3333. So much less than yesterday.

In addition, the while loops run as long as possible in order to use the full 5.333 milliseconds, but not 100%, because I don't want to have the last half / third / eighth of a cycle.

VB.NET 19.12.2021 14:45

Private Sub FFT()
        If Not continue_ Then
            Return
        End If

        getLatestData()
        If DataArray Is Nothing Then
            Return
        End If

        Dim Time_ As Double ' in ms

        ''№ 1
        'cnt = 0
        'x_F_0020 = 0.0F
        'Time_ = 0.0
        'While cnt < CInt(Math.Floor(usedMilliseconds / (1.0 / 20.0 * 1000.0) * Math.Floor(DataArray.Length / (usedMilliseconds / (1.0 / 20.0 * 1000.0)))))
        '    x_F_0020 += CSng(DataArray(cnt) * RealPart(20.0, Time_))
        '    Time_ += (usedMilliseconds / DataArray.Length)
        '    cnt += 1
        '    If cnt >= DataArray.Length Then Exit While
        'End While
        'x_F_0020 /= cnt

        '№ 2
        cnt = 0
        x_F_0040 = 0.0F
        Time_ = 0.0
        While cnt < CInt(Math.Floor(usedMilliseconds / (1.0 / 40.0 * 1000.0) * Math.Floor(DataArray.Length / (usedMilliseconds / (1.0 / 40.0 * 1000.0)))))
            x_F_0040 += CSng(DataArray(cnt) * RealPart(40.0, Time_))
            Time_ += (usedMilliseconds / DataArray.Length)
            cnt += 1
        End While
        x_F_0040 /= cnt

        '№ 3
        cnt = 0
        x_F_0060 = 0.0F
        Time_ = 0.0
        While cnt < CInt(Math.Floor(usedMilliseconds / (1.0 / 60.0 * 1000.0) * Math.Floor(DataArray.Length / (usedMilliseconds / (1.0 / 60.0 * 1000.0)))))
            x_F_0060 += CSng(DataArray(cnt) * RealPart(60.0, Time_))
            Time_ += (usedMilliseconds / DataArray.Length)
            cnt += 1
        End While
        x_F_0060 /= cnt

        '№ 4
        cnt = 0
        x_F_0100 = 0.0F
        Time_ = 0.0
        While cnt < CInt(Math.Floor(usedMilliseconds / (1.0 / 100.0 * 1000.0) * Math.Floor(DataArray.Length / (usedMilliseconds / (1.0 / 100.0 * 1000.0)))))
            x_F_0100 += CSng(DataArray(cnt) * RealPart(100.0 / 1000.0F, Time_))
            Time_ += (usedMilliseconds / DataArray.Length)
            cnt += 1
        End While
        x_F_0100 /= cnt
        x_F_0100 /= 5.0F

        '№ 5
        cnt = 0
        x_F_0200 = 0.0F
        Time_ = 0.0
        While cnt < CInt(Math.Floor(usedMilliseconds / (1.0 / 200.0 * 1000.0) * Math.Floor(DataArray.Length / (usedMilliseconds / (1.0 / 200.0 * 1000.0)))))
            x_F_0200 += CSng(DataArray(cnt) * RealPart(200.0 / 1000.0, Time_))
            Time_ += (usedMilliseconds / DataArray.Length)
            cnt += 1
        End While
        x_F_0200 /= cnt
        x_F_0200 /= 5.0F

        '№ 6
        cnt = 0
        x_F_0300 = 0.0F
        Time_ = 0.0
        While cnt < CInt(Math.Floor(usedMilliseconds / (1.0 / 300.0 * 1000.0) * Math.Floor(DataArray.Length / (usedMilliseconds / (1.0 / 300.0 * 1000.0)))))
            x_F_0300 += CSng(DataArray(cnt) * RealPart(300.0 / 1000.0, Time_))
            Time_ += (usedMilliseconds / DataArray.Length)
            cnt += 1
        End While
        x_F_0300 /= cnt

        '№ 7
        cnt = 0
        x_F_0400 = 0.0F
        Time_ = 0.0
        While cnt < CInt(Math.Floor(usedMilliseconds / (1.0 / 400.0 * 1000.0) * Math.Floor(DataArray.Length / (usedMilliseconds / (1.0 / 400.0 * 1000.0)))))
            x_F_0400 += CSng(DataArray(cnt) * RealPart(400.0 / 1000.0, Time_))
            Time_ += (usedMilliseconds / DataArray.Length)
            cnt += 1
        End While
        x_F_0400 /= cnt

        '№ 8
        cnt = 0
        x_F_0500 = 0.0F
        Time_ = 0.0
        While cnt < CInt(Math.Floor(usedMilliseconds / (1.0 / 500.0 * 1000.0) * Math.Floor(DataArray.Length / (usedMilliseconds / (1.0 / 500.0 * 1000.0)))))
            x_F_0500 += CSng(DataArray(cnt) * RealPart(500.0 / 1000.0, Time_))
            Time_ += (usedMilliseconds / DataArray.Length)
            cnt += 1
        End While
        x_F_0500 /= cnt

        '№ 9
        cnt = 0
        x_F_0600 = 0.0F
        Time_ = 0.0
        While cnt < CInt(Math.Floor(usedMilliseconds / (1.0 / 600.0 * 1000.0) * Math.Floor(DataArray.Length / (usedMilliseconds / (1.0 / 600.0 * 1000.0)))))
            x_F_0600 += CSng(DataArray(cnt) * RealPart(600.0 / 1000.0, Time_))
            Time_ += (usedMilliseconds / DataArray.Length)
            cnt += 1
        End While
        x_F_0600 /= cnt

        '№ 10
        cnt = 0
        x_F_0700 = 0.0F
        Time_ = 0.0
        While cnt < CInt(Math.Floor(usedMilliseconds / (1.0 / 700.0 * 1000.0) * Math.Floor(DataArray.Length / (usedMilliseconds / (1.0 / 700.0 * 1000.0)))))
            x_F_0700 += CSng(DataArray(cnt) * RealPart(700.0 / 1000.0, Time_))
            Time_ += (usedMilliseconds / DataArray.Length)
            cnt += 1
        End While
        x_F_0700 /= cnt

        '№ 11
        cnt = 0
        x_F_1000 = 0.0F
        Time_ = 0.0
        While cnt < CInt(Math.Floor(usedMilliseconds / (1.0 / 1000.0 * 1000.0) * Math.Floor(DataArray.Length / (usedMilliseconds / (1.0 / 1000.0 * 1000.0)))))
            x_F_1000 += CSng(DataArray(cnt) * RealPart(1000.0 / 1000.0, Time_))
            Time_ += (usedMilliseconds / DataArray.Length)
            cnt += 1
        End While
        x_F_1000 /= cnt

        '№ 12
        cnt = 0
        x_F_2000 = 0.0F
        Time_ = 0.0
        While cnt < CInt(Math.Floor(usedMilliseconds / (1.0 / 2000.0 * 1000.0) * Math.Floor(DataArray.Length / (usedMilliseconds / (1.0 / 2000.0 * 1000.0)))))
            x_F_2000 += CSng(DataArray(cnt) * RealPart(2000.0 / 1000.0, Time_))
            Time_ += (usedMilliseconds / DataArray.Length)
            cnt += 1
        End While
        x_F_2000 /= cnt

        '№ 13
        cnt = 0
        x_F_3000 = 0.0F
        Time_ = 0.0
        While cnt < CInt(Math.Floor(usedMilliseconds / (1.0 / 3000.0 * 1000.0) * Math.Floor(DataArray.Length / (usedMilliseconds / (1.0 / 3000.0 * 1000.0)))))
            x_F_3000 += CSng(DataArray(cnt) * RealPart(3000.0 / 1000.0, Time_))
            Time_ += (usedMilliseconds / DataArray.Length)
            cnt += 1
        End While
        x_F_3000 /= cnt

        '№ 14
        cnt = 0
        x_F_5000 = 0.0F
        Time_ = 0.0
        While cnt < CInt(Math.Floor(usedMilliseconds / (1.0 / 5000.0 * 1000.0) * Math.Floor(DataArray.Length / (usedMilliseconds / (1.0 / 5000.0 * 1000.0)))))
            x_F_5000 += CSng(DataArray(cnt) * RealPart(5000.0 / 1000.0, Time_))
            Time_ += (usedMilliseconds / DataArray.Length)
            cnt += 1
        End While
        x_F_5000 /= cnt

        '№ 15
        cnt = 0
        x_F_7500 = 0.0F
        Time_ = 0.0
        While cnt < CInt(Math.Floor(usedMilliseconds / (1.0 / 7500.0 * 1000.0) * Math.Floor(DataArray.Length / (usedMilliseconds / (1.0 / 7500.0 * 1000.0)))))
            x_F_7500 += CSng(DataArray(cnt) * RealPart(7500.0 / 1000.0, Time_))
            Time_ += (usedMilliseconds / DataArray.Length)
            cnt += 1
        End While
        x_F_7500 /= cnt

        continue_ = False
    End Sub

    Private Shared Function getRealPart(fw As Double, t As Double) As Double
        Return Math.Cos(-2.0 * Math.PI * fw * t)
    End Function
$\endgroup$
12
  • 1
    $\begingroup$ "I've already answered my question from yesterday." Please add the answer to your original question and accept it. $\endgroup$
    – MBaz
    Commented Dec 18, 2021 at 17:44
  • 1
    $\begingroup$ Hey @MBaz 😃 I have made an answer. I can accept it in 23 hours. $\endgroup$
    – Christian
    Commented Dec 18, 2021 at 17:50
  • $\begingroup$ I don't fully understand your description, nor do I "speak" VB.NET, (and honestly, looking at this code, it looks pretty painful doing math in it) but from your description I see at least one design "noooope!": you're trying to communicate between threads using a global object, to which you add no access arbitratration, so your FFT might well be updating the data while your GUI thread tries to read it. That's a recipe for disaster! This gets even worse: I sadly can't predict how well your code performs, but seeing that this language really doesn't really enable you to do vectorized math, $\endgroup$ Commented Dec 18, 2021 at 19:07
  • $\begingroup$ I'm not confident the first FFT has finished before the next segment of audio needs to be consumed or the screen wants to be updated. Well, you say your test tone isn't "right"; so, what do you get instead? What happens when you vary the tone frequency? When you're testing this using simple sine/cosine samples (instead of "over the air" with unknown noise, imperfections), do you get the "right" results? $\endgroup$ Commented Dec 18, 2021 at 19:08
  • $\begingroup$ When you're testing this using simple sine/cosine samples (instead of "over the air" with unknown noise, imperfections), do you get the "right" results? No, in that case, alle the drawn bars are rising although only one should $\endgroup$
    – Christian
    Commented Dec 18, 2021 at 19:11

1 Answer 1

1
$\begingroup$

Sorry, you are not implementing an FFT. It looks like you are calculating the real part of the DFT (Discrete Fourier transform) at a few isolated frequencies. That's NOT how you build an audio spectrum analyzer. (if that is what you are trying to do).

A real analyzer will either use a band-pass filter bank with RMS detectors or implement a complete FFT (with ALL frequencies) and energy summing over third-octave or octave bands .

If you really want to role your own FFT (instead of using a library) I recommend using a standard SW development approach

  1. Start with the mathematical definition, make sure you understand it and then set quantitative requirements (max length, signal to noise, mem + CPU footprints, etc.)
  2. Get a known good reference to for testing purpose
  3. Debug & verify your code step by step. Start with a single impulse and a short FFT, work your way up to longer FFTs, a few more impulses, sine waves, multiple sine waves and noise signals.
  4. Always verify against a known good reference or mathematical result done by hand.

Once you are sure that your FFT works and is properly unit tested, you can start wrapping your application around it.

$\endgroup$
2
  • $\begingroup$ Ok, you're right, I do the calculation for discrete frequencies instead of a continuous band. If it's DFT instead of FFT, that's fine ... I'll edit my question a bit and describe it in more detail in a moment. I went on and on and can now give you specific examples. $\endgroup$
    – Christian
    Commented Dec 19, 2021 at 13:40
  • $\begingroup$ PS: I can't give you a +1 due to my low reputation. But I really appreciate. $\endgroup$
    – Christian
    Commented Dec 19, 2021 at 13:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.