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).
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)
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) { 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); aggregator.Add(real); } } |
Das Event wird immer dann aufgerufen wenn die FFT Berechnung vom
aggregator abgeschlossen ist.
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); drawRectangles = true; if (!IsDisposed) OnPaint(new PaintEventArgs(CreateGraphics(), new Rectangle(0, 0, 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:
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 { public event EventHandler<MaxSampleEventArgs> MaximumCalculated; private float maxValue; private float minValue; public int NotificationCount { get; set; } int count;
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; 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(this, new 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:
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.
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler