FIR ํ•„ํ„ฐ ์„ค๊ณ„(FIR Filter Design)

FIR ํ•„ํ„ฐ ์„ค๊ณ„(FIR Filter Design)

ํ•™์Šต ๋ชฉํ‘œ

  • ๋””์ง€ํ„ธ ํ•„ํ„ฐ ์„ค๊ณ„์˜ ์‚ฌ์–‘๊ณผ ์šฉ์–ด๋ฅผ ์ดํ•ดํ•œ๋‹ค
  • FIR ํ•„ํ„ฐ ์„ค๊ณ„๋ฅผ ์œ„ํ•œ ์ฐฝํ•จ์ˆ˜๋ฒ•(Window Method)์„ ๋งˆ์Šคํ„ฐํ•˜๊ณ  ์ฐฝํ•จ์ˆ˜๋“ค์„ ๋น„๊ตํ•œ๋‹ค
  • ์ฃผํŒŒ์ˆ˜ ํ‘œ๋ณธํ™”(Frequency Sampling) ๋ฐ ์ตœ์  ๋“ฑ๋ฆฌํ”Œ(Parks-McClellan) ์„ค๊ณ„ ๋ฐฉ๋ฒ•์„ ํ•™์Šตํ•œ๋‹ค
  • ์„ ํ˜• ์œ„์ƒ(Linear Phase) FIR ํ•„ํ„ฐ ์œ ํ˜•๊ณผ ๊ทธ ์ œ์•ฝ ์กฐ๊ฑด์„ ์ดํ•ดํ•œ๋‹ค
  • Python์˜ scipy.signal ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•˜์—ฌ FIR ํ•„ํ„ฐ๋ฅผ ์„ค๊ณ„ํ•œ๋‹ค
  • ์ง์ ‘ ํ•ฉ์„ฑ๊ณฑ(Direct Convolution), ์ค‘์ฒฉ ๊ฐ€์‚ฐ๋ฒ•(Overlap-Add), ์ค‘์ฒฉ ์ €์žฅ๋ฒ•(Overlap-Save)์„ ์ด์šฉํ•œ FIR ํ•„ํ„ฐ๋ง์„ ๊ตฌํ˜„ํ•œ๋‹ค
  • ์ „์ด ๋Œ€์—ญํญ(Transition Width), ์ €์ง€๋Œ€์—ญ ๊ฐ์‡ (Stopband Attenuation), ์—ฐ์‚ฐ ๋น„์šฉ ์ธก๋ฉด์—์„œ ์„ค๊ณ„ ๋ฐฉ๋ฒ•๋“ค์„ ๋น„๊ตํ•œ๋‹ค

๋ชฉ์ฐจ

  1. FIR ํ•„ํ„ฐ ์„ค๊ณ„ ์‚ฌ์–‘
  2. ์ด์ƒ์ ์ธ ํ•„ํ„ฐ์™€ ์ž„ํŽ„์Šค ์‘๋‹ต
  3. ์ฐฝํ•จ์ˆ˜๋ฒ•
  4. ์ฐฝํ•จ์ˆ˜
  5. ์นด์ด์ € ์ฐฝํ•จ์ˆ˜ ์„ค๊ณ„
  6. ์ฃผํŒŒ์ˆ˜ ํ‘œ๋ณธํ™”๋ฒ•
  7. ์ตœ์  ๋“ฑ๋ฆฌํ”Œ ์„ค๊ณ„: Parks-McClellan ์•Œ๊ณ ๋ฆฌ์ฆ˜
  8. ์„ ํ˜• ์œ„์ƒ FIR ํ•„ํ„ฐ
  9. ์„ค๊ณ„ ๋ฐฉ๋ฒ• ๋น„๊ต
  10. FIR ํ•„ํ„ฐ ๊ตฌํ˜„
  11. Python ๊ตฌํ˜„
  12. ์—ฐ์Šต ๋ฌธ์ œ

1. FIR ํ•„ํ„ฐ ์„ค๊ณ„ ์‚ฌ์–‘

1.1 ํ•„ํ„ฐ ์šฉ์–ด

๋””์ง€ํ„ธ ํ•„ํ„ฐ๋Š” ์ฃผํŒŒ์ˆ˜ ์‘๋‹ต(Frequency Response) $H(e^{j\omega})$๋กœ ํŠน์„ฑํ™”๋˜๋ฉฐ, ์ด๋Š” ์‹ ํ˜ธ์˜ ๊ฐ ์ฃผํŒŒ์ˆ˜ ์„ฑ๋ถ„์ด ์–ด๋–ป๊ฒŒ ๋ณ€ํ˜•๋˜๋Š”์ง€๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค. ์ฃผ์š” ์‚ฌ์–‘์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค:

์ฃผํŒŒ์ˆ˜ ๋Œ€์—ญ: - ํ†ต๊ณผ๋Œ€์—ญ(Passband): ์ตœ์†Œํ•œ์˜ ์™œ๊ณก์œผ๋กœ ํ†ต๊ณผ์‹œ์ผœ์•ผ ํ•  ์ฃผํŒŒ์ˆ˜, $0 \leq \omega \leq \omega_p$ - ์ €์ง€๋Œ€์—ญ(Stopband): ๊ฐ์‡ ์‹œ์ผœ์•ผ ํ•  ์ฃผํŒŒ์ˆ˜, $\omega_s \leq \omega \leq \pi$ - ์ „์ด ๋Œ€์—ญ(Transition Band): ํ†ต๊ณผ๋Œ€์—ญ๊ณผ ์ €์ง€๋Œ€์—ญ ์‚ฌ์ด์˜ ์˜์—ญ, $\omega_p < \omega < \omega_s$

ํ—ˆ์šฉ ์˜ค์ฐจ(Tolerances): - ํ†ต๊ณผ๋Œ€์—ญ ๋ฆฌํ”Œ(Passband Ripple) $\delta_1$: ํ†ต๊ณผ๋Œ€์—ญ์—์„œ ๋‹จ์œ„๋กœ๋ถ€ํ„ฐ์˜ ์ตœ๋Œ€ ํŽธ์ฐจ - ์ €์ง€๋Œ€์—ญ ๊ฐ์‡ (Stopband Attenuation) $\delta_2$: ์ €์ง€๋Œ€์—ญ์—์„œ์˜ ์ตœ๋Œ€ ํฌ๊ธฐ

์ง„ํญ ์‘๋‹ต ์‚ฌ์–‘(Magnitude Response Specification)
|H(e^jฯ‰)|
    โ†‘
1+ฮดโ‚ โ”€ โ”€ โ”€ โ”€โ”
    โ”‚        โ”‚
  1 โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
    โ”‚        โ”‚
1-ฮดโ‚ โ”€ โ”€ โ”€ โ”€โ”˜        โ•ฒ
    โ”‚                    โ•ฒ
  ฮดโ‚‚ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    โ”‚                      โ”‚
  0 โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ†’ ฯ‰
    0     ฯ‰p    ฯ‰s        ฯ€

         ํ†ต๊ณผ๋Œ€์—ญโ”‚์ „์ด โ”‚ ์ €์ง€๋Œ€์—ญ
                โ”‚๋Œ€์—ญ โ”‚

1.2 ๋ฐ์‹œ๋ฒจ ์‚ฌ์–‘

์‹ค์ œ๋กœ ํ•„ํ„ฐ ์‚ฌ์–‘์€ ์ข…์ข… ๋ฐ์‹œ๋ฒจ(dB)๋กœ ํ‘œํ˜„๋œ๋‹ค:

$$A_p = -20\log_{10}(1 - \delta_1) \quad \text{(ํ†ต๊ณผ๋Œ€์—ญ ๋ฆฌํ”Œ, dB)}$$

$$A_s = -20\log_{10}(\delta_2) \quad \text{(์ €์ง€๋Œ€์—ญ ๊ฐ์‡ , dB)}$$

์ผ๋ฐ˜์ ์ธ ์‚ฌ์–‘ ์˜ˆ์‹œ: - ํ†ต๊ณผ๋Œ€์—ญ ๋ฆฌํ”Œ: $A_p \leq 0.1$ dB ($\delta_1 \approx 0.0115$ ์˜๋ฏธ) - ์ €์ง€๋Œ€์—ญ ๊ฐ์‡ : $A_s \geq 60$ dB ($\delta_2 = 0.001$ ์˜๋ฏธ)

1.3 FIR vs IIR ํŠธ๋ ˆ์ด๋“œ์˜คํ”„

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              FIR vs IIR ๋น„๊ต                            โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ํŠน์„ฑ             โ”‚ FIR             โ”‚ IIR               โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ์•ˆ์ •์„ฑ           โ”‚ ํ•ญ์ƒ ์•ˆ์ •       โ”‚ ๋ถˆ์•ˆ์ • ๊ฐ€๋Šฅ       โ”‚
โ”‚ ์„ ํ˜• ์œ„์ƒ        โ”‚ ์‰ฝ๊ฒŒ ๋‹ฌ์„ฑ       โ”‚ ๋ถˆ๊ฐ€๋Šฅ            โ”‚
โ”‚ ํ•„ํ„ฐ ์ฐจ์ˆ˜        โ”‚ ๋†’์Œ            โ”‚ ๋‚ฎ์Œ              โ”‚
โ”‚ ์—ฐ์‚ฐ๋Ÿ‰           โ”‚ ๊ณฑ์…ˆ ๋งŽ์Œ       โ”‚ ๊ณฑ์…ˆ ์ ์Œ         โ”‚
โ”‚ ์„ค๊ณ„ ๋ฐฉ๋ฒ•        โ”‚ Window, PM, FS  โ”‚ ์•„๋‚ ๋กœ๊ทธ ์›ํ˜•     โ”‚
โ”‚ ๊ตฐ์ง€์—ฐ           โ”‚ ์ผ์ •            โ”‚ ๋น„์ผ์ •            โ”‚
โ”‚ ๋ฐ˜์˜ฌ๋ฆผ ์˜ค์ฐจ      โ”‚ ๋œ ๋ฏผ๊ฐ         โ”‚ ๋” ๋ฏผ๊ฐ           โ”‚
โ”‚ ๊ตฌํ˜„             โ”‚ ํ”ผ๋“œ๋ฐฑ ์—†์Œ     โ”‚ ํ”ผ๋“œ๋ฐฑ ํ•„์š”       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

์ฐจ์ˆ˜ $M$์˜ FIR ํ•„ํ„ฐ๋Š” ๋‹ค์Œ์˜ ์ „๋‹ฌ ํ•จ์ˆ˜(Transfer Function)๋ฅผ ๊ฐ–๋Š”๋‹ค:

$$H(z) = \sum_{n=0}^{M} h[n] z^{-n}$$

์ถœ๋ ฅ์€ ์œ ํ•œ ํ•ฉ์„ฑ๊ณฑ(Finite Convolution)์œผ๋กœ ํ‘œํ˜„๋œ๋‹ค:

$$y[n] = \sum_{k=0}^{M} h[k] x[n-k]$$


2. ์ด์ƒ์ ์ธ ํ•„ํ„ฐ์™€ ์ž„ํŽ„์Šค ์‘๋‹ต

2.1 ์ด์ƒ์ ์ธ ์ €์—ญํ†ต๊ณผ ํ•„ํ„ฐ

์ด์ƒ์ ์ธ ์ €์—ญํ†ต๊ณผ ํ•„ํ„ฐ(Ideal Lowpass Filter)์˜ ์ฃผํŒŒ์ˆ˜ ์‘๋‹ต์€:

$$H_d(e^{j\omega}) = \begin{cases} 1, & |\omega| \leq \omega_c \\ 0, & \omega_c < |\omega| \leq \pi \end{cases}$$

์ž„ํŽ„์Šค ์‘๋‹ต์€ ์—ญ DTFT๋ฅผ ํ†ตํ•ด ๊ตฌํ•œ๋‹ค:

$$h_d[n] = \frac{1}{2\pi} \int_{-\pi}^{\pi} H_d(e^{j\omega}) e^{j\omega n} d\omega = \frac{\sin(\omega_c n)}{\pi n}$$

์ด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํŠน์„ฑ์„ ๊ฐ€์ง„ sinc ํ•จ์ˆ˜์ด๋‹ค: - ์ง€์† ์‹œ๊ฐ„์ด ๋ฌดํ•œํ•˜๋‹ค (๋น„์ธ๊ณผ์ , ์‹คํ˜„ ๋ถˆ๊ฐ€) - $n = 0$์— ๋Œ€ํ•ด ๋Œ€์นญ์ด๋‹ค

2.2 ๋‹ค๋ฅธ ์ด์ƒ์ ์ธ ํ•„ํ„ฐ ์œ ํ˜•

์ด์ƒ์ ์ธ ๊ณ ์—ญํ†ต๊ณผ ํ•„ํ„ฐ(Ideal Highpass Filter):

$$h_d[n] = \delta[n] - \frac{\sin(\omega_c n)}{\pi n}$$

์ด์ƒ์ ์ธ ๋Œ€์—ญํ†ต๊ณผ ํ•„ํ„ฐ(Ideal Bandpass Filter) ($\omega_{c1} < |\omega| < \omega_{c2}$):

$$h_d[n] = \frac{\sin(\omega_{c2} n)}{\pi n} - \frac{\sin(\omega_{c1} n)}{\pi n}$$

์ด์ƒ์ ์ธ ๋Œ€์—ญ์ €์ง€ ํ•„ํ„ฐ(Ideal Bandstop Filter):

