Autor Beitrag
C#
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Sa 02.11.13 23:00 
Hey Leute,

ich habe mal vor einiger Zeit ein Thread eröffnet zum Thema SpectrumAnalyser in NAudio. Habe das Thema jetzt wieder aufgegriffen und möchte einen Spektrumanalysator selbst schreiben. Ich habe wieder NAudio benutzt aber ich weiß nicht ob ich es richtig verwebdet habe, da meine ausschläge bei den einzelnen Frequenzen sehr gering sind. Außerdem wird das Maximum (float 1.0) nie erreicht. Alle Frequenzanteile liegen unter 0.5.
Ich erhalte außerdem ein ein Grundausschlag, auch wenn kein Audiosignal abgespielt wird. Das erste Bild zeigt das Grundrauschen. Das zweite Bild zeigt den Ausschlag bei einem recht basslastigen Lied. Ich verwende eine FFT-Länge von 256 (eigentlich 512 aber der zweite Teil ist ja nur der Spiegel bei realen Signalen).
SpectrumAnalyzer_Grundrauschen
SpectrumAnalyzer_Bassmusik
Der weiße Balken ganz links gibt das absolute Maximum an (=1.0), der orangene Balken darin gibt den momentanen Höchstwert an. Das Label darunter gibt nochmal den momentanen Höchstwert als Zahl an.

Die entscheidenden Code-Ausschnitte:

Dieses Event wird immer aufgerufen wenn Daten vom Streamingdevie (in diesem Fall Stereomix) empfangen werden. Dieses Event wird auch aufgerufen wenn kein Audiosignal anliegt (siehe Grundrauschen)
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
private void WaveInDataVailable(object sender, WaveInEventArgs e)
    {
      uint val = 0;

      int bps = input.WaveFormat.BitsPerSample / 8;
      for (int i = 0; i < e.BytesRecorded; i += bps)
      {
        switch (input.WaveFormat.BitsPerSample)    // Getting the current value depending on the wave resolution
        {
          case 16:
            val = BitConverter.ToUInt16(e.Buffer, i);
            break;

          case 24:
            val = Convert.ToUInt32(e.Buffer[i] + (e.Buffer[i + 1] << 8) + (e.Buffer[i + 2] << 16));
            break;

          case 32:
            val = BitConverter.ToUInt32(e.Buffer, i);
            break;
        }

        float real = (float)(val * 2 / (Math.Pow(2, input.WaveFormat.BitsPerSample) - 1.0d) - 1.0f);  // converting the value to a range from -1 to +1 and add it to the fft aggregator
        //float real = (float)(val / (Math.Pow(2, input.WaveFormat.BitsPerSample) - 1.0d));  // converting the value to a range from 0 to +1 and add it to the fft aggregator
        
        aggregator.Add(real);
      }
    }


Das Event wird immer dann aufgerufen wenn die FFT Berechnung vom aggregator abgeschlossen ist.
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
    private void AggregatorCalculatedFFT(object sender, FftEventArgs e)
    {
      for (int i = 0; i < filter; i++)
        spectrums[i] = (float) Math.Sqrt(e.Result[i].X * e.Result[i].X + e.Result[i].Y * e.Result[i].Y);
        //spectrums[i] = e.Result[i].X;
      
      drawRectangles = true;
      if (!IsDisposed) OnPaint(new PaintEventArgs(CreateGraphics(), new Rectangle(00, Width, Height)));
    }

spectrums sollte eigentlich die einzelnen Werte von 0.0 bis 1.0 für die entsprechenden Frequenzen enthalten.

