.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "tutorials/filter_design_tutorial.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note Click :ref:`here ` to download the full example code .. rst-class:: sphx-glr-example-title .. _sphx_glr_tutorials_filter_design_tutorial.py: Filter design tutorial ====================== **Author**: `Moto Hira `__ This tutorial shows how to create basic digital filters (impulse responses) and their properties. We look into low-pass, high-pass and band-pass filters based on windowed-sinc kernels, and frequency sampling method. .. warning:: This tutorial requires prototype DSP features, which are available in nightly builds. Please refer to https://pytorch.org/get-started/locally for instructions for installing a nightly build. .. GENERATED FROM PYTHON SOURCE LINES 24-32 .. code-block:: default import torch import torchaudio print(torch.__version__) print(torchaudio.__version__) import matplotlib.pyplot as plt .. rst-class:: sphx-glr-script-out .. code-block:: none 2.4.0.dev20240418 2.2.0.dev20240419 .. GENERATED FROM PYTHON SOURCE LINES 34-36 .. code-block:: default from torchaudio.prototype.functional import frequency_impulse_response, sinc_impulse_response .. GENERATED FROM PYTHON SOURCE LINES 37-59 Windowed-Sinc Filter -------------------- `Sinc filter `_ is an idealized filter which removes frequencies above the cutoff frequency without affecting the lower frequencies. Sinc filter has infinite filter width in analytical solution. In numerical computation, sinc filter cannot be expressed exactly, so an approximation is required. Windowed-sinc finite impulse response is an approximation of sinc filter. It is obtained by first evaluating sinc function for given cutoff frequencies, then truncating the filter skirt, and applying a window, such as Hamming window, to reduce the artifacts introduced from the truncation. :py:func:`~torchaudio.prototype.functional.sinc_impulse_response` generates windowed-sinc impulse response for given cutoff frequencies. .. GENERATED FROM PYTHON SOURCE LINES 62-64 Low-pass filter ~~~~~~~~~~~~~~~ .. GENERATED FROM PYTHON SOURCE LINES 67-73 Impulse Response ^^^^^^^^^^^^^^^^ Creating sinc IR is as easy as passing cutoff frequency values to :py:func:`~torchaudio.prototype.functional.sinc_impulse_response`. .. GENERATED FROM PYTHON SOURCE LINES 74-82 .. code-block:: default cutoff = torch.linspace(0.0, 1.0, 9) irs = sinc_impulse_response(cutoff, window_size=513) print("Cutoff shape:", cutoff.shape) print("Impulse response shape:", irs.shape) .. rst-class:: sphx-glr-script-out .. code-block:: none Cutoff shape: torch.Size([9]) Impulse response shape: torch.Size([9, 513]) .. GENERATED FROM PYTHON SOURCE LINES 83-85 Let's visualize the resulting impulse responses. .. GENERATED FROM PYTHON SOURCE LINES 86-106 .. code-block:: default def plot_sinc_ir(irs, cutoff): num_filts, window_size = irs.shape half = window_size // 2 fig, axes = plt.subplots(num_filts, 1, sharex=True, figsize=(9.6, 8)) t = torch.linspace(-half, half - 1, window_size) for ax, ir, coff, color in zip(axes, irs, cutoff, plt.cm.tab10.colors): ax.plot(t, ir, linewidth=1.2, color=color, zorder=4, label=f"Cutoff: {coff}") ax.legend(loc=(1.05, 0.2), handletextpad=0, handlelength=0) ax.grid(True) fig.suptitle( "Impulse response of sinc low-pass filter for different cut-off frequencies\n" "(Frequencies are relative to Nyquist frequency)" ) axes[-1].set_xticks([i * half // 4 for i in range(-4, 5)]) fig.tight_layout() .. GENERATED FROM PYTHON SOURCE LINES 108-110 .. code-block:: default plot_sinc_ir(irs, cutoff) .. image-sg:: /tutorials/images/sphx_glr_filter_design_tutorial_001.png :alt: Impulse response of sinc low-pass filter for different cut-off frequencies (Frequencies are relative to Nyquist frequency) :srcset: /tutorials/images/sphx_glr_filter_design_tutorial_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 111-118 Frequency Response ^^^^^^^^^^^^^^^^^^ Next, let's look at the frequency responses. Simpy applying Fourier transform to the impulse responses will give the frequency responses. .. GENERATED FROM PYTHON SOURCE LINES 119-123 .. code-block:: default frs = torch.fft.rfft(irs, n=2048, dim=1).abs() .. GENERATED FROM PYTHON SOURCE LINES 124-126 Let's visualize the resulting frequency responses. .. GENERATED FROM PYTHON SOURCE LINES 127-152 .. code-block:: default def plot_sinc_fr(frs, cutoff, band=False): num_filts, num_fft = frs.shape num_ticks = num_filts + 1 if band else num_filts fig, axes = plt.subplots(num_filts, 1, sharex=True, sharey=True, figsize=(9.6, 8)) for ax, fr, coff, color in zip(axes, frs, cutoff, plt.cm.tab10.colors): ax.grid(True) ax.semilogy(fr, color=color, zorder=4, label=f"Cutoff: {coff}") ax.legend(loc=(1.05, 0.2), handletextpad=0, handlelength=0).set_zorder(3) axes[-1].set( ylim=[None, 100], yticks=[1e-9, 1e-6, 1e-3, 1], xticks=torch.linspace(0, num_fft, num_ticks), xticklabels=[f"{i/(num_ticks - 1)}" for i in range(num_ticks)], xlabel="Frequency", ) fig.suptitle( "Frequency response of sinc low-pass filter for different cut-off frequencies\n" "(Frequencies are relative to Nyquist frequency)" ) fig.tight_layout() .. GENERATED FROM PYTHON SOURCE LINES 154-156 .. code-block:: default plot_sinc_fr(frs, cutoff) .. image-sg:: /tutorials/images/sphx_glr_filter_design_tutorial_002.png :alt: Frequency response of sinc low-pass filter for different cut-off frequencies (Frequencies are relative to Nyquist frequency) :srcset: /tutorials/images/sphx_glr_filter_design_tutorial_002.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 157-167 High-pass filter ~~~~~~~~~~~~~~~~ High-pass filter can be obtained by subtracting low-pass impulse response from the Dirac delta function. Passing ``high_pass=True`` to :py:func:`~torchaudio.prototype.functional.sinc_impulse_response` will change the returned filter kernel to high pass filter. .. GENERATED FROM PYTHON SOURCE LINES 168-172 .. code-block:: default irs = sinc_impulse_response(cutoff, window_size=513, high_pass=True) frs = torch.fft.rfft(irs, n=2048, dim=1).abs() .. GENERATED FROM PYTHON SOURCE LINES 173-176 Impulse Response ^^^^^^^^^^^^^^^^ .. GENERATED FROM PYTHON SOURCE LINES 177-180 .. code-block:: default plot_sinc_ir(irs, cutoff) .. image-sg:: /tutorials/images/sphx_glr_filter_design_tutorial_003.png :alt: Impulse response of sinc low-pass filter for different cut-off frequencies (Frequencies are relative to Nyquist frequency) :srcset: /tutorials/images/sphx_glr_filter_design_tutorial_003.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 181-184 Frequency Response ^^^^^^^^^^^^^^^^^^ .. GENERATED FROM PYTHON SOURCE LINES 185-188 .. code-block:: default plot_sinc_fr(frs, cutoff) .. image-sg:: /tutorials/images/sphx_glr_filter_design_tutorial_004.png :alt: Frequency response of sinc low-pass filter for different cut-off frequencies (Frequencies are relative to Nyquist frequency) :srcset: /tutorials/images/sphx_glr_filter_design_tutorial_004.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 189-194 Band-pass filter ~~~~~~~~~~~~~~~~ Band-pass filter can be obtained by subtracting low-pass filter for upper band from that of lower band. .. GENERATED FROM PYTHON SOURCE LINES 195-203 .. code-block:: default cutoff = torch.linspace(0.0, 1, 11) c_low = cutoff[:-1] c_high = cutoff[1:] irs = sinc_impulse_response(c_low, window_size=513) - sinc_impulse_response(c_high, window_size=513) frs = torch.fft.rfft(irs, n=2048, dim=1).abs() .. GENERATED FROM PYTHON SOURCE LINES 204-207 Impulse Response ^^^^^^^^^^^^^^^^ .. GENERATED FROM PYTHON SOURCE LINES 208-212 .. code-block:: default coff = [f"{l.item():.1f}, {h.item():.1f}" for l, h in zip(c_low, c_high)] plot_sinc_ir(irs, coff) .. image-sg:: /tutorials/images/sphx_glr_filter_design_tutorial_005.png :alt: Impulse response of sinc low-pass filter for different cut-off frequencies (Frequencies are relative to Nyquist frequency) :srcset: /tutorials/images/sphx_glr_filter_design_tutorial_005.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 213-216 Frequency Response ^^^^^^^^^^^^^^^^^^ .. GENERATED FROM PYTHON SOURCE LINES 217-221 .. code-block:: default plot_sinc_fr(frs, coff, band=True) .. image-sg:: /tutorials/images/sphx_glr_filter_design_tutorial_006.png :alt: Frequency response of sinc low-pass filter for different cut-off frequencies (Frequencies are relative to Nyquist frequency) :srcset: /tutorials/images/sphx_glr_filter_design_tutorial_006.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 222-238 Frequency Sampling ------------------ The next method we look into starts from a desired frequency response and obtain impulse response by applying inverse Fourier transform. :py:func:`~torchaudio.prototype.functional.frequency_impulse_response` takes (unnormalized) magnitude distribution of frequencies and construct impulse response from it. Note however that the resulting impulse response does not produce the desired frequency response. In the following, we create multiple filters and compare the input frequency response and the actual frequency response. .. GENERATED FROM PYTHON SOURCE LINES 241-245 Brick-wall filter ~~~~~~~~~~~~~~~~~ Let's start from brick-wall filter .. GENERATED FROM PYTHON SOURCE LINES 246-254 .. code-block:: default magnitudes = torch.concat([torch.ones((128,)), torch.zeros((128,))]) ir = frequency_impulse_response(magnitudes) print("Magnitudes:", magnitudes.shape) print("Impulse Response:", ir.shape) .. rst-class:: sphx-glr-script-out .. code-block:: none Magnitudes: torch.Size([256]) Impulse Response: torch.Size([510]) .. GENERATED FROM PYTHON SOURCE LINES 256-282 .. code-block:: default def plot_ir(magnitudes, ir, num_fft=2048): fr = torch.fft.rfft(ir, n=num_fft, dim=0).abs() ir_size = ir.size(-1) half = ir_size // 2 fig, axes = plt.subplots(3, 1) t = torch.linspace(-half, half - 1, ir_size) axes[0].plot(t, ir) axes[0].grid(True) axes[0].set(title="Impulse Response") axes[0].set_xticks([i * half // 4 for i in range(-4, 5)]) t = torch.linspace(0, 1, fr.numel()) axes[1].plot(t, fr, label="Actual") axes[2].semilogy(t, fr, label="Actual") t = torch.linspace(0, 1, magnitudes.numel()) for i in range(1, 3): axes[i].plot(t, magnitudes, label="Desired (input)", linewidth=1.1, linestyle="--") axes[i].grid(True) axes[1].set(title="Frequency Response") axes[2].set(title="Frequency Response (log-scale)", xlabel="Frequency") axes[2].legend(loc="center right") fig.tight_layout() .. GENERATED FROM PYTHON SOURCE LINES 284-287 .. code-block:: default plot_ir(magnitudes, ir) .. image-sg:: /tutorials/images/sphx_glr_filter_design_tutorial_007.png :alt: Impulse Response, Frequency Response, Frequency Response (log-scale) :srcset: /tutorials/images/sphx_glr_filter_design_tutorial_007.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 288-291 Notice that there are artifacts around the transition band. This is more noticeable when the window size is small. .. GENERATED FROM PYTHON SOURCE LINES 292-296 .. code-block:: default magnitudes = torch.concat([torch.ones((32,)), torch.zeros((32,))]) ir = frequency_impulse_response(magnitudes) .. GENERATED FROM PYTHON SOURCE LINES 298-301 .. code-block:: default plot_ir(magnitudes, ir) .. image-sg:: /tutorials/images/sphx_glr_filter_design_tutorial_008.png :alt: Impulse Response, Frequency Response, Frequency Response (log-scale) :srcset: /tutorials/images/sphx_glr_filter_design_tutorial_008.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 302-306 Arbitrary shapes ~~~~~~~~~~~~~~~~ .. GENERATED FROM PYTHON SOURCE LINES 307-312 .. code-block:: default magnitudes = torch.linspace(0, 1, 64) ** 4.0 ir = frequency_impulse_response(magnitudes) .. GENERATED FROM PYTHON SOURCE LINES 314-317 .. code-block:: default plot_ir(magnitudes, ir) .. image-sg:: /tutorials/images/sphx_glr_filter_design_tutorial_009.png :alt: Impulse Response, Frequency Response, Frequency Response (log-scale) :srcset: /tutorials/images/sphx_glr_filter_design_tutorial_009.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 319-323 .. code-block:: default magnitudes = torch.sin(torch.linspace(0, 10, 64)) ** 4.0 ir = frequency_impulse_response(magnitudes) .. GENERATED FROM PYTHON SOURCE LINES 325-329 .. code-block:: default plot_ir(magnitudes, ir) .. image-sg:: /tutorials/images/sphx_glr_filter_design_tutorial_010.png :alt: Impulse Response, Frequency Response, Frequency Response (log-scale) :srcset: /tutorials/images/sphx_glr_filter_design_tutorial_010.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 330-338 References ---------- - https://en.wikipedia.org/wiki/Sinc_filter - https://www.analog.com/media/en/technical-documentation/dsp-book/dsp_book_Ch16.pdf - https://courses.engr.illinois.edu/ece401/fa2020/slides/lec10.pdf - https://ccrma.stanford.edu/~jos/sasp/Windowing_Desired_Impulse_Response.html .. rst-class:: sphx-glr-timing **Total running time of the script:** ( 0 minutes 5.259 seconds) .. _sphx_glr_download_tutorials_filter_design_tutorial.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: filter_design_tutorial.py ` .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: filter_design_tutorial.ipynb ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_