$$h_d[n] = \delta[n] - \frac{\sin(\omega_{c2} n)}{\pi n} + \frac{\sin(\omega_{c1} n)}{\pi n}$$

import numpy as np
import matplotlib.pyplot as plt
from scipy import signal

def ideal_lowpass(omega_c, M):
    """Compute ideal lowpass filter impulse response (truncated)."""
    n = np.arange(-M, M + 1)
    h = np.zeros_like(n, dtype=float)

    # Handle n = 0 case (sinc(0) = omega_c / pi)
    center = M
    h[center] = omega_c / np.pi

    # Non-zero indices
    nonzero = np.concatenate([np.arange(0, center), np.arange(center + 1, 2 * M + 1)])
    h[nonzero] = np.sin(omega_c * n[nonzero]) / (np.pi * n[nonzero])

    return n, h

# Plot ideal lowpass impulse responses for different cutoff frequencies
fig, axes = plt.subplots(1, 3, figsize=(15, 4))

for ax, wc, title in zip(axes, [np.pi/4, np.pi/2, 3*np.pi/4],
                           ['ฯ‰c = ฯ€/4', 'ฯ‰c = ฯ€/2', 'ฯ‰c = 3ฯ€/4']):
    n, h = ideal_lowpass(wc, 30)
    ax.stem(n, h, linefmt='b-', markerfmt='bo', basefmt='k-')
    ax.set_xlabel('n')
    ax.set_ylabel('h[n]')
    ax.set_title(f'Ideal LP Impulse Response ({title})')
    ax.set_xlim(-32, 32)
    ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('ideal_lowpass_responses.png', dpi=150)
plt.close()

3. ์ฐฝํ•จ์ˆ˜๋ฒ•

3.1 ๊ธฐ๋ณธ ์›๋ฆฌ

์ฐฝํ•จ์ˆ˜๋ฒ•(Window Method)์€ FIR ์„ค๊ณ„์—์„œ ๊ฐ€์žฅ ๋‹จ์ˆœํ•œ ๋ฐฉ๋ฒ•์ด๋‹ค. ๊ธฐ๋ณธ ์•„์ด๋””์–ด๋Š”:

  1. ์ด์ƒ์ ์ธ (๋ฌดํ•œ) ์ž„ํŽ„์Šค ์‘๋‹ต $h_d[n]$์—์„œ ์‹œ์ž‘ํ•œ๋‹ค
  2. ๊ธธ์ด $N = M + 1$์ธ ์œ ํ•œ ์ง€์† ์‹œ๊ฐ„ ์ฐฝํ•จ์ˆ˜ $w[n]$์„ ๊ณฑํ•œ๋‹ค
  3. ํ•„ํ„ฐ๋ฅผ ์ธ๊ณผ์ (Causal)์œผ๋กœ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ๊ฒฐ๊ณผ๋ฅผ ์ด๋™์‹œํ‚จ๋‹ค

$$h[n] = h_d[n - M/2] \cdot w[n], \quad n = 0, 1, \ldots, M$$

์ฃผํŒŒ์ˆ˜ ์˜์—ญ์—์„œ ์ด ๊ณฑ์…ˆ์€ ํ•ฉ์„ฑ๊ณฑ(Convolution)์— ํ•ด๋‹นํ•œ๋‹ค:

$$H(e^{j\omega}) = \frac{1}{2\pi} H_d(e^{j\omega}) * W(e^{j\omega})$$

3.2 ์ฐฝํ•จ์ˆ˜ ์ ์šฉ์˜ ํšจ๊ณผ

์ฐฝํ•จ์ˆ˜์˜ ์ฃผํŒŒ์ˆ˜ ์‘๋‹ต $W(e^{j\omega})$์€ ๋‹ค์Œ์„ ๊ฒฐ์ •ํ•œ๋‹ค:

  1. ์ฃผ์—ฝ ํญ(Mainlobe Width): ์ „์ด ๋Œ€์—ญํญ์„ ์ œ์–ดํ•œ๋‹ค -- ์ฃผ์—ฝ์ด ๋„“์„์ˆ˜๋ก ์ „์ด ๋Œ€์—ญ์ด ๋„“์–ด์ง„๋‹ค
  2. ๋ถ€์—ฝ ๋ ˆ๋ฒจ(Sidelobe Level): ์ €์ง€๋Œ€์—ญ ๊ฐ์‡ ๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค -- ๋ถ€์—ฝ์ด ๋†’์„์ˆ˜๋ก ์ €์ง€๋Œ€์—ญ ๊ฐ์‡ ๊ฐ€ ์ž‘์•„์ง„๋‹ค
  3. ๋ถ€์—ฝ ๊ฐ์†Œ์œจ(Sidelobe Decay Rate): ์ฃผํŒŒ์ˆ˜์— ๋”ฐ๋ผ ๋ถ€์—ฝ์ด ์–ผ๋งˆ๋‚˜ ๋นจ๋ฆฌ ๊ฐ์†Œํ•˜๋Š”์ง€
์ฐฝํ•จ์ˆ˜ ์ŠคํŽ™ํŠธ๋Ÿผ ํŠน์„ฑ(Window Spectrum Properties)
|W(e^jฯ‰)|
    โ†‘
    โ”‚   โ•ฑโ•ฒ        ์ฃผ์—ฝ(Mainlobe)
    โ”‚  โ•ฑ  โ•ฒ       ํญ = ฮ”ฯ‰_ML
    โ”‚ โ•ฑ    โ•ฒ
    โ”‚โ•ฑ      โ•ฒ  โ•ฑโ•ฒ
    โ”‚        โ•ฒโ•ฑ  โ•ฒ  โ•ฑโ•ฒ       ์ตœ๋Œ€ ๋ถ€์—ฝ
    โ”‚         โ”‚   โ•ฒโ•ฑ  โ•ฒ      ๋ ˆ๋ฒจ (dB)
    โ”‚         โ”‚    โ”‚   โ•ฒโ•ฑโ•ฒ
    โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ†’ ฯ‰
    0      ฮ”ฯ‰_ML/2          ฯ€

3.3 ๊น์Šค ํ˜„์ƒ(Gibbs Phenomenon)

์ง์‚ฌ๊ฐํ˜• ์ฐฝ(Rectangular Window)์œผ๋กœ ์ด์ƒ์ ์ธ ์ž„ํŽ„์Šค ์‘๋‹ต์„ ์ ˆ๋‹จํ•˜๋ฉด ๊น์Šค ํ˜„์ƒ(Gibbs Phenomenon)์ด ๋ฐœ์ƒํ•˜์—ฌ ํ•„ํ„ฐ ์ฐจ์ˆ˜ $M$์— ๊ด€๊ณ„์—†์ด ๋Œ€์—ญ ๊ฒฝ๊ณ„์—์„œ ์•ฝ 9%์˜ ์˜ค๋ฒ„์ŠˆํŠธ(์•ฝ $-21$ dB ์ €์ง€๋Œ€์—ญ ๊ฐ์‡ )๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ํ•„ํ„ฐ ์ฐจ์ˆ˜ $M$์„ ๋Š˜๋ฆฌ๋ฉด ์ „์ด ํญ์€ ์ข์•„์ง€์ง€๋งŒ ๋ฆฌํ”Œ ์ง„ํญ์€ ์ค„์–ด๋“ค์ง€ ์•Š๋Š”๋‹ค.

def windowed_lowpass(omega_c, M, window_type='rectangular'):
    """Design lowpass FIR filter using window method."""
    n = np.arange(0, M + 1)
    alpha = M / 2  # Delay for linear phase

    # Ideal lowpass impulse response (shifted for causality)
    h_d = np.zeros(M + 1)
    for i in range(M + 1):
        if n[i] == alpha:
            h_d[i] = omega_c / np.pi
        else:
            h_d[i] = np.sin(omega_c * (n[i] - alpha)) / (np.pi * (n[i] - alpha))

    # Apply window
    if window_type == 'rectangular':
        w = np.ones(M + 1)
    elif window_type == 'hamming':
        w = signal.windows.hamming(M + 1)
    elif window_type == 'hanning':
        w = signal.windows.hann(M + 1)
    elif window_type == 'blackman':
        w = signal.windows.blackman(M + 1)
    else:
        w = np.ones(M + 1)

    h = h_d * w
    return h

# Demonstrate Gibbs phenomenon with rectangular window
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
omega_c = np.pi / 2

for idx, M in enumerate([10, 20, 50, 100]):
    ax = axes[idx // 2, idx % 2]
    h = windowed_lowpass(omega_c, M, 'rectangular')

    # Frequency response
    w_freq, H = signal.freqz(h, worN=2048)
    H_dB = 20 * np.log10(np.abs(H) + 1e-15)

    ax.plot(w_freq / np.pi, H_dB, 'b-', linewidth=1.5)
    ax.axhline(-21, color='r', linestyle='--', alpha=0.5, label='-21 dB (Gibbs)')
    ax.axvline(0.5, color='g', linestyle='--', alpha=0.5, label='ฯ‰c = ฯ€/2')
    ax.set_xlabel('Normalized Frequency (ร—ฯ€ rad/sample)')
    ax.set_ylabel('Magnitude (dB)')
    ax.set_title(f'Rectangular Window, M = {M}')
    ax.set_ylim(-80, 5)
    ax.legend(fontsize=8)
    ax.grid(True, alpha=0.3)

plt.suptitle('Gibbs Phenomenon: Stopband ripple remains ~-21 dB', fontsize=12)
plt.tight_layout()
plt.savefig('gibbs_phenomenon.png', dpi=150)
plt.close()

4. ์ฐฝํ•จ์ˆ˜

4.1 ์ผ๋ฐ˜์ ์ธ ์ฐฝํ•จ์ˆ˜

๊ฐ ์ฐฝํ•จ์ˆ˜๋Š” ์ฃผ์—ฝ ํญ๊ณผ ๋ถ€์—ฝ ๋ ˆ๋ฒจ ์‚ฌ์ด์˜ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„๋ฅผ ์กฐ์ ˆํ•œ๋‹ค. ๋” ๋งค๋„๋Ÿฌ์šด ์ฐฝํ•จ์ˆ˜์ผ์ˆ˜๋ก ์ฃผ์—ฝ์ด ๋„“์ง€๋งŒ ๋ถ€์—ฝ์€ ๋‚ฎ๋‹ค.

์ง์‚ฌ๊ฐํ˜• ์ฐฝ(Rectangular Window)

$$w[n] = 1, \quad 0 \leq n \leq M$$

  • ์ฃผ์—ฝ ํญ: $\Delta\omega_\text{ML} = 4\pi / (M+1)$
  • ์ตœ๋Œ€ ๋ถ€์—ฝ: $-13$ dB
  • ์ตœ์†Œ ์ €์ง€๋Œ€์—ญ ๊ฐ์‡ : $-21$ dB
  • ์ „์ด ํญ: $\Delta\omega \approx 0.92 \cdot 2\pi / (M+1)$

ํ•œ(Hann) ์ฐฝ (ํ•ด๋‹(Hanning) ์ฐฝ)

$$w[n] = 0.5 - 0.5\cos\left(\frac{2\pi n}{M}\right), \quad 0 \leq n \leq M$$

  • ์ฃผ์—ฝ ํญ: $\Delta\omega_\text{ML} = 8\pi / (M+1)$
  • ์ตœ๋Œ€ ๋ถ€์—ฝ: $-31$ dB
  • ์ตœ์†Œ ์ €์ง€๋Œ€์—ญ ๊ฐ์‡ : $-44$ dB
  • ์ „์ด ํญ: $\Delta\omega \approx 3.11 \cdot 2\pi / (M+1)$

ํ•ด๋ฐ ์ฐฝ(Hamming Window)

$$w[n] = 0.54 - 0.46\cos\left(\frac{2\pi n}{M}\right), \quad 0 \leq n \leq M$$

  • ์ฃผ์—ฝ ํญ: $\Delta\omega_\text{ML} = 8\pi / (M+1)$
  • ์ตœ๋Œ€ ๋ถ€์—ฝ: $-41$ dB
  • ์ตœ์†Œ ์ €์ง€๋Œ€์—ญ ๊ฐ์‡ : $-53$ dB
  • ์ „์ด ํญ: $\Delta\omega \approx 3.32 \cdot 2\pi / (M+1)$

๋ธ”๋ž™๋งŒ ์ฐฝ(Blackman Window)

$$w[n] = 0.42 - 0.5\cos\left(\frac{2\pi n}{M}\right) + 0.08\cos\left(\frac{4\pi n}{M}\right), \quad 0 \leq n \leq M$$

  • ์ฃผ์—ฝ ํญ: $\Delta\omega_\text{ML} = 12\pi / (M+1)$
  • ์ตœ๋Œ€ ๋ถ€์—ฝ: $-57$ dB
  • ์ตœ์†Œ ์ €์ง€๋Œ€์—ญ ๊ฐ์‡ : $-74$ dB
  • ์ „์ด ํญ: $\Delta\omega \approx 5.56 \cdot 2\pi / (M+1)$

4.2 ์ฐฝํ•จ์ˆ˜ ๋น„๊ตํ‘œ

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ์ฐฝํ•จ์ˆ˜      โ”‚ ์ตœ๋Œ€ ๋ถ€์—ฝ    โ”‚ ์ตœ์†Œ ์ €์ง€๋Œ€์—ญ โ”‚ ๊ทผ์‚ฌ ์ „์ดํญ       โ”‚
โ”‚             โ”‚ ๋ ˆ๋ฒจ (dB)   โ”‚ ๊ฐ์‡  (dB)     โ”‚ (rad)             โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Rectangular โ”‚ -13          โ”‚ -21           โ”‚ 0.92ยท(2ฯ€/M)       โ”‚
โ”‚ Hann        โ”‚ -31          โ”‚ -44           โ”‚ 3.11ยท(2ฯ€/M)       โ”‚
โ”‚ Hamming     โ”‚ -41          โ”‚ -53           โ”‚ 3.32ยท(2ฯ€/M)       โ”‚
โ”‚ Blackman    โ”‚ -57          โ”‚ -74           โ”‚ 5.56ยท(2ฯ€/M)       โ”‚
โ”‚ Kaiser(ฮฒ=6) โ”‚ -44          โ”‚ -60           โ”‚ adjustable        โ”‚
โ”‚ Kaiser(ฮฒ=9) โ”‚ -69          โ”‚ -90           โ”‚ adjustable        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

4.3 ์ฐฝํ•จ์ˆ˜์™€ ์ŠคํŽ™ํŠธ๋Ÿผ ์‹œ๊ฐํ™”

def compare_windows(M=50):
    """Compare common window functions in time and frequency domains."""
    windows = {
        'Rectangular': np.ones(M + 1),
        'Hann': signal.windows.hann(M + 1),
        'Hamming': signal.windows.hamming(M + 1),
        'Blackman': signal.windows.blackman(M + 1),
    }

    fig, axes = plt.subplots(2, 1, figsize=(12, 8))
    colors = ['blue', 'orange', 'green', 'red']

    # Time domain
    ax = axes[0]
    for (name, w), color in zip(windows.items(), colors):
        ax.plot(np.arange(M + 1), w, color=color, linewidth=2, label=name)
    ax.set_xlabel('Sample n')
    ax.set_ylabel('w[n]')
    ax.set_title('Window Functions (Time Domain)')
    ax.legend()
    ax.grid(True, alpha=0.3)

    # Frequency domain (log magnitude)
    ax = axes[1]
    nfft = 4096
    for (name, w), color in zip(windows.items(), colors):
        W = np.fft.fft(w, nfft)
        W_shift = np.fft.fftshift(W)
        freq = np.linspace(-np.pi, np.pi, nfft)
        W_dB = 20 * np.log10(np.abs(W_shift) / np.max(np.abs(W_shift)) + 1e-15)
        ax.plot(freq / np.pi, W_dB, color=color, linewidth=1.5, label=name)

    ax.set_xlabel('Normalized Frequency (ร—ฯ€ rad/sample)')
    ax.set_ylabel('Magnitude (dB)')
    ax.set_title('Window Spectra (Frequency Domain)')
    ax.set_xlim(-0.5, 0.5)
    ax.set_ylim(-100, 5)
    ax.legend()
    ax.grid(True, alpha=0.3)

    plt.tight_layout()
    plt.savefig('window_comparison.png', dpi=150)
    plt.close()

compare_windows(M=50)

4.4 ์ฐฝํ•จ์ˆ˜๋ฒ• ์„ค๊ณ„ ์ ˆ์ฐจ

  1. ์‚ฌ์–‘ ๊ฒฐ์ •: $\omega_p$, $\omega_s$, $\delta_1$, $\delta_2$
  2. ์ฐฝํ•จ์ˆ˜ ์œ ํ˜• ์„ ํƒ: ์š”๊ตฌ๋˜๋Š” ์ €์ง€๋Œ€์—ญ ๊ฐ์‡ ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์„ ํƒ
  3. ์ „์ด ํญ ๊ณ„์‚ฐ: $\Delta\omega = \omega_s - \omega_p$
  4. ํ•„ํ„ฐ ์ฐจ์ˆ˜ ๊ฒฐ์ •: ์ฐฝํ•จ์ˆ˜์˜ ์ „์ด ํญ ๊ณต์‹์œผ๋กœ๋ถ€ํ„ฐ $M$ ๊ฒฐ์ •
  5. ์ฐจ๋‹จ ์ฃผํŒŒ์ˆ˜ ์„ค์ •: $\omega_c = (\omega_p + \omega_s) / 2$
  6. ์ด์ƒ์ ์ธ ์ž„ํŽ„์Šค ์‘๋‹ต ๊ณ„์‚ฐ: $M/2$๋งŒํผ ์ด๋™๋œ $h_d[n]$ ๊ณ„์‚ฐ
  7. ์ฐฝํ•จ์ˆ˜ ์ ์šฉ: $h[n] = h_d[n] \cdot w[n]$ ๊ณ„์‚ฐ
def design_fir_window(wp, ws, delta_s, window_type='hamming'):
    """
    Design FIR lowpass filter using window method.

    Parameters:
        wp: passband edge (normalized, 0 to pi)
        ws: stopband edge (normalized, 0 to pi)
        delta_s: stopband attenuation (linear)
        window_type: 'rectangular', 'hann', 'hamming', or 'blackman'

    Returns:
        h: filter coefficients
        M: filter order
    """
    # Transition width
    delta_w = ws - wp

    # Cutoff frequency (midpoint)
    wc = (wp + ws) / 2

    # Determine filter order M from window type
    transition_coefficients = {
        'rectangular': 0.92,
        'hann':        3.11,
        'hamming':     3.32,
        'blackman':    5.56,
    }

    coeff = transition_coefficients[window_type]
    M = int(np.ceil(coeff * 2 * np.pi / delta_w))
    if M % 2 == 1:
        M += 1  # Ensure even order for Type I filter

    # Compute windowed ideal lowpass
    n = np.arange(0, M + 1)
    alpha = M / 2

    h_d = np.where(
        n == alpha,
        wc / np.pi,
        np.sin(wc * (n - alpha)) / (np.pi * (n - alpha))
    )

    # Apply window
    windows = {
        'rectangular': np.ones(M + 1),
        'hann': signal.windows.hann(M + 1),
        'hamming': signal.windows.hamming(M + 1),
        'blackman': signal.windows.blackman(M + 1),
    }
    w = windows[window_type]
    h = h_d * w

    return h, M

# Example: design lowpass filter
wp = 0.3 * np.pi  # Passband edge
ws = 0.5 * np.pi  # Stopband edge

fig, axes = plt.subplots(2, 2, figsize=(14, 10))
window_types = ['rectangular', 'hann', 'hamming', 'blackman']

for ax, wtype in zip(axes.flat, window_types):
    h, M = design_fir_window(wp, ws, 0.001, wtype)
    w_freq, H = signal.freqz(h, worN=4096)
    H_dB = 20 * np.log10(np.abs(H) + 1e-15)

    ax.plot(w_freq / np.pi, H_dB, 'b-', linewidth=1.5)
    ax.axvline(0.3, color='g', linestyle='--', alpha=0.7, label=f'ฯ‰p = 0.3ฯ€')
    ax.axvline(0.5, color='r', linestyle='--', alpha=0.7, label=f'ฯ‰s = 0.5ฯ€')
    ax.set_xlabel('Normalized Frequency (ร—ฯ€ rad/sample)')
    ax.set_ylabel('Magnitude (dB)')
    ax.set_title(f'{wtype.capitalize()} Window, M = {M}')
    ax.set_ylim(-100, 5)
    ax.legend(fontsize=8)
    ax.grid(True, alpha=0.3)

plt.suptitle('FIR Lowpass Filter Design: Window Method Comparison', fontsize=13)
plt.tight_layout()
plt.savefig('fir_window_method.png', dpi=150)
plt.close()

5. ์นด์ด์ € ์ฐฝํ•จ์ˆ˜ ์„ค๊ณ„

5.1 ์นด์ด์ € ์ฐฝํ•จ์ˆ˜ ์ •์˜

์นด์ด์ € ์ฐฝํ•จ์ˆ˜(Kaiser Window)๋Š” ์ฃผ์—ฝ ํญ๊ณผ ๋ถ€์—ฝ ๋ ˆ๋ฒจ ์‚ฌ์ด์˜ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„๋ฅผ ์ œ์–ดํ•˜๋Š” ์—ฐ์† ์กฐ์ ˆ ๊ฐ€๋Šฅํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ $\beta$๋ฅผ ์ œ๊ณตํ•œ๋‹ค:

$$w[n] = \frac{I_0\left(\beta\sqrt{1 - \left(\frac{2n}{M} - 1\right)^2}\right)}{I_0(\beta)}, \quad 0 \leq n \leq M$$

์—ฌ๊ธฐ์„œ $I_0(\cdot)$๋Š” ์ œ1์ข… 0์ฐจ ๋ณ€ํ˜• ๋ฒ ์…€ ํ•จ์ˆ˜(Zeroth-Order Modified Bessel Function of the First Kind)์ด๋‹ค.

5.2 ์นด์ด์ €์˜ ๊ฒฝํ—˜์  ๊ณต์‹

์›ํ•˜๋Š” ์ €์ง€๋Œ€์—ญ ๊ฐ์‡  $A_s$ (dB)๊ฐ€ ์ฃผ์–ด์กŒ์„ ๋•Œ:

ํŒŒ๋ผ๋ฏธํ„ฐ $\beta$:

$$\beta = \begin{cases} 0.1102(A_s - 8.7), & A_s > 50 \\ 0.5842(A_s - 21)^{0.4} + 0.07886(A_s - 21), & 21 \leq A_s \leq 50 \\ 0, & A_s < 21 \end{cases}$$

ํ•„ํ„ฐ ์ฐจ์ˆ˜ $M$:

$$M = \left\lceil \frac{A_s - 7.95}{2.285 \cdot \Delta\omega} \right\rceil$$

์—ฌ๊ธฐ์„œ $\Delta\omega = \omega_s - \omega_p$๋Š” ์ „์ด ํญ์ด๋‹ค.

5.3 ์นด์ด์ € ์ฐฝํ•จ์ˆ˜ ์„ค๊ณ„ ์ ˆ์ฐจ

def kaiser_fir_design(wp, ws, As_dB):
    """
    Design FIR lowpass filter using Kaiser window.

    Parameters:
        wp: passband edge (normalized, 0 to pi)
        ws: stopband edge (normalized, 0 to pi)
        As_dB: stopband attenuation in dB

    Returns:
        h: filter coefficients
        M: filter order
        beta: Kaiser window parameter
    """
    # Transition width
    delta_w = ws - wp

    # Cutoff frequency
    wc = (wp + ws) / 2

    # Kaiser's empirical formula for beta
    if As_dB > 50:
        beta = 0.1102 * (As_dB - 8.7)
    elif As_dB >= 21:
        beta = 0.5842 * (As_dB - 21)**0.4 + 0.07886 * (As_dB - 21)
    else:
        beta = 0.0

    # Filter order
    M = int(np.ceil((As_dB - 7.95) / (2.285 * delta_w)))
    if M % 2 == 1:
        M += 1  # Ensure even order for Type I

    # Ideal lowpass impulse response
    n = np.arange(0, M + 1)
    alpha = M / 2
    h_d = np.where(
        n == alpha,
        wc / np.pi,
        np.sin(wc * (n - alpha)) / (np.pi * (n - alpha))
    )

    # Kaiser window
    w = signal.windows.kaiser(M + 1, beta)

    h = h_d * w
    return h, M, beta

# Design filters with different stopband attenuations
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
specs = [
    (0.3 * np.pi, 0.5 * np.pi, 30),
    (0.3 * np.pi, 0.5 * np.pi, 50),
    (0.3 * np.pi, 0.5 * np.pi, 70),
    (0.3 * np.pi, 0.5 * np.pi, 90),
]

for ax, (wp, ws, As) in zip(axes.flat, specs):
    h, M, beta = kaiser_fir_design(wp, ws, As)
    w_freq, H = signal.freqz(h, worN=4096)
    H_dB = 20 * np.log10(np.abs(H) + 1e-15)

    ax.plot(w_freq / np.pi, H_dB, 'b-', linewidth=1.5)
    ax.axhline(-As, color='r', linestyle='--', alpha=0.5, label=f'-{As} dB')
    ax.set_xlabel('Normalized Frequency (ร—ฯ€ rad/sample)')
    ax.set_ylabel('Magnitude (dB)')
    ax.set_title(f'Kaiser: As={As} dB, ฮฒ={beta:.2f}, M={M}')
    ax.set_ylim(-As - 20, 5)
    ax.legend()
    ax.grid(True, alpha=0.3)

plt.suptitle('Kaiser Window FIR Design: Varying Stopband Attenuation', fontsize=13)
plt.tight_layout()
plt.savefig('kaiser_design.png', dpi=150)
plt.close()

5.4 scipy.signal.kaiserord ์‚ฌ์šฉ

# scipy provides convenient functions for Kaiser window design
from scipy.signal import kaiserord, firwin

# Specification
fs = 8000  # Sample rate (Hz)
f_pass = 1000  # Passband edge (Hz)
f_stop = 1500  # Stopband edge (Hz)
A_stop = 60  # Stopband attenuation (dB)

# Compute transition width in normalized frequency
nyquist = fs / 2
width = (f_stop - f_pass) / nyquist  # Normalized transition width

# Kaiser window parameters
numtaps, beta = kaiserord(A_stop, width)
if numtaps % 2 == 0:
    numtaps += 1  # firwin needs odd number of taps for Type I

# Design the filter
cutoff = (f_pass + f_stop) / 2 / nyquist  # Normalized cutoff
h = firwin(numtaps, cutoff, window=('kaiser', beta))

print(f"Filter order: {numtaps - 1}")
print(f"Number of taps: {numtaps}")
print(f"Kaiser beta: {beta:.4f}")

# Frequency response
w, H = signal.freqz(h, worN=4096, fs=fs)
H_dB = 20 * np.log10(np.abs(H) + 1e-15)

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Magnitude response
axes[0].plot(w, H_dB, 'b-', linewidth=1.5)
axes[0].axhline(-A_stop, color='r', linestyle='--', label=f'-{A_stop} dB')
axes[0].axvline(f_pass, color='g', linestyle='--', alpha=0.5, label=f'fp={f_pass} Hz')
axes[0].axvline(f_stop, color='orange', linestyle='--', alpha=0.5, label=f'fs={f_stop} Hz')
axes[0].set_xlabel('Frequency (Hz)')
axes[0].set_ylabel('Magnitude (dB)')
axes[0].set_title('Magnitude Response')
axes[0].set_ylim(-100, 5)
axes[0].legend()
axes[0].grid(True, alpha=0.3)

# Impulse response
axes[1].stem(h, linefmt='b-', markerfmt='bo', basefmt='k-')
axes[1].set_xlabel('Sample n')
axes[1].set_ylabel('h[n]')
axes[1].set_title(f'Impulse Response (M={numtaps - 1})')
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('kaiser_scipy.png', dpi=150)
plt.close()

6. ์ฃผํŒŒ์ˆ˜ ํ‘œ๋ณธํ™”๋ฒ•

6.1 ์›๋ฆฌ

์ฃผํŒŒ์ˆ˜ ํ‘œ๋ณธํ™”๋ฒ•(Frequency Sampling Method)์€ $N$๊ฐœ์˜ ๋“ฑ๊ฐ„๊ฒฉ ์ฃผํŒŒ์ˆ˜ ์ ์—์„œ ์›ํ•˜๋Š” ์ฃผํŒŒ์ˆ˜ ์‘๋‹ต์„ ์ง€์ •ํ•˜๊ณ  ์—ญ DFT๋ฅผ ํ†ตํ•ด ํ•„ํ„ฐ ๊ณ„์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•˜์—ฌ FIR ํ•„ํ„ฐ๋ฅผ ์„ค๊ณ„ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค:

$$H[k] = H(e^{j 2\pi k / N}), \quad k = 0, 1, \ldots, N-1$$

$$h[n] = \frac{1}{N} \sum_{k=0}^{N-1} H[k] e^{j 2\pi k n / N}, \quad n = 0, 1, \ldots, N-1$$

6.2 ์ „์ด ํ‘œ๋ณธ(Transition Samples)

ํ•ต์‹ฌ ์•„์ด๋””์–ด๋Š” ์ „์ด ๋Œ€์—ญ ํ‘œ๋ณธ์„ ์ตœ์ ํ™”ํ•˜์—ฌ ์ €์ง€๋Œ€์—ญ ๋ฆฌํ”Œ์„ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. 1์—์„œ 0์œผ๋กœ ๊ธ‰๊ฒฉํžˆ ์ „ํ™˜ํ•˜๋Š” ๋Œ€์‹ , ์ค‘๊ฐ„ ๊ฐ’ $T_1, T_2, \ldots$๋ฅผ ์ „์ด ๋Œ€์—ญ์— ๋ฐฐ์น˜ํ•œ๋‹ค.

์ฃผํŒŒ์ˆ˜ ํ‘œ๋ณธ H[k]:
H[k]
  โ†‘
  1 โ”€ โ— โ”€ โ— โ”€ โ— โ”€ โ—
  โ”‚                   โ•ฒ
Tโ‚โ”‚                    โ—   โ† ์ „์ด ํ‘œ๋ณธ (์ตœ์ ํ™”๋จ)
  โ”‚                      โ•ฒ
  0 โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€โ— โ”€ โ— โ”€ โ— โ”€ โ— โ”€ โ—
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ†’ k

6.3 ๊ตฌํ˜„

def freq_sampling_lowpass(N, cutoff_bin, transition_values=None):
    """
    FIR filter design via frequency sampling.

    Parameters:
        N: number of samples (filter length)
        cutoff_bin: passband ends at bin index cutoff_bin
        transition_values: list of transition band sample values

    Returns:
        h: filter coefficients (real)
    """
    H = np.zeros(N, dtype=complex)

    # Passband (bins 0 to cutoff_bin)
    H[:cutoff_bin + 1] = 1.0

    # Mirror for negative frequencies (ensure real h[n])
    H[N - cutoff_bin:] = 1.0

    # Transition band samples
    if transition_values is not None:
        for i, T in enumerate(transition_values):
            H[cutoff_bin + 1 + i] = T
            H[N - cutoff_bin - 1 - i] = T

    # Inverse DFT to get filter coefficients
    h = np.real(np.fft.ifft(H))

    # Circular shift for causal filter
    h = np.roll(h, N // 2)

    return h

# Compare with and without optimized transition samples
N = 33
cutoff_bin = 5

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Without transition samples
h1 = freq_sampling_lowpass(N, cutoff_bin)
w1, H1 = signal.freqz(h1, worN=4096)

# With optimized transition sample
h2 = freq_sampling_lowpass(N, cutoff_bin, transition_values=[0.5])
w2, H2 = signal.freqz(h2, worN=4096)

# With two optimized transition samples
h3 = freq_sampling_lowpass(N, cutoff_bin, transition_values=[0.59, 0.11])
w3, H3 = signal.freqz(h3, worN=4096)

axes[0].plot(w1 / np.pi, 20 * np.log10(np.abs(H1) + 1e-15), label='No transition')
axes[0].plot(w2 / np.pi, 20 * np.log10(np.abs(H2) + 1e-15), label='T=[0.5]')
axes[0].plot(w3 / np.pi, 20 * np.log10(np.abs(H3) + 1e-15), label='T=[0.59, 0.11]')
axes[0].set_xlabel('Normalized Frequency (ร—ฯ€ rad/sample)')
axes[0].set_ylabel('Magnitude (dB)')
axes[0].set_title('Frequency Sampling: Effect of Transition Values')
axes[0].set_ylim(-100, 5)
axes[0].legend()
axes[0].grid(True, alpha=0.3)

axes[1].stem(np.arange(N), h3, linefmt='b-', markerfmt='bo', basefmt='k-')
axes[1].set_xlabel('Sample n')
axes[1].set_ylabel('h[n]')
axes[1].set_title('Impulse Response (Optimized Transition)')
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('freq_sampling.png', dpi=150)
plt.close()

7. ์ตœ์  ๋“ฑ๋ฆฌํ”Œ ์„ค๊ณ„: Parks-McClellan ์•Œ๊ณ ๋ฆฌ์ฆ˜

7.1 ๋ฏธ๋‹ˆ๋งฅ์Šค ์ตœ์ ํ™”(Minimax Optimization)

Parks-McClellan (PM) ์•Œ๊ณ ๋ฆฌ์ฆ˜์€ Remez ๊ตํ™˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜(Remez Exchange Algorithm)์œผ๋กœ๋„ ์•Œ๋ ค์ ธ ์žˆ์œผ๋ฉฐ, ์ฃผํŒŒ์ˆ˜ ์˜์—ญ์—์„œ ์ตœ๋Œ€ ๊ฐ€์ค‘ ์˜ค์ฐจ๋ฅผ ์ตœ์†Œํ™”ํ•˜๋Š” FIR ํ•„ํ„ฐ๋ฅผ ์„ค๊ณ„ํ•œ๋‹ค:

$$\min_{h} \max_{\omega \in \text{bands}} \left| W(\omega) \left[ H_d(\omega) - H(\omega) \right] \right|$$

์—ฌ๊ธฐ์„œ: - $H_d(\omega)$๋Š” ์›ํ•˜๋Š” ์ฃผํŒŒ์ˆ˜ ์‘๋‹ต - $H(\omega) = \sum_{n=0}^{M} h[n] e^{-j\omega n}$๋Š” ์‹ค์ œ ์‘๋‹ต - $W(\omega)$๋Š” ๊ฐ€์ค‘ ํ•จ์ˆ˜

7.2 ๋“ฑ๋ฆฌํ”Œ ํŠน์„ฑ(Equiripple Property)

์ฒด๋น„์‡ผํ”„ ์ •๋ฆฌ(Chebyshev Theorem)๋Š” ์ตœ์  ํ•ด๊ฐ€ ๋“ฑ๋ฆฌํ”Œ(Equiripple) ์˜ค์ฐจ๋ฅผ ๋‚˜ํƒ€๋ƒ„์„ ๋ณด์žฅํ•œ๋‹ค -- ์˜ค์ฐจ๋Š” ์ง€์ •๋œ ๋ชจ๋“  ๋Œ€์—ญ์—์„œ ๋™์ผํ•œ ์ง„ํญ์œผ๋กœ $\pm\delta$ ์‚ฌ์ด๋ฅผ ์ง„๋™ํ•œ๋‹ค:

๋“ฑ๋ฆฌํ”Œ ์˜ค์ฐจ ๊ฑฐ๋™:
          ํ†ต๊ณผ๋Œ€์—ญ                      ์ €์ง€๋Œ€์—ญ
    โ†‘    +ฮดโ‚ โ”€ โ”€ โ”€ โ•ฑโ•ฒ    โ•ฑโ•ฒ
H(ฯ‰)โ”‚          โ•ฑ  โ•ฒ โ•ฑ  โ•ฒ
    โ”‚    โ”€โ”€โ”€โ”€โ”€โ•ฑโ”€โ”€โ”€โ”€โ•ฒโ•ฑโ”€โ”€โ”€โ”€โ•ฒโ”€โ”€โ”€โ”€ 1.0
    โ”‚
    โ”‚                                     โ•ฑโ•ฒ    โ•ฑโ•ฒ    โ•ฑโ•ฒ
    โ”‚     -ฮดโ‚ โ”€ โ”€ โ”€               โ”€โ”€โ•ฒโ”€โ”€โ•ฑโ”€โ”€โ•ฒโ”€โ”€โ•ฑโ”€โ”€โ•ฒโ”€โ”€โ•ฑโ”€โ”€
    โ”‚                          +ฮดโ‚‚ โ”€ โ”€โ•ฒโ•ฑโ”€ โ”€ โ•ฒโ•ฑโ”€ โ”€ โ•ฒโ•ฑโ”€
    โ”‚                          -ฮดโ‚‚ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ†’ ฯ‰

7.3 Remez ๊ตํ™˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜

์•Œ๊ณ ๋ฆฌ์ฆ˜์€ ๋ฐ˜๋ณต์ ์œผ๋กœ:

  1. $(M/2 + 2)$๊ฐœ์˜ ๊ทน์  ์ฃผํŒŒ์ˆ˜(Extremal Frequency) ์ ์œผ๋กœ ์ดˆ๊ธฐํ™”ํ•œ๋‹ค
  2. ๋“ฑ๋ฆฌํ”Œ ๊ฑฐ๋™์„ ๋งŒ๋“ค์–ด๋‚ด๋Š” ๋‹คํ•ญ์‹์„ ๊ตฌํ•˜๋Š” ๋ณด๊ฐ„ ๋ฌธ์ œ๋ฅผ ํ’€์ดํ•œ๋‹ค
  3. ์˜ค์ฐจ๊ฐ€ ์ตœ๋Œ€์ธ ์ƒˆ๋กœ์šด ๊ทน์ ์„ ํƒ์ƒ‰ํ•œ๋‹ค
  4. ํ˜„์žฌ ๊ทน์  ์ง‘ํ•ฉ์„ ์ƒˆ ์ง‘ํ•ฉ์œผ๋กœ ๊ตํ™˜ํ•œ๋‹ค
  5. ์ˆ˜๋ ดํ•  ๋•Œ๊นŒ์ง€ (๊ทน์ ์ด ์•ˆ์ •๋  ๋•Œ๊นŒ์ง€) ๋ฐ˜๋ณตํ•œ๋‹ค

7.4 scipy.signal.remez๋ฅผ ์ด์šฉํ•œ ๊ตฌํ˜„

from scipy.signal import remez, freqz

def parks_mcclellan_lowpass(numtaps, fp, fs, fs_hz=1.0, weight=None):
    """
    Design lowpass FIR filter using Parks-McClellan algorithm.

    Parameters:
        numtaps: number of filter taps (M + 1)
        fp: passband edge frequency (Hz)
        fs_freq: stopband edge frequency (Hz)
        fs_hz: sampling frequency (Hz)
        weight: weighting [W_pass, W_stop]

    Returns:
        h: filter coefficients
    """
    if weight is None:
        weight = [1, 1]

    # Bands: [0, fp, fs, fs_hz/2]
    bands = [0, fp, fs, fs_hz / 2]
    desired = [1, 0]  # Passband = 1, Stopband = 0

    h = remez(numtaps, bands, desired, weight=weight, fs=fs_hz)
    return h

# Example: lowpass filter design
fs_hz = 8000
numtaps = 51
fp = 1000  # Passband edge (Hz)
f_stop = 1500  # Stopband edge (Hz)

# Different weighting schemes
fig, axes = plt.subplots(2, 2, figsize=(14, 10))

weights = [
    ([1, 1], 'Equal weight'),
    ([1, 10], 'Stopband 10ร— weight'),
    ([10, 1], 'Passband 10ร— weight'),
    ([1, 100], 'Stopband 100ร— weight'),
]

for ax, (w, title) in zip(axes.flat, weights):
    h = parks_mcclellan_lowpass(numtaps, fp, f_stop, fs_hz, weight=w)
    freq, H = signal.freqz(h, worN=4096, fs=fs_hz)
    H_dB = 20 * np.log10(np.abs(H) + 1e-15)

    ax.plot(freq, H_dB, 'b-', linewidth=1.5)
    ax.axvline(fp, color='g', linestyle='--', alpha=0.5, label=f'fp={fp} Hz')
    ax.axvline(f_stop, color='r', linestyle='--', alpha=0.5, label=f'fs={f_stop} Hz')
    ax.set_xlabel('Frequency (Hz)')
    ax.set_ylabel('Magnitude (dB)')
    ax.set_title(f'PM Design: {title}')
    ax.set_ylim(-80, 5)
    ax.legend(fontsize=8)
    ax.grid(True, alpha=0.3)

plt.suptitle('Parks-McClellan Filter Design with Different Weights', fontsize=13)
plt.tight_layout()
plt.savefig('parks_mcclellan.png', dpi=150)
plt.close()

7.5 ๋Œ€์—ญํ†ต๊ณผ ํ•„ํ„ฐ ์˜ˆ์‹œ

# Bandpass filter using Parks-McClellan
fs_hz = 8000
numtaps = 101

# Bandpass: pass 1000-2000 Hz, stop below 500 and above 2500
bands = [0, 500, 1000, 2000, 2500, fs_hz / 2]
desired = [0, 1, 0]
weight = [1, 1, 1]

h_bp = remez(numtaps, bands, desired, weight=weight, fs=fs_hz)

# Frequency response
freq, H_bp = signal.freqz(h_bp, worN=4096, fs=fs_hz)
H_bp_dB = 20 * np.log10(np.abs(H_bp) + 1e-15)

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

axes[0].plot(freq, H_bp_dB, 'b-', linewidth=1.5)
axes[0].axvspan(1000, 2000, alpha=0.1, color='green', label='Passband')
axes[0].set_xlabel('Frequency (Hz)')
axes[0].set_ylabel('Magnitude (dB)')
axes[0].set_title('Bandpass FIR (Parks-McClellan)')
axes[0].set_ylim(-80, 5)
axes[0].legend()
axes[0].grid(True, alpha=0.3)

axes[1].plot(freq, np.abs(H_bp), 'b-', linewidth=1.5)
axes[1].axvspan(1000, 2000, alpha=0.1, color='green', label='Passband')
axes[1].set_xlabel('Frequency (Hz)')
axes[1].set_ylabel('|H(f)|')
axes[1].set_title('Magnitude Response (Linear)')
axes[1].legend()
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('bandpass_pm.png', dpi=150)
plt.close()

8. ์„ ํ˜• ์œ„์ƒ FIR ํ•„ํ„ฐ

8.1 ์„ ํ˜• ์œ„์ƒ์˜ ์ค‘์š”์„ฑ

ํ•„ํ„ฐ๊ฐ€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ‘œํ˜„๋  ์ˆ˜ ์žˆ์„ ๋•Œ ์„ ํ˜• ์œ„์ƒ(Linear Phase)์„ ๊ฐ–๋Š”๋‹ค๊ณ  ํ•œ๋‹ค:

$$H(e^{j\omega}) = |H(e^{j\omega})| \cdot e^{-j\omega \tau}$$

์—ฌ๊ธฐ์„œ $\tau$๋Š” ์ผ์ •ํ•œ ๊ตฐ์ง€์—ฐ(Group Delay)์ด๋‹ค. ์„ ํ˜• ์œ„์ƒ์€ ๋ชจ๋“  ์ฃผํŒŒ์ˆ˜ ์„ฑ๋ถ„์ด ๋™์ผํ•œ ์‹œ๊ฐ„๋งŒํผ ์ง€์—ฐ๋จ์„ ์˜๋ฏธํ•˜๋ฉฐ, ํŒŒํ˜• ๋ชจ์–‘์„ ๋ณด์กดํ•œ๋‹ค.

๊ตฐ์ง€์—ฐ:

$$\tau(\omega) = -\frac{d\angle H(e^{j\omega})}{d\omega} = \text{์ƒ์ˆ˜} = \frac{M}{2}$$

8.2 ๋Œ€์นญ ์กฐ๊ฑด

์„ ํ˜• ์œ„์ƒ FIR ํ•„ํ„ฐ๋Š” ๋Œ€์นญ ๋˜๋Š” ๋ฐ˜๋Œ€์นญ ์ž„ํŽ„์Šค ์‘๋‹ต์„ ํ•„์š”๋กœ ํ•œ๋‹ค:

๋Œ€์นญ(Symmetric) (ํƒ€์ž… I ๋ฐ II): $h[n] = h[M - n]$

๋ฐ˜๋Œ€์นญ(Antisymmetric) (ํƒ€์ž… III ๋ฐ IV): $h[n] = -h[M - n]$

8.3 ์„ ํ˜• ์œ„์ƒ FIR ํ•„ํ„ฐ์˜ ๋„ค ๊ฐ€์ง€ ์œ ํ˜•

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ์œ ํ˜•   โ”‚ ๋Œ€์นญ์„ฑ       โ”‚ ์ฐจ์ˆ˜ M               โ”‚ ์ ํ•ฉํ•œ ํ•„ํ„ฐ ์œ ํ˜•  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ I      โ”‚ ๋Œ€์นญ         โ”‚ ์ง์ˆ˜                 โ”‚ ๋ชจ๋“  ํ•„ํ„ฐ ์œ ํ˜•    โ”‚
โ”‚ II     โ”‚ ๋Œ€์นญ         โ”‚ ํ™€์ˆ˜                 โ”‚ ์ €์—ญํ†ต๊ณผ, ๋Œ€์—ญํ†ต๊ณผโ”‚
โ”‚ III    โ”‚ ๋ฐ˜๋Œ€์นญ       โ”‚ ์ง์ˆ˜                 โ”‚ ๋Œ€์—ญํ†ต๊ณผ, ํž๋ฒ„ํŠธ, โ”‚
โ”‚        โ”‚              โ”‚                      โ”‚ ๋ฏธ๋ถ„๊ธฐ            โ”‚
โ”‚ IV     โ”‚ ๋ฐ˜๋Œ€์นญ       โ”‚ ํ™€์ˆ˜                 โ”‚ ๊ณ ์—ญํ†ต๊ณผ, ๋Œ€์—ญํ†ต๊ณผโ”‚
โ”‚        โ”‚              โ”‚                      โ”‚ ํž๋ฒ„ํŠธ            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

ํƒ€์ž… I (์ง์ˆ˜ ์ฐจ์ˆ˜, ๋Œ€์นญ): - $H(e^{j0}) = \sum h[n]$ (์ œ์•ฝ ์—†์Œ) - $H(e^{j\pi}) = \sum (-1)^n h[n]$ (์ œ์•ฝ ์—†์Œ) - ๋ชจ๋“  ํ•„ํ„ฐ ์œ ํ˜•์— ์ ํ•ฉ

ํƒ€์ž… II (ํ™€์ˆ˜ ์ฐจ์ˆ˜, ๋Œ€์นญ): - $H(e^{j\pi}) = 0$ ํ•ญ์ƒ ์„ฑ๋ฆฝ - ๊ณ ์—ญํ†ต๊ณผ(Highpass) ๋˜๋Š” ๋Œ€์—ญ์ €์ง€(Bandstop) ํ•„ํ„ฐ์— ์‚ฌ์šฉ ๋ถˆ๊ฐ€

ํƒ€์ž… III (์ง์ˆ˜ ์ฐจ์ˆ˜, ๋ฐ˜๋Œ€์นญ): - $H(e^{j0}) = 0$ ๋ฐ $H(e^{j\pi}) = 0$ ํ•ญ์ƒ ์„ฑ๋ฆฝ - ๋Œ€์—ญํ†ต๊ณผ, ํž๋ฒ„ํŠธ ๋ณ€ํ™˜(Hilbert Transform), ๋ฏธ๋ถ„๊ธฐ(Differentiator)์— ์ ํ•ฉ

ํƒ€์ž… IV (ํ™€์ˆ˜ ์ฐจ์ˆ˜, ๋ฐ˜๋Œ€์นญ): - $H(e^{j0}) = 0$ ํ•ญ์ƒ ์„ฑ๋ฆฝ - ์ €์—ญํ†ต๊ณผ ํ•„ํ„ฐ์— ์‚ฌ์šฉ ๋ถˆ๊ฐ€

8.4 ์ง„ํญ ์‘๋‹ต ๊ณต์‹

ํƒ€์ž… I ํ•„ํ„ฐ (์ง์ˆ˜ $M$, ๋Œ€์นญ)์˜ ๊ฒฝ์šฐ, ์ฃผํŒŒ์ˆ˜ ์‘๋‹ต์€:

$$H(e^{j\omega}) = e^{-j\omega M/2} A(\omega)$$

์—ฌ๊ธฐ์„œ ์ง„ํญ ์‘๋‹ต(Amplitude Response)์€:

$$A(\omega) = h[M/2] + 2 \sum_{k=1}^{M/2} h[M/2 - k] \cos(k\omega)$$

์ด๋Š” ์‹ค์ˆ˜ ๊ฐ’ ํ•จ์ˆ˜๋กœ, ํ†ต๊ณผ๋Œ€์—ญ/์ €์ง€๋Œ€์—ญ ๊ฑฐ๋™ ๋ถ„์„์ด ์šฉ์ดํ•˜๋‹ค.

def analyze_linear_phase(h, filter_type=None):
    """Analyze linear phase properties of FIR filter."""
    M = len(h) - 1

    # Check symmetry
    is_symmetric = np.allclose(h, h[::-1])
    is_antisymmetric = np.allclose(h, -h[::-1])

    if is_symmetric and M % 2 == 0:
        ftype = "Type I"
    elif is_symmetric and M % 2 == 1:
        ftype = "Type II"
    elif is_antisymmetric and M % 2 == 0:
        ftype = "Type III"
    elif is_antisymmetric and M % 2 == 1:
        ftype = "Type IV"
    else:
        ftype = "Not linear phase"

    # Frequency response
    w, H = signal.freqz(h, worN=4096)

    # Group delay
    w_gd, gd = signal.group_delay((h, [1]), w=4096)

    fig, axes = plt.subplots(2, 2, figsize=(14, 10))

    # Magnitude
    axes[0, 0].plot(w / np.pi, 20 * np.log10(np.abs(H) + 1e-15), 'b-')
    axes[0, 0].set_ylabel('Magnitude (dB)')
    axes[0, 0].set_title(f'{ftype}: Magnitude Response')
    axes[0, 0].grid(True, alpha=0.3)

    # Phase
    axes[0, 1].plot(w / np.pi, np.unwrap(np.angle(H)), 'r-')
    axes[0, 1].set_ylabel('Phase (radians)')
    axes[0, 1].set_title(f'{ftype}: Phase Response')
    axes[0, 1].grid(True, alpha=0.3)

    # Group delay
    axes[1, 0].plot(w_gd / np.pi, gd, 'g-')
    axes[1, 0].axhline(M / 2, color='r', linestyle='--', alpha=0.5,
                         label=f'M/2 = {M/2}')
    axes[1, 0].set_ylabel('Group Delay (samples)')
    axes[1, 0].set_title('Group Delay')
    axes[1, 0].legend()
    axes[1, 0].grid(True, alpha=0.3)

    # Impulse response (with symmetry visualization)
    axes[1, 1].stem(h, linefmt='b-', markerfmt='bo', basefmt='k-')
    axes[1, 1].axvline(M / 2, color='r', linestyle='--', alpha=0.5,
                        label='Center of symmetry')
    axes[1, 1].set_ylabel('h[n]')
    axes[1, 1].set_title(f'Impulse Response (M={M})')
    axes[1, 1].legend()
    axes[1, 1].grid(True, alpha=0.3)

    for ax in [axes[1, 0], axes[1, 1]]:
        ax.set_xlabel('Normalized Frequency (ร—ฯ€)' if ax == axes[1, 0] else 'Sample n')
    for ax in [axes[0, 0], axes[0, 1]]:
        ax.set_xlabel('Normalized Frequency (ร—ฯ€)')

    plt.suptitle(f'Linear Phase Analysis: {ftype}', fontsize=13)
    plt.tight_layout()
    return ftype

# Example: Type I lowpass
h_type1 = firwin(51, 0.4)  # 50th order, symmetric
ftype = analyze_linear_phase(h_type1)
plt.savefig('linear_phase_analysis.png', dpi=150)
plt.close()
print(f"Detected filter type: {ftype}")

9. ์„ค๊ณ„ ๋ฐฉ๋ฒ• ๋น„๊ต

9.1 ์ง์ ‘ ๋น„๊ต

def compare_design_methods(numtaps=51, wp=0.3, ws=0.5):
    """
    Compare window, Kaiser, and Parks-McClellan methods for the same specs.
    """
    wc_norm = (wp + ws) / 2  # Cutoff for window methods

    # Method 1: Hamming window
    h_hamming = firwin(numtaps, wc_norm, window='hamming')

    # Method 2: Kaiser window (60 dB attenuation)
    h_kaiser = firwin(numtaps, wc_norm, window=('kaiser', 5.65))

    # Method 3: Parks-McClellan
    h_pm = remez(numtaps, [0, wp / 2, ws / 2, 0.5], [1, 0])

    # Frequency responses
    methods = [
        ('Hamming Window', h_hamming),
        ('Kaiser Window (ฮฒ=5.65)', h_kaiser),
        ('Parks-McClellan', h_pm),
    ]

    fig, axes = plt.subplots(2, 2, figsize=(14, 10))
    colors = ['blue', 'green', 'red']

    # Overlay magnitude responses
    ax = axes[0, 0]
    for (name, h), color in zip(methods, colors):
        w, H = signal.freqz(h, worN=4096)
        ax.plot(w / np.pi, 20 * np.log10(np.abs(H) + 1e-15),
                color=color, linewidth=1.5, label=name)
    ax.set_xlabel('Normalized Frequency (ร—ฯ€)')
    ax.set_ylabel('Magnitude (dB)')
    ax.set_title('Magnitude Response Comparison')
    ax.set_ylim(-80, 5)
    ax.legend()
    ax.grid(True, alpha=0.3)

    # Passband detail
    ax = axes[0, 1]
    for (name, h), color in zip(methods, colors):
        w, H = signal.freqz(h, worN=4096)
        ax.plot(w / np.pi, 20 * np.log10(np.abs(H) + 1e-15),
                color=color, linewidth=1.5, label=name)
    ax.set_xlabel('Normalized Frequency (ร—ฯ€)')
    ax.set_ylabel('Magnitude (dB)')
    ax.set_title('Passband Detail')
    ax.set_xlim(0, wp + 0.05)
    ax.set_ylim(-1, 0.5)
    ax.legend(fontsize=8)
    ax.grid(True, alpha=0.3)

    # Transition band detail
    ax = axes[1, 0]
    for (name, h), color in zip(methods, colors):
        w, H = signal.freqz(h, worN=4096)
        ax.plot(w / np.pi, np.abs(H), color=color, linewidth=1.5, label=name)
    ax.set_xlabel('Normalized Frequency (ร—ฯ€)')
    ax.set_ylabel('|H(f)|')
    ax.set_title('Transition Band Detail')
    ax.set_xlim(wp - 0.05, ws + 0.05)
    ax.legend(fontsize=8)
    ax.grid(True, alpha=0.3)

    # Impulse responses
    ax = axes[1, 1]
    n = np.arange(numtaps)
    for (name, h), color in zip(methods, colors):
        ax.plot(n, h, color=color, linewidth=1.5, marker='o',
                markersize=3, label=name)
    ax.set_xlabel('Sample n')
    ax.set_ylabel('h[n]')
    ax.set_title('Impulse Responses')
    ax.legend(fontsize=8)
    ax.grid(True, alpha=0.3)

    plt.suptitle(f'FIR Design Comparison (N={numtaps})', fontsize=13)
    plt.tight_layout()
    plt.savefig('design_comparison.png', dpi=150)
    plt.close()

compare_design_methods(numtaps=51, wp=0.3, ws=0.5)

9.2 ์„ค๊ณ„ ๋ฐฉ๋ฒ• ์š”์•ฝ

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ๋ฐฉ๋ฒ•             โ”‚ ์ตœ์ ์„ฑ       โ”‚ ์ œ์–ด           โ”‚ ์ตœ์  ์šฉ๋„        โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ์ฐฝํ•จ์ˆ˜๋ฒ•         โ”‚ ์ค€์ตœ์        โ”‚ ์ œํ•œ์          โ”‚ ๋น ๋ฅธ ์„ค๊ณ„,       โ”‚
โ”‚                  โ”‚              โ”‚ (์ด์‚ฐ ์ฐฝํ•จ์ˆ˜   โ”‚ ๊ต์œก์šฉ           โ”‚
โ”‚                  โ”‚              โ”‚  ์„ ํƒ)         โ”‚                  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ์นด์ด์ € ์ฐฝํ•จ์ˆ˜    โ”‚ ์ค€์ตœ์ ์— ๊ทผ์ ‘โ”‚ ์—ฐ์† ฮฒ         โ”‚ ์ œ์–ด์™€ ๋‹จ์ˆœ์„ฑ์˜  โ”‚
โ”‚                  โ”‚              โ”‚ ํŒŒ๋ผ๋ฏธํ„ฐ       โ”‚ ์ข‹์€ ๊ท ํ˜•        โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ์ฃผํŒŒ์ˆ˜ ํ‘œ๋ณธํ™”    โ”‚ ์ค€์ตœ์        โ”‚ ์ „์ด ํ‘œ๋ณธ      โ”‚ ํ•˜๋“œ์›จ์–ด ๊ตฌํ˜„,   โ”‚
โ”‚                  โ”‚              โ”‚                โ”‚ DFT ๊ธฐ๋ฐ˜ ํ•„ํ„ฐ    โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Parks-McClellan  โ”‚ ์ตœ์          โ”‚ ๊ฐ€์ค‘ ํ•จ์ˆ˜,     โ”‚ ์‹ค์ œ ์ œํ’ˆ,       โ”‚
โ”‚                  โ”‚ (๋ฏธ๋‹ˆ๋งฅ์Šค)   โ”‚ ๋‹ค์ค‘ ๋Œ€์—ญ      โ”‚ ์—„๊ฒฉํ•œ ์‚ฌ์–‘      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

ํ•ต์‹ฌ ์ธ์‚ฌ์ดํŠธ: ๋™์ผํ•œ ํ•„ํ„ฐ ์ฐจ์ˆ˜์—์„œ Parks-McClellan์€ ๊ฐ€์žฅ ์ž‘์€ ์ตœ๋Œ€ ์˜ค์ฐจ(๋“ฑ๋ฆฌํ”Œ)๋ฅผ ๋‹ฌ์„ฑํ•œ๋‹ค. ์ฐฝํ•จ์ˆ˜๋ฒ•์€ ๋” ๋†’์€ ์ฃผํŒŒ์ˆ˜์—์„œ ๋‹จ์กฐ ์ฆ๊ฐ€ํ•˜๋Š” ์ €์ง€๋Œ€์—ญ ๊ฐ์‡ ๋ฅผ ๊ฐ–๋Š” ํ•„ํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๋Š”๋ฐ, ์ด๋Š” ๋†’์€ ์ฃผํŒŒ์ˆ˜์—์„œ์˜ ๊ฐ์‡ ๋ฅผ "๋‚ญ๋น„"ํ•˜๋Š” ์…ˆ์ด๋‹ค.


10. FIR ํ•„ํ„ฐ ๊ตฌํ˜„

10.1 ์ง์ ‘ ํ•ฉ์„ฑ๊ณฑ(Direct Convolution)

๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•์€ ์ถœ๋ ฅ์„ ์ง์ ‘ ํ•ฉ์„ฑ๊ณฑ์œผ๋กœ ๊ณ„์‚ฐํ•œ๋‹ค:

$$y[n] = \sum_{k=0}^{M} h[k] x[n-k]$$

๋ณต์žก๋„: $M$์ฐจ ํ•„ํ„ฐ๋กœ $N$๊ฐœ์˜ ํ‘œ๋ณธ์„ ํ•„ํ„ฐ๋งํ•˜๋Š” ๋ฐ $O(MN)$.

def fir_direct(x, h):
    """Direct-form FIR filter implementation."""
    M = len(h)
    N = len(x)
    y = np.zeros(N + M - 1)

    for n in range(N + M - 1):
        for k in range(M):
            if 0 <= n - k < N:
                y[n] += h[k] * x[n - k]

    return y

# Using numpy's convolve (optimized)
x = np.random.randn(1000)
h = firwin(51, 0.3)

y_direct = np.convolve(x, h, mode='full')
print(f"Input length: {len(x)}, Filter length: {len(h)}, Output length: {len(y_direct)}")

10.2 FFT ๊ธฐ๋ฐ˜ ํ•„ํ„ฐ๋ง

๊ธด ์‹ ํ˜ธ์˜ ๊ฒฝ์šฐ FFT ๊ธฐ๋ฐ˜ ํ•ฉ์„ฑ๊ณฑ์ด ํ›จ์”ฌ ๋น ๋ฅด๋‹ค:

$$y = \text{IFFT}[\text{FFT}(x) \cdot \text{FFT}(h)]$$

๋ณต์žก๋„: $O(MN)$ ๋Œ€์‹  $O(N \log N)$.

FFT๊ฐ€ ๋” ๋นจ๋ผ์ง€๋Š” ๊ต์ฐจ์ ์€ ์ผ๋ฐ˜์ ์œผ๋กœ $M \approx 50-100$์ด๋‹ค.

10.3 ์ค‘์ฒฉ ๊ฐ€์‚ฐ๋ฒ•(Overlap-Add Method)

์ž…๋ ฅ์ด ์„ธ๊ทธ๋จผํŠธ๋กœ ๋„์ฐฉํ•˜๋Š” ์‹ค์‹œ๊ฐ„ ๋˜๋Š” ๋ธ”๋ก ๊ธฐ๋ฐ˜ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ๋ฐฉ๋ฒ•:

  1. ์ž…๋ ฅ $x[n]$์„ ๊ธธ์ด $L$์˜ ๋น„์ค‘์ฒฉ ๋ธ”๋ก์œผ๋กœ ๋ถ„ํ• ํ•œ๋‹ค
  2. ๊ฐ ๋ธ”๋ก์„ ๊ธธ์ด $L + M$์œผ๋กœ ์˜ ํŒจ๋”ฉํ•œ๋‹ค
  3. ๊ฐ ๋ธ”๋ก์„ ํ•„ํ„ฐ $h[n]$๊ณผ FFT ํ•ฉ์„ฑ๊ณฑํ•œ๋‹ค
  4. ์ธ์ ‘ ์ถœ๋ ฅ ๋ธ”๋ก๋“ค์„ ์ค‘์ฒฉ ๊ฐ€์‚ฐํ•œ๋‹ค (๋งˆ์ง€๋ง‰ $M$๊ฐœ์˜ ํ‘œ๋ณธ์ด ์ค‘์ฒฉ๋จ)
def overlap_add(x, h, L=256):
    """
    Overlap-add FIR filtering.

    Parameters:
        x: input signal
        h: FIR filter coefficients
        L: block size

    Returns:
        y: filtered output
    """
    M = len(h)
    N = len(x)
    N_fft = L + M - 1

    # Zero-pad filter to FFT length
    H = np.fft.fft(h, N_fft)

    # Output array
    y = np.zeros(N + M - 1)

    # Process blocks
    num_blocks = int(np.ceil(N / L))

    for i in range(num_blocks):
        # Extract block
        start = i * L
        end = min(start + L, N)
        x_block = np.zeros(L)
        x_block[:end - start] = x[start:end]

        # FFT convolution
        X_block = np.fft.fft(x_block, N_fft)
        y_block = np.real(np.fft.ifft(X_block * H))

        # Overlap-add
        y[start:start + N_fft] += y_block

    return y[:N + M - 1]

# Verify overlap-add produces same result as direct convolution
x = np.random.randn(5000)
h = firwin(101, 0.3)

y_direct = np.convolve(x, h)
y_ola = overlap_add(x, h, L=256)

error = np.max(np.abs(y_direct - y_ola))
print(f"Maximum error between direct and overlap-add: {error:.2e}")

10.4 ์ค‘์ฒฉ ์ €์žฅ๋ฒ•(Overlap-Save Method)

์ค‘์ฒฉ ๊ฐ€์‚ฐ๋ฒ•์˜ ๋Œ€์•ˆ:

  1. ์ž…๋ ฅ ๋ธ”๋ก์„ $M-1$๊ฐœ์˜ ํ‘œ๋ณธ๋งŒํผ ์ค‘์ฒฉ์‹œํ‚จ๋‹ค (๊ฐ ๋ธ”๋ก์— ์ด์ „ ๋ธ”๋ก์˜ $M-1$๊ฐœ ํ‘œ๋ณธ ํฌํ•จ)
  2. ์›ํ˜• ํ•ฉ์„ฑ๊ณฑ(Circular Convolution)์„ ์ด์šฉํ•˜์—ฌ FFT ํ•ฉ์„ฑ๊ณฑํ•œ๋‹ค (๊ธธ์ด $L+M-1$)
  3. ๊ฐ ์ถœ๋ ฅ ๋ธ”๋ก์˜ ์ฒซ $M-1$๊ฐœ ํ‘œ๋ณธ์„ ๋ฒ„๋ฆฐ๋‹ค (์›ํ˜• ๊ฐ๊น€์— ์˜ํ•ด ์˜ค์—ผ๋จ)
  4. ๋‚˜๋จธ์ง€ ํ‘œ๋ณธ๋“ค์„ ์—ฐ๊ฒฐํ•œ๋‹ค
def overlap_save(x, h, L=256):
    """
    Overlap-save FIR filtering.

    Parameters:
        x: input signal
        h: FIR filter coefficients
        L: block size (output samples per block)

    Returns:
        y: filtered output
    """
    M = len(h)
    N = len(x)
    N_fft = L + M - 1

    # Zero-pad filter to FFT length
    H = np.fft.fft(h, N_fft)

    # Prepend M-1 zeros to input
    x_padded = np.concatenate([np.zeros(M - 1), x])
    N_padded = len(x_padded)

    # Output
    y = np.zeros(N + M - 1)

    output_idx = 0
    block_start = 0

    while block_start < N_padded:
        # Extract overlapping block of length N_fft
        block_end = min(block_start + N_fft, N_padded)
        x_block = np.zeros(N_fft)
        x_block[:block_end - block_start] = x_padded[block_start:block_end]

        # Circular convolution via FFT
        X_block = np.fft.fft(x_block, N_fft)
        y_block = np.real(np.fft.ifft(X_block * H))

        # Keep only valid samples (discard first M-1)
        valid = y_block[M - 1:]
        valid_len = min(len(valid), N + M - 1 - output_idx)
        y[output_idx:output_idx + valid_len] = valid[:valid_len]

        output_idx += L
        block_start += L  # Advance by L (not N_fft)

    return y[:N + M - 1]

# Verify overlap-save
y_ols = overlap_save(x, h, L=256)
error_ols = np.max(np.abs(y_direct[:len(y_ols)] - y_ols[:len(y_direct)]))
print(f"Maximum error between direct and overlap-save: {error_ols:.2e}")

10.5 ์„ฑ๋Šฅ ๋น„๊ต

import time

def benchmark_methods(signal_lengths, filter_order=100):
    """Benchmark different FIR filtering methods."""
    h = firwin(filter_order + 1, 0.3)
    results = {name: [] for name in ['Direct (np.convolve)', 'Overlap-Add', 'scipy.fftconvolve']}

    for N in signal_lengths:
        x = np.random.randn(N)

        # Direct convolution
        t0 = time.time()
        _ = np.convolve(x, h)
        results['Direct (np.convolve)'].append(time.time() - t0)

        # Overlap-add
        t0 = time.time()
        _ = overlap_add(x, h, L=1024)
        results['Overlap-Add'].append(time.time() - t0)

        # scipy FFT convolve
        from scipy.signal import fftconvolve
        t0 = time.time()
        _ = fftconvolve(x, h)
        results['scipy.fftconvolve'].append(time.time() - t0)

    return results

signal_lengths = [1000, 5000, 10000, 50000, 100000, 500000]
results = benchmark_methods(signal_lengths)

plt.figure(figsize=(10, 6))
for name, times in results.items():
    plt.loglog(signal_lengths, times, 'o-', linewidth=2, label=name)
plt.xlabel('Signal Length N')
plt.ylabel('Time (seconds)')
plt.title('FIR Filtering: Performance Comparison')
plt.legend()
plt.grid(True, which='both', alpha=0.3)
plt.tight_layout()
plt.savefig('fir_performance.png', dpi=150)
plt.close()

11. Python ๊ตฌํ˜„

11.1 ์™„์ „ํ•œ ์„ค๊ณ„ ์˜ˆ์‹œ

import numpy as np
from scipy import signal
from scipy.signal import firwin, remez, freqz, kaiserord
import matplotlib.pyplot as plt

def complete_fir_design(fs, f_pass, f_stop, A_stop_dB, method='kaiser'):
    """
    Complete FIR lowpass filter design workflow.

    Parameters:
        fs: sampling frequency (Hz)
        f_pass: passband edge (Hz)
        f_stop: stopband edge (Hz)
        A_stop_dB: stopband attenuation (dB)
        method: 'kaiser', 'hamming', or 'remez'

    Returns:
        h: filter coefficients
        info: design information dictionary
    """
    nyq = fs / 2

    if method == 'kaiser':
        width = (f_stop - f_pass) / nyq
        numtaps, beta = kaiserord(A_stop_dB, width)
        if numtaps % 2 == 0:
            numtaps += 1
        cutoff = (f_pass + f_stop) / 2 / nyq
        h = firwin(numtaps, cutoff, window=('kaiser', beta))
        info = {'method': 'Kaiser', 'numtaps': numtaps, 'beta': beta}

    elif method == 'hamming':
        # Estimate order from transition width
        delta_f = (f_stop - f_pass) / fs
        numtaps = int(np.ceil(3.32 / delta_f)) + 1
        if numtaps % 2 == 0:
            numtaps += 1
        cutoff = (f_pass + f_stop) / 2 / nyq
        h = firwin(numtaps, cutoff, window='hamming')
        info = {'method': 'Hamming', 'numtaps': numtaps}

    elif method == 'remez':
        # Start with estimated order, iterate if needed
        delta_f = (f_stop - f_pass) / fs
        numtaps = int(np.ceil((-20 * np.log10(np.sqrt(0.001 * 0.001)) - 13) /
                              (14.6 * delta_f))) + 1
        if numtaps % 2 == 0:
            numtaps += 1

        delta_s = 10**(-A_stop_dB / 20)
        weight = [1, 1 / delta_s]  # Weight stopband more
        h = remez(numtaps, [0, f_pass, f_stop, nyq], [1, 0],
                  weight=[1, 1], fs=fs)
        info = {'method': 'Parks-McClellan', 'numtaps': numtaps}

    return h, info

# Design and analyze
fs = 16000
f_pass = 2000
f_stop = 3000
A_stop = 60

fig, axes = plt.subplots(3, 2, figsize=(14, 14))
methods = ['kaiser', 'hamming', 'remez']

for i, method in enumerate(methods):
    h, info = complete_fir_design(fs, f_pass, f_stop, A_stop, method=method)

    # Frequency response
    w, H = freqz(h, worN=4096, fs=fs)
    H_dB = 20 * np.log10(np.abs(H) + 1e-15)

    # Magnitude
    ax = axes[i, 0]
    ax.plot(w, H_dB, 'b-', linewidth=1.5)
    ax.axhline(-A_stop, color='r', linestyle='--', alpha=0.5, label=f'-{A_stop} dB')
    ax.axvline(f_pass, color='g', linestyle='--', alpha=0.5)
    ax.axvline(f_stop, color='orange', linestyle='--', alpha=0.5)
    ax.set_xlabel('Frequency (Hz)')
    ax.set_ylabel('Magnitude (dB)')
    ax.set_title(f"{info['method']} (N={info['numtaps']})")
    ax.set_ylim(-100, 5)
    ax.legend()
    ax.grid(True, alpha=0.3)

    # Group delay
    w_gd, gd = signal.group_delay((h, [1]), w=4096, fs=fs)
    ax = axes[i, 1]
    ax.plot(w_gd, gd, 'g-', linewidth=1.5)
    ax.set_xlabel('Frequency (Hz)')
    ax.set_ylabel('Group Delay (samples)')
    ax.set_title(f"{info['method']}: Group Delay")
    ax.grid(True, alpha=0.3)

plt.suptitle('Complete FIR Filter Design Comparison', fontsize=14)
plt.tight_layout()
plt.savefig('complete_fir_design.png', dpi=150)
plt.close()

11.2 ์‹ค์ œ ์‹ ํ˜ธ ํ•„ํ„ฐ๋ง

def demonstrate_filtering():
    """Demonstrate FIR filtering on a multi-tone signal."""
    fs = 8000
    t = np.arange(0, 1, 1/fs)

    # Create signal: 200 Hz + 800 Hz + 2000 Hz + noise
    x = (np.sin(2 * np.pi * 200 * t) +
         0.5 * np.sin(2 * np.pi * 800 * t) +
         0.3 * np.sin(2 * np.pi * 2000 * t) +
         0.1 * np.random.randn(len(t)))

    # Design lowpass filter (cutoff ~1000 Hz)
    h = firwin(101, 1000, fs=fs, window='hamming')

    # Apply filter
    y = signal.lfilter(h, 1, x)

    # Compensate for group delay
    delay = (len(h) - 1) // 2

    fig, axes = plt.subplots(3, 1, figsize=(14, 10))

    # Time domain
    t_ms = t * 1000
    axes[0].plot(t_ms[:500], x[:500], 'b-', alpha=0.7, label='Original')
    axes[0].plot(t_ms[:500], y[delay:delay + 500], 'r-', linewidth=2,
                  label='Filtered (delay compensated)')
    axes[0].set_xlabel('Time (ms)')
    axes[0].set_ylabel('Amplitude')
    axes[0].set_title('Time Domain')
    axes[0].legend()
    axes[0].grid(True, alpha=0.3)

    # Spectrum comparison
    from scipy.fft import fft, fftfreq

    N = len(x)
    freq = fftfreq(N, 1/fs)[:N//2]
    X = 2 / N * np.abs(fft(x))[:N//2]
    Y = 2 / N * np.abs(fft(y))[:N//2]

    axes[1].plot(freq, X, 'b-', alpha=0.7, label='Original')
    axes[1].plot(freq, Y, 'r-', linewidth=2, label='Filtered')
    axes[1].axvline(1000, color='g', linestyle='--', alpha=0.5, label='Cutoff')
    axes[1].set_xlabel('Frequency (Hz)')
    axes[1].set_ylabel('Magnitude')
    axes[1].set_title('Frequency Domain')
    axes[1].set_xlim(0, fs / 2)
    axes[1].legend()
    axes[1].grid(True, alpha=0.3)

    # Spectrogram
    f_spec, t_spec, Sxx = signal.spectrogram(x, fs, nperseg=256)
    f_spec_f, t_spec_f, Syy = signal.spectrogram(y, fs, nperseg=256)

    axes[2].pcolormesh(t_spec * 1000, f_spec, 10 * np.log10(Sxx + 1e-15),
                        shading='gouraud', cmap='viridis')
    axes[2].set_xlabel('Time (ms)')
    axes[2].set_ylabel('Frequency (Hz)')
    axes[2].set_title('Spectrogram (Original Signal)')

    plt.tight_layout()
    plt.savefig('fir_filtering_demo.png', dpi=150)
    plt.close()

demonstrate_filtering()

11.3 ๋‹ค์ค‘ ๋Œ€์—ญ ํ•„ํ„ฐ ์„ค๊ณ„

# Design a multi-band filter using firwin2
from scipy.signal import firwin2

# Arbitrary magnitude response
numtaps = 201
freq_points = [0, 0.1, 0.15, 0.3, 0.35, 0.5, 0.55, 0.7, 0.75, 1.0]
gain_points = [0, 0, 1, 1, 0, 0, 1, 1, 0, 0]

h_multiband = firwin2(numtaps, freq_points, gain_points)

w, H = freqz(h_multiband, worN=4096)
H_dB = 20 * np.log10(np.abs(H) + 1e-15)

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Desired vs actual
axes[0].plot(np.array(freq_points) / 2, gain_points, 'ro-',
             linewidth=2, label='Desired', markersize=8)
axes[0].plot(w / np.pi / 2, np.abs(H), 'b-', linewidth=1.5, label='Actual')
axes[0].set_xlabel('Normalized Frequency')
axes[0].set_ylabel('Magnitude')
axes[0].set_title('Multi-band Filter: Magnitude')
axes[0].legend()
axes[0].grid(True, alpha=0.3)

axes[1].plot(w / np.pi, H_dB, 'b-', linewidth=1.5)
axes[1].set_xlabel('Normalized Frequency (ร—ฯ€)')
axes[1].set_ylabel('Magnitude (dB)')
axes[1].set_title('Multi-band Filter: dB Scale')
axes[1].set_ylim(-80, 5)
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('multiband_fir.png', dpi=150)
plt.close()

12. ์—ฐ์Šต ๋ฌธ์ œ

์—ฐ์Šต ๋ฌธ์ œ 1: ์ฐฝํ•จ์ˆ˜ ์„ ํƒ

๋‹ค์Œ ์‚ฌ์–‘์œผ๋กœ ์ €์—ญํ†ต๊ณผ FIR ํ•„ํ„ฐ๋ฅผ ์„ค๊ณ„ํ•˜๋ผ: - ํ‘œ๋ณธํ™” ์ฃผํŒŒ์ˆ˜: $f_s = 10000$ Hz - ํ†ต๊ณผ๋Œ€์—ญ ๊ฒฝ๊ณ„: $f_p = 1500$ Hz - ์ €์ง€๋Œ€์—ญ ๊ฒฝ๊ณ„: $f_s = 2000$ Hz - ์ตœ์†Œ ์ €์ง€๋Œ€์—ญ ๊ฐ์‡ : $60$ dB

(a) ์–ด๋–ค ์ฐฝํ•จ์ˆ˜ ์œ ํ˜•์ด ์ €์ง€๋Œ€์—ญ ์‚ฌ์–‘์„ ์ถฉ์กฑํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€?

(b) ๊ฐ ๊ฐ€๋Šฅํ•œ ์ฐฝํ•จ์ˆ˜์— ๋Œ€ํ•ด ํ•„์š”ํ•œ ํ•„ํ„ฐ ์ฐจ์ˆ˜ $M$์„ ๊ณ„์‚ฐํ•˜๋ผ.

(c) scipy.signal.firwin์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•„ํ„ฐ๋ฅผ ์„ค๊ณ„ํ•˜๊ณ  ์‚ฌ์–‘์ด ์ถฉ์กฑ๋˜๋Š”์ง€ ๊ฒ€์ฆํ•˜๋ผ.

(d) ํ†ต๊ณผ๋Œ€์—ญ ๋ฆฌํ”Œ๊ณผ ์ „์ด ํญ ์ธก๋ฉด์—์„œ ๊ฒฐ๊ณผ ํ•„ํ„ฐ๋“ค์„ ๋น„๊ตํ•˜๋ผ.

์—ฐ์Šต ๋ฌธ์ œ 2: ์นด์ด์ € ์ฐฝํ•จ์ˆ˜ ์„ค๊ณ„

๋””์ง€ํ„ธ ํ•„ํ„ฐ๊ฐ€ ๋‹ค์Œ์„ ๋งŒ์กฑํ•ด์•ผ ํ•œ๋‹ค: - ํ†ต๊ณผ๋Œ€์—ญ: $0 \leq f \leq 3$ kHz, ๋ฆฌํ”Œ $\leq 0.1$ dB - ์ €์ง€๋Œ€์—ญ: $f \geq 4$ kHz, ๊ฐ์‡  $\geq 50$ dB - ํ‘œ๋ณธํ™”์œจ: $f_s = 20$ kHz

(a) ๊ฒฝํ—˜์  ๊ณต์‹์„ ์ด์šฉํ•˜์—ฌ ์นด์ด์ € ์ฐฝํ•จ์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ $\beta$๋ฅผ ๊ณ„์‚ฐํ•˜๋ผ.

(b) ์ตœ์†Œ ํ•„ํ„ฐ ์ฐจ์ˆ˜ $M$์„ ๊ณ„์‚ฐํ•˜๋ผ.

(c) ํ•„ํ„ฐ๋ฅผ ์„ค๊ณ„ํ•˜๊ณ  ์ง„ํญ ์‘๋‹ต, ์œ„์ƒ ์‘๋‹ต, ๊ตฐ์ง€์—ฐ์„ ๊ทธ๋ž˜ํ”„๋กœ ๋‚˜ํƒ€๋‚ด๋ผ.

(d) ์‹ค์ œ ํ†ต๊ณผ๋Œ€์—ญ ๋ฆฌํ”Œ๊ณผ ์ €์ง€๋Œ€์—ญ ๊ฐ์‡ ๋ฅผ ์ธก์ •ํ•˜์—ฌ ์„ค๊ณ„๊ฐ€ ์‚ฌ์–‘์„ ์ถฉ์กฑํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•˜๋ผ.

์—ฐ์Šต ๋ฌธ์ œ 3: Parks-McClellan ๋Œ€์—ญํ†ต๊ณผ ํ•„ํ„ฐ

Parks-McClellan ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•˜์—ฌ ๋Œ€์—ญํ†ต๊ณผ FIR ํ•„ํ„ฐ๋ฅผ ์„ค๊ณ„ํ•˜๋ผ: - $f_s = 44100$ Hz (CD ์˜ค๋””์˜ค ๋ ˆ์ดํŠธ) - ์ €์ง€๋Œ€์—ญ 1: $0$์—์„œ $800$ Hz (๊ฐ์‡  $\geq 40$ dB) - ํ†ต๊ณผ๋Œ€์—ญ: $1000$์—์„œ $3000$ Hz (๋ฆฌํ”Œ $\leq 0.5$ dB) - ์ €์ง€๋Œ€์—ญ 2: $3500$์—์„œ $22050$ Hz (๊ฐ์‡  $\geq 40$ dB)

(a) ์–‘์ชฝ ์ €์ง€๋Œ€์—ญ์—์„œ ๋“ฑ๋ฆฌํ”Œ์„ ์œ„ํ•œ ์ ์ ˆํ•œ ๊ฐ€์ค‘์น˜๋ฅผ ์„ ํƒํ•˜๋ผ.

(b) ์ตœ์†Œ ํ•„ํ„ฐ ์ฐจ์ˆ˜๋ฅผ ๊ฒฐ์ •ํ•˜๋ผ.

(c) ์ง„ํญ ์‘๋‹ต์„ ๊ทธ๋ž˜ํ”„๋กœ ๋‚˜ํƒ€๋‚ด๊ณ  ์‚ฌ์–‘์„ ๊ฒ€์ฆํ•˜๋ผ.

(d) ์ฒ˜ํ”„ ์‹ ํ˜ธ(100 Hz์—์„œ 10 kHz๋กœ ์Šค์œ•)์— ํ•„ํ„ฐ๋ฅผ ์ ์šฉํ•˜๊ณ  ํ•„ํ„ฐ๋ง ์ „ํ›„์˜ ์ŠคํŽ™ํŠธ๋กœ๊ทธ๋žจ์„ ํ‘œ์‹œํ•˜๋ผ.

์—ฐ์Šต ๋ฌธ์ œ 4: ์ค‘์ฒฉ ๊ฐ€์‚ฐ๋ฒ• ๊ตฌํ˜„

์ค‘์ฒฉ ๊ฐ€์‚ฐ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋Š” ์‹ค์‹œ๊ฐ„ FIR ํ•„ํ„ฐ ํ”„๋กœ์„ธ์„œ๋ฅผ ๊ตฌํ˜„ํ•˜๋ผ:

(a) ๋ธ”๋ก ๋‹จ์œ„๋กœ ์˜ค๋””์˜ค๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” StreamingFIRFilter ํด๋ž˜์Šค๋ฅผ ์ž‘์„ฑํ•˜๋ผ.

(b) 64, 256, 1024, 4096 ํ‘œ๋ณธ์˜ ๋ธ”๋ก ํฌ๊ธฐ๋กœ ํ…Œ์ŠคํŠธํ•˜๋ผ.

(c) ์ถœ๋ ฅ์ด ์ง์ ‘ ํ•ฉ์„ฑ๊ณฑ (np.convolve)๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•˜๋ผ.

(d) ๋ธ”๋ก ํฌ๊ธฐ์˜ ํ•จ์ˆ˜๋กœ ๋ธ”๋ก๋‹น ์ฒ˜๋ฆฌ ์‹œ๊ฐ„์„ ์ธก์ •ํ•˜๊ณ  ๊ทธ๋ž˜ํ”„๋กœ ๋‚˜ํƒ€๋‚ด๋ผ.

(e) 44100 Hz์—์„œ 10์ดˆ ์˜ค๋””์˜ค ์‹ ํ˜ธ์— 200ํƒญ ํ•„ํ„ฐ๋ฅผ ์ ์šฉํ•  ๋•Œ ์ตœ์  ๋ธ”๋ก ํฌ๊ธฐ๋ฅผ ์ฐพ์•„๋ผ.

์—ฐ์Šต ๋ฌธ์ œ 5: ์„ ํ˜• ์œ„์ƒ ์ œ์•ฝ

(a) ํƒ€์ž… II FIR ํ•„ํ„ฐ (ํ™€์ˆ˜ ์ฐจ์ˆ˜, ๋Œ€์นญ ๊ณ„์ˆ˜)๊ฐ€ ํ•ญ์ƒ $H(e^{j\pi}) = 0$์ž„์„ ์ฆ๋ช…ํ•˜๋ผ. ์™œ ์ด๊ฒƒ์ด ๊ณ ์—ญํ†ต๊ณผ ํ•„ํ„ฐ ์„ค๊ณ„์— ๋ถ€์ ํ•ฉํ•œ๊ฐ€?

(b) ์ฐจ๋‹จ ์ฃผํŒŒ์ˆ˜ $0.6\pi$์—์„œ ๊ณ ์—ญํ†ต๊ณผ FIR ํ•„ํ„ฐ๋ฅผ ๋‹ค์Œ์œผ๋กœ ์„ค๊ณ„ํ•˜๋ผ: - ํƒ€์ž… I (์ง์ˆ˜ ์ฐจ์ˆ˜) - ํƒ€์ž… IV (ํ™€์ˆ˜ ์ฐจ์ˆ˜, ๋ฐ˜๋Œ€์นญ)

๊ฒฐ๊ณผ๋ฅผ ๋น„๊ตํ•˜๋ผ. ์–ด๋А ๊ฒƒ์ด $\omega = \pi$ ๊ทผ์ฒ˜์—์„œ ๋” ์ข‹์€ ์„ฑ๋Šฅ์„ ๋ณด์ด๋Š”๊ฐ€?

(c) ์ฐจ์ˆ˜ 30์˜ ํž๋ฒ„ํŠธ ๋ณ€ํ™˜ ํ•„ํ„ฐ(ํƒ€์ž… III)๋ฅผ ์„ค๊ณ„ํ•˜๋ผ. ์ง„ํญ ๋ฐ ์œ„์ƒ ์‘๋‹ต์„ ๊ทธ๋ž˜ํ”„๋กœ ๋‚˜ํƒ€๋‚ด๋ผ. $\omega = 0$๊ณผ $\omega = \pi$์—์„œ ์–ด๋–ค ์ผ์ด ๋ฐœ์ƒํ•˜๋Š”๊ฐ€?

์—ฐ์Šต ๋ฌธ์ œ 6: ๋น„๊ต ์—ฐ๊ตฌ

์‚ฌ์–‘: $f_s = 16000$ Hz, $f_p = 2000$ Hz, $f_\text{stop} = 2500$ Hz, $A_s = 50$ dB:

(a) (i) ํ•ด๋ฐ ์ฐฝํ•จ์ˆ˜, (ii) ์นด์ด์ € ์ฐฝํ•จ์ˆ˜, (iii) Parks-McClellan์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•„ํ„ฐ๋ฅผ ์„ค๊ณ„ํ•˜๋ผ.

(b) ๊ฐ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ๋‹ค์Œ์„ ๋ณด๊ณ ํ•˜๋ผ: ํ•„ํ„ฐ ์ฐจ์ˆ˜, ํ†ต๊ณผ๋Œ€์—ญ ๋ฆฌํ”Œ (dB), ์‹ค์ œ ์ €์ง€๋Œ€์—ญ ๊ฐ์‡  (dB), ์ „์ด ํญ (Hz).

(c) 4๊ฐœ์˜ ์„œ๋ธŒํ”Œ๋กฏ์ด ํฌํ•จ๋œ ๋‹จ์ผ ๊ทธ๋ฆผ์„ ์ƒ์„ฑํ•˜๋ผ: ์ง„ํญ ์‘๋‹ต ์˜ค๋ฒ„๋ ˆ์ด, ํ†ต๊ณผ๋Œ€์—ญ ์ƒ์„ธ, ์ž„ํŽ„์Šค ์‘๋‹ต, ์˜์ -๊ทน์  ์„ ๋„(Pole-Zero Plot).

(d) ์–ด๋–ค ๋ฐฉ๋ฒ•์ด ๊ฐ€์žฅ ๋‚ฎ์€ ํ•„ํ„ฐ ์ฐจ์ˆ˜์—์„œ ์ตœ์„ ์˜ ๊ฒฐ๊ณผ๋ฅผ ์ œ๊ณตํ•˜๋Š”๊ฐ€? ๋‹ต์„ ์ •๋‹นํ™”ํ•˜๋ผ.

์—ฐ์Šต ๋ฌธ์ œ 7: ์ตœ์†Œ ์œ„์ƒ FIR ํ•„ํ„ฐ

(a) ํ•ด๋ฐ ์ฐฝํ•จ์ˆ˜๋ฒ•์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฐจ์ˆ˜ 40์˜ ์„ ํ˜• ์œ„์ƒ ์ €์—ญํ†ต๊ณผ FIR ํ•„ํ„ฐ๋ฅผ ์„ค๊ณ„ํ•˜๋ผ.

(b) ์ผ‘์ŠคํŠธ๋Ÿผ ๋ฐฉ๋ฒ•(Cepstral Method)์„ ์‚ฌ์šฉํ•˜์—ฌ ์ตœ์†Œ ์œ„์ƒ(Minimum-Phase) FIR ํ•„ํ„ฐ๋กœ ๋ณ€ํ™˜ํ•˜๋ผ: - ์ผ‘์ŠคํŠธ๋Ÿผ $\hat{h}[n] = \text{IFFT}(\log|\text{FFT}(h)|)$ ๊ณ„์‚ฐ - ์ตœ์†Œ ์œ„์ƒ ๋ฒ„์ „ ๊ตฌ์„ฑ

(c) ๋‘ ํ•„ํ„ฐ์˜ ์ง„ํญ ์‘๋‹ต, ์œ„์ƒ ์‘๋‹ต, ๊ตฐ์ง€์—ฐ์„ ๋น„๊ตํ•˜๋ผ.

(d) ์–ธ์ œ ์ตœ์†Œ ์œ„์ƒ์„ ์„ ํ˜• ์œ„์ƒ๋ณด๋‹ค ์„ ํ˜ธํ•˜๋Š”๊ฐ€? ์˜ˆ์‹œ๋ฅผ ๋“ค์–ด ๋…ผ์˜ํ•˜๋ผ.


์ฐธ๊ณ  ๋ฌธํ—Œ

  1. Oppenheim, A. V., & Schafer, R. W. (2010). Discrete-Time Signal Processing (3rd ed.). Pearson. Chapters 7-8.
  2. Proakis, J. G., & Manolakis, D. G. (2007). Digital Signal Processing (4th ed.). Pearson. Chapter 10.
  3. Parks, T. W., & Burrus, C. S. (1987). Digital Filter Design. Wiley.
  4. Kaiser, J. F. (1974). "Nonrecursive Digital Filter Design Using the I0-sinh Window Function." Proceedings of the 1974 IEEE International Symposium on Circuits and Systems.
  5. McClellan, J. H., Parks, T. W., & Rabiner, L. R. (1973). "A Computer Program for Designing Optimum FIR Linear Phase Digital Filters." IEEE Trans. Audio Electroacoustics, 21(6), 506-526.
  6. SciPy Documentation -- scipy.signal module: https://docs.scipy.org/doc/scipy/reference/signal.html

ํƒ์ƒ‰

to navigate between lessons