Der Code vom aggregator ist aus der NAudio Library:
ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
    public class SampleAggregator
    {
        // volume
        public event EventHandler<MaxSampleEventArgs> MaximumCalculated;
        private float maxValue;
        private float minValue;
        public int NotificationCount { get; set; }
        int count;

        // FFT
        public event EventHandler<FftEventArgs> FftCalculated;
        public bool PerformFFT { get; set; }
        private Complex[] fftBuffer;
        private FftEventArgs fftArgs;
        private int fftPos;
        private int fftLength;
        private int m;

        public SampleAggregator(int fftLength = 1024)
        {
            if (!IsPowerOfTwo(fftLength))
            {
                throw new ArgumentException("FFT Length must be a power of two");
            }
            this.m = (int)Math.Log(fftLength, 2.0);
            this.fftLength = fftLength;
            this.fftBuffer = new Complex[fftLength];
            this.fftArgs = new FftEventArgs(fftBuffer);
        }

        bool IsPowerOfTwo(int x)
        {
            return (x & (x - 1)) == 0;
        }


        public void Reset()
        {
            count = 0;
            maxValue = minValue = 0;
        }

        public void Add(float value)
        {
            if (PerformFFT && FftCalculated != null)
            {
              fftBuffer[fftPos].X = (float) (value * FastFourierTransform.HammingWindow(fftPos, fftBuffer.Length));
                fftBuffer[fftPos].Y = 0;
                fftPos++;
                if (fftPos >= fftBuffer.Length)
                {
                    fftPos = 0;
                    // 1024 = 2^10
                    FastFourierTransform.FFT(true, m, fftBuffer);
                    FftCalculated(this, fftArgs);
                }
            }

            maxValue = Math.Max(maxValue, value);
            minValue = Math.Min(minValue, value);
            count++;
            if (count >= NotificationCount && NotificationCount > 0)
            {
                if (MaximumCalculated != null)
                {
                    MaximumCalculated(thisnew MaxSampleEventArgs(minValue, maxValue));
                }
                Reset();
            }
        }
    }

    public class MaxSampleEventArgs : EventArgs
    {
        [DebuggerStepThrough]
        public MaxSampleEventArgs(float minValue, float maxValue)
        {
            this.MaxSample = maxValue;
            this.MinSample = minValue;
        }
        public float MaxSample { get; private set; }
        public float MinSample { get; private set; }
    }

    public class FftEventArgs : EventArgs
    {
        [DebuggerStepThrough]
        public FftEventArgs(Complex[] result)
        {
            this.Result = result;
        }
        public Complex[] Result { get; private set; }
    }


Und zuguterletzt noch die FFT und HammingWindow Funktion aus der NAudio Library:
ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
 public class FastFourierTransform
  {
    public static void FFT(bool forward, int m, Complex[] data)
    {
      int num1 = 1;
      for (int index = 0; index < m; ++index)
        num1 *= 2;
      int num2 = num1 >> 1;
      int index1 = 0;
      for (int index2 = 0; index2 < num1 - 1; ++index2)
      {
        if (index2 < index1)
        {
          float num3 = data[index2].X;
          float num4 = data[index2].Y;
          data[index2].X = data[index1].X;
          data[index2].Y = data[index1].Y;
          data[index1].X = num3;
          data[index1].Y = num4;
        }
        int num5 = num2;
        while (num5 <= index1)
        {
          index1 -= num5;
          num5 >>= 1;
        }
        index1 += num5;
      }
      float num6 = -1f;
      float num7 = 0.0f;
      int num8 = 1;
      for (int index2 = 0; index2 < m; ++index2)
      {
        int num3 = num8;
        num8 <<= 1;
        float num4 = 1f;
        float num5 = 0.0f;
        for (int index3 = 0; index3 < num3; ++index3)
        {
          int index4 = index3;
          while (index4 < num1)
          {
            int index5 = index4 + num3;
            float num9 = (float) ((double) num4 * (double) data[index5].X - (double) num5 * (double) data[index5].Y);
            float num10 = (float) ((double) num4 * (double) data[index5].Y + (double) num5 * (double) data[index5].X);
            data[index5].X = data[index4].X - num9;
            data[index5].Y = data[index4].Y - num10;
            data[index4].X += num9;
            data[index4].Y += num10;
            index4 += num8;
          }
          float num11 = (float) ((double) num4 * (double) num6 - (double) num5 * (double) num7);
          num5 = (float) ((double) num4 * (double) num7 + (double) num5 * (double) num6);
          num4 = num11;
        }
        num7 = (float) Math.Sqrt((1.0 - (double) num6) / 2.0);
        if (forward)
          num7 = -num7;
        num6 = (float) Math.Sqrt((1.0 + (double) num6) / 2.0);
      }
      if (!forward)
        return;
      for (int index2 = 0; index2 < num1; ++index2)
      {
        data[index2].X /= (float) num1;
        data[index2].Y /= (float) num1;
      }
    }

    public static double HammingWindow(int n, int frameSize)
    {
      return 0.54 - 0.46 * Math.Cos(2.0 * Math.PI * (double) n / (double) (frameSize - 1));
    }

    public static double HannWindow(int n, int frameSize)
    {
      return 0.5 * (1.0 - Math.Cos(2.0 * Math.PI * (double) n / (double) (frameSize - 1)));
    }

    public static double BlackmannHarrisWindow(int n, int frameSize)
    {
      return 287.0 / 800.0 - 0.48829 * Math.Cos(2.0 * Math.PI * (double) n / (double) (frameSize - 1)) + 0.14128 * Math.Cos(4.0 * Math.PI * (double) n / (double) (frameSize - 1)) - 0.01168 * Math.Cos(4.0 * Math.PI * (double) n / (double) (frameSize - 1));
    }
  }


