Cara Membina Penapis Tolak Jalur dengan Kawalan Lebar dan Pusat yang Berasingan
Bina penapis tolak jalur digital dengan dua parameter: frekuensi pusat dalam hertz dan lebar jalur penolakan dalam hertz. Kira semula pekali penapis setiap kali salah satu parameter berubah. Jika pustaka penapis anda menerima Q dan bukannya lebar jalur, kira Q = center / width pada setiap kemas kini supaya perubahan pada pusat mengekalkan lebar yang diminta.
Tutorial ini membina prototaip audio Python yang berfungsi dengan hanya menggunakan pustaka standard. Ia menjana isyarat ujian, memprosesnya, mengesahkan lebar jalur penolakan dan secara pilihan mengeksport fail WAV untuk didengar. Kawalannya ialah parameter baris perintah; fungsi pekali yang sama kemudiannya boleh digunakan untuk dua tombol dalam aplikasi audio.
Apakah Sebenarnya Perubahan yang Dibuat oleh Kedua-dua Kawalan Ini?
Kawalan pusat menetapkan frekuensi penolakan maksimum. Kawalan lebar menetapkan jarak antara dua sempadan separuh kuasa, yang lazimnya dipanggil titik −3 dB. Takuk ialah bentuk sempit jalur bagi penapis henti jalur; dokumentasi rasmi SciPy mentakrifkan faktor kualitinya sebagai frekuensi pusat dibahagikan dengan lebar jalur ini. SciPy: iirnotch
Pilih unit lebar sebelum mereka bentuk kawalan. Lebar yang dinyatakan dalam hertz mengekalkan julat frekuensi mutlak. Lebar yang dinyatakan dalam oktaf menerangkan nisbah frekuensi. Audio EQ Cookbook W3C menyokong lebar jalur oktaf secara jelas untuk penapis takuk dan mengambil kira herotan frekuensi dalam formula pekali digitalnya. Kedua-duanya menghasilkan tingkah laku kawalan yang berbeza, jadi nyatakan unit yang dipilih dengan jelas. W3C: Audio EQ Cookbook
Untuk binaan ini, gunakan hertz. Katakan pusatnya 1,000 Hz dan lebarnya 200 Hz: nilai Q yang sepadan ialah 5. Jika pusat dialihkan ke 2,000 Hz sambil mengekalkan lebar 200 Hz, Q perlu menjadi 10. Jika Q dikekalkan pada 5, lebar jalur sebaliknya akan menjadi 400 Hz. Pengiraan contoh ini menjelaskan sebab kawalan Q tetap tidak memenuhi tugasan yang kita pilih.
Struktur Penapis Apakah yang Memberikan Kawalan Berasingan?
Gunakan satu penapis tindak balas impuls tak terhingga tertib kedua, yang juga dikenali sebagai biquad. Penapis ini mempunyai tiga pekali pengangka dan tiga pekali penyebut. Selepas pekali penyebut pertama dinormalkan kepada satu, persamaan sampelnya ialah:
[ y[n]=b_0x[n]+b_1x[n-1]+b_2x[n-2]-a_1y[n-1]-a_2y[n-2]. ]
Hubungan biquad ini didokumenkan dalam Audio EQ Cookbook. W3C: Audio EQ Cookbook
Bagi kadar pensampelan \(F_s\), pusat \(f_c\) dan lebar \(B\), kira:
[ \omega_c=2\pi f_c/F_s,\qquad \beta=\tan(\pi B/F_s),\qquad g=\frac{1}{1+\beta}. ]
Kemudian gunakan:
[ b=g[1,-2\cos(\omega_c),1], ]
[ a=[1,-2g\cos(\omega_c),2g-1]. ]
Inilah persamaan pekali takuk yang digunakan dalam pelaksanaan SciPy, ditulis semula supaya lebar jalur dalam hertz menjadi input langsung. Kod sumber reka bentuk penapis SciPy
Kedua-dua kawalan boleh mengubah beberapa pekali. Kebebasan masing-masing merujuk kepada tindak balas yang terhasil: apabila lebar diubah, frekuensi penolakan kekal; apabila pusat diubah, lebar jalur yang ditetapkan kekal. Menetapkan setiap tombol kepada satu pekali sahaja tidak akan menghasilkan tingkah laku tersebut.
Bagaimanakah Anda Membina Prototaip Audio yang Berfungsi?
Simpan program lengkap berikut sebagai band_reject.py. Program ini memerlukan Python 3.8 atau lebih baharu dan tiada pakej tambahan. Kadar pensampelannya ialah 48,000 Hz, dan input yang dijana mengandungi tiga nada pada 300, 1,000 dan 5,000 Hz. Setiap nada mempunyai amplitud 0.2.
Program ini menilai tindak balas penapis secara langsung, mencari kedua-dua sempadan separuh kuasa dengan kaedah pembahagian dua dan memproses dua saat audio yang dijana. Eksport WAV menggunakan antara muka wave yang didokumenkan oleh Python untuk menulis fail PCM mono 16-bit. Jika fail output sedia ada, program akan memaparkan ralat dan bukannya menimpanya. Python: wave — Membaca dan menulis fail WAV
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()
Jalankan python band_reject.py --center 1000 --width 200 untuk menyemak tindak balas. Tambahkan --write untuk mencipta input-demo.wav dan filtered-demo.wav. Import fail tersebut ke dalam editor audio pada trek berasingan, selaraskan permulaannya dan bandingkan dengan gandaan main balik yang tidak berubah.
Saya telah melaksanakan program lengkap tanpa eksport fail. Pengesahan lebar jalur, penolakan pusat, panjang output dan output terhingga semuanya lulus. Penulisan WAV tidak diuji dalam pelaksanaan itu. Tindak balas yang dicetak adalah kira-kira −0.02 dB pada 300 Hz dan −0.01 dB pada 5,000 Hz; nilai pusat mencapai had paparan −240 dB. Had itu ialah had pelaporan berangka, bukannya kedalaman penolakan yang diukur daripada fail audio.
Bagaimanakah Anda Mengesahkan Bahawa Kawalan Ini Bebas antara Satu Sama Lain?
Uji tiga tetapan: tetapan asas, pusat yang diubah dengan lebar yang tidak berubah, serta lebar yang diubah dengan pusat yang tidak berubah. Ini mengasingkan setiap kawalan. Nilai berikut dikira dengan melaksanakan rutin pekali dan carian sempadan pada 48 kHz; nilai ini menerangkan tindak balas matematik, bukannya ukuran rakaman.
| Tetapan pusat | Tetapan lebar | Sempadan separuh kuasa bawah | Sempadan separuh kuasa atas | Julat yang dikira |
|---|---|---|---|---|
| 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 |
Perhatikan bahawa sempadannya tidak sama tepat dengan center ± width/2. Dalam konteks ini, “pusat” merujuk kepada titik nol penolakan, manakala “lebar” merujuk kepada jarak antara lintasan separuh kuasa. Tindak balas yang dikira menunjukkan bahawa titik tengah aritmetik kedua-dua sempadan itu tidak semestinya sama dengan frekuensi nol. Jangan bina paparan sempadan dengan hanya menambah dan menolak separuh lebar.
Untuk semakan berangka tambahan dalam persekitaran yang dilengkapi SciPy, nilai pekali yang dipulangkan dengan scipy.signal.freqz. Berikan fs dan frekuensi secara eksplisit, termasuk frekuensi pusat yang tepat. Dokumentasi menerangkan bahawa tatasusunan frekuensi eksplisit menentukan lokasi penilaian tindak balas; grid lalai yang kasar boleh terlepas nol yang sempit. SciPy: freqz
Bagaimanakah Anda Patut Memilih Pusat dan Lebar untuk Sesuatu Bunyi?
Gunakan petikan pendek yang boleh diulang daripada bunyi yang sedang anda sunting. Mula-mula kenal pasti kawasan frekuensi yang ingin dikurangkan, kemudian tetapkan pusat di situ. Mulakan dengan lebar yang sempit dan tambahkannya sehingga pengurangan itu meliputi kawasan yang dimaksudkan. Bandingkan dengan mod pintasan selepas setiap perubahan, sambil mengekalkan gandaan main balik yang konsisten supaya perbandingan kekal berguna.
Gunakan panduan keputusan ini untuk memilih pelarasan seterusnya:
| Perkara yang ditunjukkan oleh perbandingan | Pelarasan seterusnya |
|---|---|
| Nada yang tidak diingini masih kedengaran, tetapi kandungan berdekatan berubah | Semak semula pusat sebelum melebarkan jalur |
| Pusat sudah betul, tetapi kawasan yang tidak diingini melangkaui julatnya | Tambah lebar secara beransur-ansur |
| Terlalu banyak timbre sekeliling hilang | Kurangkan lebar dan bandingkan semula |
| Beberapa nada yang berasingan perlu dikurangkan | Pertimbangkan bahagian takuk berasingan pada frekuensi tersebut |
| Kawasan penolakan yang luas dan rata diperlukan | Pertimbangkan semula bentuk dan tertib penapis |
Ini ialah cadangan pelarasan praktikal, bukannya ukuran daripada rakaman tertentu. Contoh tiga nada ini sengaja ringkas: contoh ini membolehkan anda memastikan pemproses berfungsi dengan betul sebelum membuat penilaian terhadap bunyi yang lebih kompleks.
Takuk tunggal juga tidak menyediakan kawalan kedalaman yang boleh dilaras secara berasingan. Cookbook memberikan keluarga pekali berasingan untuk penapis takuk dan EQ puncak. Jika tugasan anda memerlukan potongan sederhana yang boleh dilaras dan bukannya titik nol penolakan, EQ puncak ialah reka bentuk lain yang sesuai untuk diteliti. W3C: Audio EQ Cookbook
Apakah Perubahan Apabila Anda Menyambungkan Dua Tombol Langsung?
Kekalkan kontrak parameter yang sama: center_hz dan width_hz dimasukkan bersama ke dalam fungsi pekali. Kemas kini keseluruhan set pekali setiap kali salah satu parameter berubah, dan kira semula apabila kadar pensampelan pemprosesan berubah. Untuk prototaip ini, kedua-dua kawalan mesti lebih besar daripada sifar dan lebih kecil daripada separuh kadar pensampelan; tetapan yang tidak sah akan mencetuskan pengecualian.
Fungsi process() yang disediakan mengendalikan penimbal mono lengkap dan bermula dengan sejarah sifar pada setiap panggilan. Versi penstriman mesti mengekalkan sejarah kelewatannya antara blok. lfilter dalam SciPy mendokumenkan keadaan kelewatan awal dan keadaan akhir yang dipulangkan untuk tujuan ini. Kekalkan sejarah berasingan bagi setiap saluran audio. SciPy: lfilter
Perlakukan perubahan tombol sebagai langkah pelaksanaan tambahan. Contoh ini mengesahkan tetapan tetap; contoh ini tidak melaksanakan automasi berterusan. Satu strategi peralihan yang praktikal ialah menjalankan penapis tetap lama dan baharu secara selari untuk seketika, membolehkan penapis baharu membina sejarahnya, kemudian melakukan silang pudar pada outputnya. Nilai peralihan itu menggunakan bahan sebenar dan kelajuan kawalan yang dimaksudkan.
Bagi beberapa penapis yang disusun secara bersiri, kekalkan setiap penapis sebagai bahagian tertib kedua yang berasingan dan bukannya mengembangkan rangkaian itu menjadi satu polinomial tertib tinggi. SciPy mengesyorkan bahagian tertib kedua untuk kebanyakan tugas penapisan kerana bahagian ini mempunyai lebih sedikit masalah berangka. SciPy: lfilter
Keadaan Kegagalan Apakah yang Patut Anda Semak Dahulu?
Jika takuk muncul pada frekuensi yang salah, semak sama ada kadar pensampelan yang digunakan untuk pengiraan pekali sepadan dengan kadar pensampelan sebenar audio. Jika lebar bertambah apabila anda mengalihkan pusat, semak sama ada pembalut anda mengekalkan Q pada nilai tetap. Jika paparan tindak balas kelihatan cetek, nilai frekuensi pusat yang tepat sebelum mengubah reka bentuk.
Jika penimbal berulang menghasilkan ketakselanjaran, periksa pengendalian keadaan: demonstrasi ini menetapkan semula sejarah setiap kali process() dipanggil. Jika eksport WAV menghasilkan ralat julat, kurangkan amplitud input dan jalankan semula; program ini sengaja tidak memotong sampel di luar julat secara senyap. Anda perlu menamakan semula atau membuang fail demonstrasi sedia ada sebelum mengeksport pasangan baharu.
Binaan ini lengkap apabila ujian pusat meletakkan titik nol di lokasi yang diminta, jarak antara dua lintasan sempadan kekal seperti yang diminta dan bunyi yang diproses menyokong suntingan yang dimaksudkan. Kekalkan semakan tersebut bersama kedua-dua kawalan apabila anda beralih daripada prototaip luar talian ini kepada alat audio interaktif.
