Metlivi Blog

So bauen Sie einen Bandsperrfilter mit separaten Reglern für Bandbreite und Mittenfrequenz

Bauen Sie einen digitalen Bandsperrfilter mit zwei Parametern: Mittenfrequenz in Hertz und Sperrbandbreite in Hertz. Berechnen Sie die Filterkoeffizienten jedes Mal neu, wenn sich einer der beiden Werte ändert. Wenn Ihre Filterbibliothek statt der Bandbreite den Gütefaktor Q erwartet, berechnen Sie bei jeder Aktualisierung Q = center / width, damit beim Verschieben der Mittenfrequenz die gewünschte Bandbreite erhalten bleibt.

Dieses Tutorial erstellt einen funktionsfähigen Python-Audioprototyp, der nur die Standardbibliothek verwendet. Er erzeugt ein Testsignal, verarbeitet es, überprüft die Sperrbandbreite und exportiert auf Wunsch WAV-Dateien zum Anhören. Die Bedienelemente sind Kommandozeilenparameter; dieselbe Koeffizientenfunktion kann später zwei Reglern in einer Audioanwendung zugrunde liegen.

29. Oktober 20269 Minuten LesezeitVon Metlivi Editorial Team
Abschnitt 1

Was genau sollen die beiden Regler verändern?

Der Regler für die Mittenfrequenz legt die Frequenz der maximalen Dämpfung fest. Der Breitenregler legt den Abstand zwischen den beiden Halb-Leistungs-Grenzen fest, die üblicherweise als −3-dB-Punkte bezeichnet werden. Eine Kerbe ist eine schmalbandige Form eines Bandsperrfilters; in der offiziellen SciPy-Dokumentation ist der Gütefaktor als Mittenfrequenz geteilt durch diese Bandbreite definiert. SciPy: iirnotch

Legen Sie vor dem Entwurf der Regler die Einheit der Breite fest. Eine in Hertz angegebene Breite erhält einen absoluten Frequenzbereich. Eine in Oktaven angegebene Breite beschreibt ein Frequenzverhältnis. Das Audio-EQ-Kochbuch des W3C unterstützt für Kerbfilter ausdrücklich eine Bandbreite in Oktaven und berücksichtigt in seinen Formeln für digitale Koeffizienten die Frequenzverzerrung. Diese Einheiten führen zu unterschiedlichem Regelverhalten; kennzeichnen Sie die gewählte Einheit daher deutlich. W3C: Audio-EQ-Kochbuch

Für diesen Aufbau verwenden wir Hertz. Angenommen, die Mittenfrequenz beträgt 1.000 Hz und die Breite 200 Hz: Der entsprechende Q-Wert ist 5. Wird die Mittenfrequenz auf 2.000 Hz verschoben und die Breite von 200 Hz beibehalten, muss Q auf 10 steigen. Bei einem konstanten Q-Wert von 5 würde die Bandbreite stattdessen 400 Hz betragen. Diese Beispielrechnungen zeigen, warum ein Regler mit festem Q-Wert die hier gewählte Aufgabe nicht erfüllt.

Abschnitt 2

Welche Filterstruktur ermöglicht separate Regler?

Verwenden Sie ein Filter zweiter Ordnung mit unendlicher Impulsantwort, auch Biquad genannt. Es hat drei Zählerkoeffizienten und drei Nennerkoeffizienten. Nachdem der erste Nennerkoeffizient auf eins normiert wurde, lautet die Abtastgleichung:

[ y[n]=b_0x[n]+b_1x[n-1]+b_2x[n-2]-a_1y[n-1]-a_2y[n-2]. ]

Diese Biquad-Beziehung ist im Audio-EQ-Kochbuch dokumentiert. W3C: Audio-EQ-Kochbuch

Für eine Abtastrate (F_s), eine Mittenfrequenz (f_c) und eine Breite (B) berechnen Sie:

[ \omega_c=2\pi f_c/F_s,\qquad \beta=\tan(\pi B/F_s),\qquad g=\frac{1}{1+\beta}. ]

Verwenden Sie anschließend:

[ b=g[1,-2\cos(\omega_c),1], ]

[ a=[1,-2g\cos(\omega_c),2g-1]. ]

Dies sind die in der SciPy-Implementierung verwendeten Koeffizientengleichungen für Kerbfilter, umgeschrieben, sodass die Bandbreite in Hertz direkt als Eingabe dient. SciPy-Quellcode für den Filterentwurf

Beide Regler können mehrere Koeffizienten verändern. Ihre Unabhängigkeit bezieht sich auf das resultierende Frequenzverhalten: Eine Änderung der Breite lässt die Sperrfrequenz unverändert, während eine Änderung der Mittenfrequenz die angegebene Bandbreite erhält. Jeden Regler einfach nur einem Koeffizienten zuzuordnen, würde dieses Verhalten nicht sicherstellen.

Abschnitt 3

Wie erstellen Sie einen funktionsfähigen Audioprototyp?

Speichern Sie das folgende vollständige Programm als band_reject.py. Es benötigt Python 3.8 oder neuer und keine zusätzlichen Pakete. Die Abtastrate beträgt 48.000 Hz; das erzeugte Eingangssignal enthält drei Töne mit 300, 1.000 und 5.000 Hz. Jeder Ton hat eine Amplitude von 0,2.

Das Programm wertet den Frequenzgang des Filters direkt aus, ermittelt beide Halb-Leistungs-Grenzen durch Bisektion und verarbeitet zwei Sekunden erzeugtes Audio. Für den WAV-Export verwendet es die dokumentierte Python-Schnittstelle wave, um Mono-Dateien mit 16-Bit-PCM zu schreiben. Wenn Ausgabedateien bereits vorhanden sind, wird ein Fehler ausgegeben, statt sie zu überschreiben. Python: wave — WAV-Dateien lesen und schreiben

import argparse
import cmath
import math
import struct
import wave


def coefficients(center, width, fs):
    if not all(math.isfinite(v) for v in (center, width, fs)):
        raise ValueError('Parameters must be finite')
    if not (fs > 0 and 0 < center < fs / 2 and 0 < width < fs / 2):
        raise ValueError('Require fs > 0 and center, width inside (0, fs/2)')
    beta = math.tan(math.pi * width / fs)
    g = 1 / (1 + beta)
    c = math.cos(2 * math.pi * center / fs)
    return (g, -2 * g * c, g), (1.0, -2 * g * c, 2 * g - 1)


def magnitude(f, b, a, fs):
    z = cmath.exp(-2j * math.pi * f / fs)
    return abs((b[0] + b[1]*z + b[2]*z*z) /
               (a[0] + a[1]*z + a[2]*z*z))


def edge(lo, hi, b, a, fs):
    target = 1 / math.sqrt(2)
    for _ in range(60):
        mid = (lo + hi) / 2
        if ((magnitude(lo, b, a, fs) - target) *
                (magnitude(mid, b, a, fs) - target)) > 0:
            lo = mid
        else:
            hi = mid
    return (lo + hi) / 2


def process(samples, b, a):
    x1 = x2 = y1 = y2 = 0.0
    result = []
    for x in samples:
        y = b[0]*x + b[1]*x1 + b[2]*x2 - a[1]*y1 - a[2]*y2
        result.append(y)
        x2, x1, y2, y1 = x1, x, y1, y
    return result


def write_wav(path, samples, fs):
    if any(not math.isfinite(x) or abs(x) > 1 for x in samples):
        raise ValueError('Output exceeds PCM range; lower the input level')
    data = b''.join(struct.pack('<h', round(x * 32767)) for x in samples)
    with open(path, 'xb') as stream:
        with wave.open(stream, 'wb') as wav:
            wav.setnchannels(1)
            wav.setsampwidth(2)
            wav.setframerate(fs)
            wav.writeframes(data)


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--center', type=float, default=1000)
    parser.add_argument('--width', type=float, default=200)
    parser.add_argument('--write', action='store_true')
    args = parser.parse_args()
    fs = 48000
    b, a = coefficients(args.center, args.width, fs)
    low = edge(0, args.center, b, a, fs)
    high = edge(args.center, fs / 2, b, a, fs)
    print(f'Edges: {low:.3f}, {high:.3f} Hz; width: {high-low:.3f} Hz')
    assert math.isclose(high-low, args.width, abs_tol=1e-5)
    assert magnitude(args.center, b, a, fs) < 1e-7
    tones = (300, 1000, 5000)
    for f in tones:
        db = 20 * math.log10(max(magnitude(f, b, a, fs), 1e-12))
        print(f'{f} Hz: {db:.2f} dB')
    samples = [sum(0.2 * math.sin(2*math.pi*f*n/fs) for f in tones)
               for n in range(2*fs)]
    filtered = process(samples, b, a)
    assert len(filtered) == len(samples)
    assert all(math.isfinite(y) for y in filtered)
    if args.write:
        write_wav('input-demo.wav', samples, fs)
        write_wav('filtered-demo.wav', filtered, fs)


if __name__ == '__main__':
    main()

Führen Sie python band_reject.py --center 1000 --width 200 aus, um den Frequenzgang zu überprüfen. Ergänzen Sie --write, um input-demo.wav und filtered-demo.wav zu erstellen. Importieren Sie die Dateien in einem Audiobearbeitungsprogramm auf separate Spuren, richten Sie ihre Anfänge aneinander aus und vergleichen Sie sie bei unveränderter Wiedergabelautstärke.

Ich habe das vollständige Programm ohne Dateiexport ausgeführt. Die Assertions zur Bandbreite, zur Dämpfung bei der Mittenfrequenz, zur Ausgabelänge und zu endlichen Ausgabewerten waren erfolgreich. Der WAV-Export wurde bei diesem Durchlauf nicht getestet. Der ausgegebene Frequenzgang lag bei 300 Hz bei ungefähr −0,02 dB und bei 5.000 Hz bei −0,01 dB; der Wert bei der Mittenfrequenz erreichte die Anzeigegrenze von −240 dB. Diese Grenze ist ein numerisches Limit der Ausgabe und keine gemessene Dämpfungstiefe der Audiodatei.

Abschnitt 4

Wie können Sie überprüfen, ob die Regler unabhängig voneinander arbeiten?

Testen Sie drei Einstellungen: eine Ausgangseinstellung, eine Einstellung mit veränderter Mittenfrequenz und unveränderter Breite sowie eine Einstellung mit veränderter Breite und unveränderter Mittenfrequenz. So lässt sich jeder Regler einzeln prüfen. Die folgenden Werte wurden durch Ausführung der Koeffizienten- und Grenzsuche bei 48 kHz berechnet. Sie beschreiben den mathematischen Frequenzgang und keine Messung einer Aufnahme.

Einstellung der Mittenfrequenz Einstellung der Breite Untere Halb-Leistungs-Grenze Obere Halb-Leistungs-Grenze Berechnete Bandbreite
1.000 Hz 200 Hz 904,959 Hz 1.104,959 Hz 200,000 Hz
2.000 Hz 200 Hz 1.902,441 Hz 2.102,441 Hz 200,000 Hz
1.000 Hz 400 Hz 819,692 Hz 1.219,692 Hz 400,000 Hz

Beachten Sie, dass die Grenzfrequenzen nicht exakt Mittenfrequenz ± Breite/2 entsprechen. „Mittenfrequenz“ bezeichnet hier die Sperrstelle, während „Breite“ den Abstand zwischen den Halb-Leistungs-Schnittpunkten angibt. Der berechnete Frequenzgang zeigt, dass deren arithmetischer Mittelpunkt nicht zwingend der Sperrfrequenz entspricht. Erstellen Sie die Anzeige der Grenzfrequenzen daher nicht, indem Sie einfach die halbe Breite addieren und subtrahieren.

Für eine zusätzliche numerische Prüfung in einer Umgebung mit SciPy können Sie die zurückgegebenen Koeffizienten mit scipy.signal.freqz auswerten. Geben Sie fs und explizite Frequenzen an, darunter die exakte Mittenfrequenz. In der Dokumentation wird erklärt, dass ein explizites Frequenzarray festlegt, an welchen Stellen der Frequenzgang ausgewertet wird; in einem groben Standardraster kann eine schmale Sperrstelle übersehen werden. SciPy: freqz

Abschnitt 5

Wie wählen Sie Mittenfrequenz und Breite für einen Klang aus?

Verwenden Sie einen kurzen, wiederholbaren Ausschnitt des Klangs, den Sie bearbeiten. Ermitteln Sie zunächst den Frequenzbereich, den Sie reduzieren möchten, und stellen Sie dann die Mittenfrequenz darauf ein. Beginnen Sie mit einer schmalen Bandbreite und erhöhen Sie sie, bis die Dämpfung den gewünschten Bereich abdeckt. Vergleichen Sie nach jeder Änderung mit dem Bypass und behalten Sie die Wiedergabelautstärke bei, damit der Vergleich aussagekräftig bleibt.

Nutzen Sie diese Entscheidungshilfe für die nächste Anpassung:

Was der Vergleich zeigt Nächste Anpassung
Der unerwünschte Ton bleibt hörbar, aber benachbarte Klanganteile verändern sich Prüfen Sie die Mittenfrequenz erneut, bevor Sie die Bandbreite erhöhen
Die Mittenfrequenz stimmt, aber der unerwünschte Bereich reicht darüber hinaus Erhöhen Sie die Bandbreite schrittweise
Zu viel des umgebenden Klangcharakters verschwindet Verringern Sie die Bandbreite und vergleichen Sie erneut
Mehrere getrennte Töne sollen reduziert werden Prüfen Sie separate Kerbfilterstufen für diese Frequenzen
Ein breiter, flacher Sperrbereich ist erforderlich Überdenken Sie Filterform und Ordnung

Dies sind praktische Empfehlungen zur Anpassung und keine Messwerte einer bestimmten Aufnahme. Das Beispiel mit drei Tönen ist bewusst einfach gehalten: So können Sie zunächst feststellen, ob der Prozessor korrekt arbeitet, bevor Sie einen komplexeren Klang beurteilen.

Ein einzelner Kerbfilter bietet außerdem keinen unabhängig einstellbaren Regler für die Dämpfungstiefe. Das Kochbuch enthält separate Koeffizientenfamilien für Kerbfilter und parametrische Equalizer. Wenn Sie statt einer Sperrstelle eine moderate, einstellbare Absenkung benötigen, ist ein parametrischer Equalizer ein weiterer geeigneter Entwurf, den Sie in Betracht ziehen können. W3C: Audio-EQ-Kochbuch

Abschnitt 6

Was ändert sich, wenn Sie zwei Regler für die Echtzeitsteuerung anschließen?

Behalten Sie dieselbe Parameterschnittstelle bei: center_hz und width_hz werden gemeinsam an die Koeffizientenfunktion übergeben. Aktualisieren Sie den vollständigen Koeffizientensatz, sobald sich einer der Parameter ändert, und berechnen Sie ihn bei einer Änderung der Abtastrate ebenfalls neu. Bei diesem Prototyp müssen beide Regler strikt größer als null und kleiner als die halbe Abtastrate sein; ungültige Einstellungen lösen eine Ausnahme aus.

Die bereitgestellte Funktion process() verarbeitet einen vollständigen Monopuffer und beginnt bei jedem Aufruf mit einem leeren Verlaufsspeicher. Eine Streaming-Version muss den Verzögerungsverlauf zwischen den Blöcken beibehalten. SciPys lfilter dokumentiert dafür den anfänglichen Verzögerungszustand und den zurückgegebenen Endzustand. Verwenden Sie für jeden Audiokanal einen separaten Verlaufsspeicher. SciPy: lfilter

Die Bewegung der Regler erfordert einen zusätzlichen Implementierungsschritt. Dieses Beispiel prüft feste Einstellungen und implementiert keine kontinuierliche Automatisierung. Eine praktische Methode für den Übergang besteht darin, die alten und neuen Filter mit festen Einstellungen kurz parallel laufen zu lassen, dem neuen Filter Zeit zu geben, seinen Verlaufsspeicher aufzubauen, und ihre Ausgänge überzublenden. Prüfen Sie den Übergang mit dem tatsächlichen Audiomaterial und der vorgesehenen Regelgeschwindigkeit.

Bei mehreren hintereinander geschalteten Filtern sollten Sie jedes als separate Sektion zweiter Ordnung beibehalten, statt die Kaskade zu einem Polynom hoher Ordnung zusammenzufassen. SciPy empfiehlt für die meisten Filteraufgaben Sektionen zweiter Ordnung, da dabei weniger numerische Probleme auftreten. SciPy: lfilter

Abschnitt 7

Welche Fehlerbedingungen sollten Sie zuerst prüfen?

Wenn die Sperrstelle bei der falschen Frequenz liegt, prüfen Sie, ob für die Koeffizientenberechnung dieselbe Abtastrate wie für das Audio verwendet wurde. Wenn die Bandbreite beim Verschieben der Mittenfrequenz zunimmt, prüfen Sie, ob Ihr Wrapper Q konstant hält. Wenn der angezeigte Frequenzgang flach erscheint, werten Sie zuerst die exakte Mittenfrequenz aus, bevor Sie den Entwurf ändern.

Wenn bei wiederholten Puffern Sprünge auftreten, prüfen Sie die Zustandsverwaltung: Die Demonstration setzt den Verlaufsspeicher bei jedem Aufruf von process() zurück. Wenn der WAV-Export einen Bereichsfehler auslöst, verringern Sie die Eingangsverstärkung und führen Sie das Programm erneut aus; das Programm verhindert bewusst, dass Werte außerhalb des zulässigen Bereichs stillschweigend abgeschnitten werden. Benennen Sie vorhandene Demonstrationsdateien um oder entfernen Sie sie, bevor Sie ein weiteres Dateipaar exportieren.

Der Aufbau ist abgeschlossen, wenn der Test der Mittenfrequenz die Sperrstelle an die gewünschte Position setzt, der Abstand zwischen den beiden Grenzfrequenzen der gewünschten Bandbreite entspricht und der verarbeitete Klang die beabsichtigte Bearbeitung ermöglicht. Behalten Sie diese Prüfungen zusammen mit den beiden Reglern bei, wenn Sie vom Offline-Prototyp zu einem interaktiven Audiowerkzeug übergehen.

Abschnitt 8

Quellen

Weitere Artikel

Dieses Thema weiter erkunden