Kann mir jemand sagen ob ich das richtig gemacht habe? Stimmt das maximum von 1.0 nach der FFT noch welches ich davor berechnet habe oder hat das nichts mehr miteeinander zu tun? Ich verstehe die Berechnung der FFT nicht.
Einloggen, um Attachments anzusehen!
_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
FinnO
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 1331
Erhaltene Danke: 123

Mac OSX, Arch
TypeScript (Webstorm), Kotlin, Clojure (IDEA), Golang (VSCode)
BeitragVerfasst: So 03.11.13 01:19 
Moin,

wenn du dir wirklich sicher sein willst, ob deine Fouriertransformation funktioniert, dann solltest du ein Testsignal in den Frequenzbereich transformieren, von dem dir das Spektrum bekannt ist, zum Beispiel eine Sinusförmige Grundschwingung.

Das Maximum von 1.0 nach der Fouriertransformation stimmt im Prinzip immer noch, jedoch musst du bedenken, dass dein Eingangssignal wenn überhaupt auch nur äußerst selten den Maximalwert erreicht. Und wenn du dir mal das Spektrum einer Cosinusschwingung anschaust, wirst du feststellen, dass sich dieses auch aus zwei Dirac-Impulsen mit einem Betrag von je 0.5 zusammensetzt (je einer bei f und -f), sprich es gibt nicht besonders viele Signale, die ein Maximum im Spektrum garantieren.

Wenn du dir die Fouriertransformation alleine erarbeiten musst, wünsche ich dir viel Spaß :P

Viele Grüße
C# Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: So 03.11.13 01:32 
Ja ich mach das so zu sagen als Hobby :D Ich hab n selbstgebauten Moving Head und den will ich jetzt eben "zur Musik tanzen" lassen. Dafür bracuh ich hald die FFT... :puke:
Ich teste mal morgen / nacher den Eingang mit einer festgelegten Frequenz. Mal sehn obs klappt xD.
Falls es noch jemand Testen will oder sich den kompletten Code ansehen will habe ich das Projekt mal angehängt. Es ist aber nur das nötigste eingebaut, sodass ich mal die FFT testen kann, also nicht wundern wenns etwas kaotisch ist :mrgreen:


/// EDIT
So sieht das ganze jetzt aus nach dem Test mit Console.Beep() (in diesem Fall mit 200Hz für 5 Sekunden:
SpectrumAnalyzer_200HzTon

Irre ich mich oder passt das nicht so ganz? Ich habe aber keine Ahnung woher die Oberschwingungen kommen. Oder ist das normal?
Einloggen, um Attachments anzusehen!
_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler