পাঠ ৩২ · ৫৭-এর মধ্যে · মডিউল ৭
Home / Courses / Digital Signal Processing / FIR ফিল্টার ডিজাইন

FIR ফিল্টার ডিজাইন — ফ্রিকোয়েন্সি স্যাম্পলিং মেথড

FIR filter design — frequency sampling method
১২ মিনিট পড়া উচ্চ · Advanced Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • ফ্রিকোয়েন্সি-স্যাম্পলিং পদ্ধতির মূল ধারণা এবং এটি উইন্ডোয়িং থেকে কীভাবে আলাদা
  • বাস্তব-মানের ফিল্টার পেতে কেন মিরর-সিমেট্রি ও লিনিয়ার-ফেজ ডিলে টার্ম প্রয়োজন
  • ইনভার্স-DFT-সদৃশ সূত্র দিয়ে সরাসরি ফিল্টার ট্যাপ গণনা করা
  • ডিজাইন করা ফিল্টারের প্রকৃত ফ্রিকোয়েন্সি রেসপন্স মূল্যায়ন করে মূল স্পেসিফিকেশনের সাথে সংখ্যাগতভাবে যাচাই করা
  • এই পদ্ধতির একটি বাস্তব সীমাবদ্ধতা — স্পেসিফাইড বিন্দুগুলোর মাঝে রেসপন্সের উপর সরাসরি নিয়ন্ত্রণ না থাকা

১ · ধারণা — ফ্রিকোয়েন্সি থেকে সরাসরি ফিল্টার

L31-এ আমরা টাইম-ডোমেইনে (সিংক ইমপালস রেসপন্স) শুরু করেছিলাম। ফ্রিকোয়েন্সি-স্যাম্পলিং পদ্ধতি উল্টো দিক থেকে শুরু করে — আমরা সরাসরি বলে দিই ফিল্টারটি N-টি নির্দিষ্ট ফ্রিকোয়েন্সি বিন্দুতে (k = 0, 1, ..., N-1, যা fs/N ব্যবধানে সাজানো) ঠিক কী মান দেবে, এবং তারপর M5/L22-এর DFT সূত্রের ইনভার্স ব্যবহার করে সেই স্পেসিফিকেশন থেকে টাইম-ডোমেইন ট্যাপ h[n] বের করি:

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

এখানে H[k] হলো আমাদের স্পেসিফাই করা কাঙ্ক্ষিত (desired) ফ্রিকোয়েন্সি রেসপন্স — একটি জটিল (complex) সংখ্যা প্রতিটি বিন্দুতে। এটি ঠিক M5/L22-এর DFT সূত্রের বিপরীত, তাই "ইনভার্স-DFT-সদৃশ নির্মাণ"।

২ · বাস্তব-মানের ফিল্টার পেতে দুটি শর্ত

সমস্যা হলো — এলোমেলোভাবে H[k] বেছে নিলে h[n] সাধারণত জটিল (complex) সংখ্যা হয়ে যায়, যা একটি বাস্তব সিগন্যালে প্রয়োগযোগ্য নয়। বাস্তব-মানের h[n] পেতে দুটি স্ট্যান্ডার্ড শর্ত লাগে:

মিরর সিমেট্রি
ম্যাগনিটিউড স্পেসিফিকেশনে |H[k]| = |H[N-k]| হতে হবে — M5/L23-এর কনজুগেট-সিমেট্রি বৈশিষ্ট্যের সরাসরি প্রয়োগ।
লিনিয়ার-ফেজ ডিলে
প্রতিটি বিন্দুতে একটি ফেজ টার্ম e^(-jπk(N-1)/N) যোগ করতে হয়, যা ফিল্টারকে কজাল ও সিমেট্রিক করে (L31-এর মতোই ঠিক লিনিয়ার ফেজ দেয়)।

অর্থাৎ চূড়ান্ত স্পেসিফিকেশন: H[k] = |H_desired[k]| · e^(-jπk(N-1)/N), যেখানে |H_desired[k]| মিরর-বিন্দুতে সিমেট্রিক (k এবং N-k-এ সমান)।

সহোদর ধারণার সাথে সম্পর্ক

এই সূত্রটি M5/L22-এর DFT সূত্রের ঠিক বিপরীত দিকের গণনা — সেখানে টাইম-ডোমেইন থেকে ফ্রিকোয়েন্সি-ডোমেইনে যাওয়া হয়েছিল, এখানে উল্টো দিকে। আর M5/L23-এর কনজুগেট-সিমেট্রি বৈশিষ্ট্য (বাস্তব সিগন্যালের DFT-তে X[N-k] = conj(X[k])) এখানে ডিজাইনের একটি প্রয়োজনীয় শর্ত হিসেবে ফিরে আসছে।

৩ · কোড দিয়ে ডিজাইন ও যাচাই

ধরা যাক fs=150 Hz, N=15 (তাই ফ্রিকোয়েন্সি-রেজোলিউশন fs/N=10 Hz)। আমরা একটি লো-পাস-সদৃশ স্পেসিফিকেশন দিচ্ছি — k=0,1,2 (অর্থাৎ 0, 10, 20 Hz) বিন্দুতে গেইন ১ (প্যাসব্যান্ড), আর মিরর বিন্দু k=13,14 (যা negative frequencies −20, −10 Hz-এর সাথে মিলে যায়, M5/L23-এর periodicity বৈশিষ্ট্য অনুযায়ী) একই গেইন ১ — বাকি সব বিন্দুতে গেইন ০ (স্টপব্যান্ড)।

Python
import math, cmath

N = 15    # ফ্রিকোয়েন্সি বিন্দু সংখ্যা = ফিল্টার দৈর্ঘ্য (বিজোড় বাছাই করা হয়েছে)
fs = 150.0
res = fs / N   # ফ্রিকোয়েন্সি রেজোলিউশন = 10 Hz

# ---- (ক) কাঙ্ক্ষিত ম্যাগনিটিউড স্পেসিফিকেশন — মিরর-সিমেট্রিক ----
Hd_mag = [0.0]*N
passband_bins = [0, 1, 2]   # 0, 10, 20 Hz -- প্যাসব্যান্ড
for k in passband_bins:
    Hd_mag[k] = 1.0
    Hd_mag[(N - k) % N] = 1.0   # মিরর বিন্দু

# ---- (খ) লিনিয়ার-ফেজ ডিলে যোগ করে সম্পূর্ণ কমপ্লেক্স স্পেসিফিকেশন ----
H = [Hd_mag[k] * cmath.exp(-1j*math.pi*k*(N-1)/N) for k in range(N)]

# ---- (গ) ইনভার্স-DFT-সদৃশ সূত্র দিয়ে ট্যাপ গণনা ----
h = []
for n in range(N):
    s = sum(H[k]*cmath.exp(2j*math.pi*k*n/N) for k in range(N)) / N
    h.append(s)

max_imag = max(abs(v.imag) for v in h)
print(f"{'n':>3} | {'h[n] (real part)':>16} | {'imag part':>12}")
for n in range(N):
    print(f"{n:>3} | {h[n].real:>16.6f} | {h[n].imag:>12.2e}")
print(f"\nসর্বোচ্চ |imag part| সব ট্যাপ মিলিয়ে: {max_imag:.2e}  (ফ্লোটিং-পয়েন্ট নির্ভুলতার সীমায়, বাস্তবে শূন্য)")

h_real = [v.real for v in h]

# ---- (ঘ) যাচাই — প্রকৃত ফ্রিকোয়েন্সি রেসপন্স মূল্যায়ন ----
def freq_response(h, f_norm):
    Nh = len(h)
    return sum(h[n]*cmath.exp(-2j*math.pi*f_norm*n) for n in range(Nh))

print()
print(f"{'k':>3} | {'freq(Hz)':>9} | {'spec |H|':>9} | {'computed |H|':>13}")
for k in range(N):
    f_hz = k*res if k <= N//2 else (k-N)*res
    resp = freq_response(h_real, k/N)
    print(f"{k:>3} | {f_hz:>9.1f} | {Hd_mag[k]:>9.3f} | {abs(resp):>13.10f}")

    
প্রকৃত আউটপুট নিশ্চিত করে — সব ট্যাপের ইমাজিনারি অংশ 10⁻¹⁶ মাত্রার (ফ্লোটিং-পয়েন্ট নির্ভুলতার সীমা, কার্যত শূন্য), তাই h[n] প্রকৃতপক্ষে বাস্তব-মানের। ট্যাপগুলো সিমেট্রিক (h[0]≈h[14]≈0.058053, h[6]≈h[8]≈0.277690, কেন্দ্রে h[7]=0.333333) — যেমন L31-এ দেখা হয়েছিল, এটাই লিনিয়ার ফেজের প্রমাণ। যাচাই টেবিলে — প্রতিটি বিন্দুতে computed |H| প্রায় হুবহু spec |H|-এর সাথে মিলে যায় (পার্থক্য 10⁻¹⁵ মাত্রার) — k=0,1,2,13,14 (0, ±10, ±20 Hz)-এ ঠিক ১.০, বাকি সব বিন্দুতে ঠিক ০.০ — একটি সত্যিকারের সংখ্যাগত ক্রস-চেক যে ডিজাইন পাইপলাইনটি সঠিকভাবে কাজ করছে।

৪ · একটি সীমাবদ্ধতা — বিন্দুগুলোর মাঝখানে কী ঘটে

স্পেসিফিকেশন শুধু আমরা যে N-টি বিন্দু বেছে নিয়েছি সেখানেই নিয়ন্ত্রণ দেয়। বিন্দুগুলোর মাঝখানে (যেমন k=2.5, অর্থাৎ 25 Hz-এর কাছাকাছি) রেসপন্স কী হবে তা স্পেসিফিকেশনে নেই — ফিল্টারটি স্বয়ংক্রিয়ভাবে একটি মসৃণ ইন্টারপোলেশন তৈরি করে, যা নিচের টেবিলে দেখা যাচ্ছে।

Python
import math, cmath

# এই সেলটি স্বয়ংসম্পূর্ণ -- আগের সেলের ডিজাইন ধাপ পুনরায় চালিয়ে h_real তৈরি করা হচ্ছে
N = 15
fs = 150.0
res = fs / N

Hd_mag = [0.0]*N
for k in [0, 1, 2]:
    Hd_mag[k] = 1.0
    Hd_mag[(N - k) % N] = 1.0

H = [Hd_mag[k] * cmath.exp(-1j*math.pi*k*(N-1)/N) for k in range(N)]
h_real = [ (sum(H[k]*cmath.exp(2j*math.pi*k*n/N) for k in range(N)) / N).real for n in range(N) ]

def freq_response(h, f_norm):
    Nh = len(h)
    return sum(h[n]*cmath.exp(-2j*math.pi*f_norm*n) for n in range(Nh))

# বিন্দুগুলোর মাঝে (k=2.0 থেকে k=4.0-এর মাঝামাঝি) রেসপন্স পরীক্ষা
print(f"{'k (bin)':>8} | {'freq(Hz)':>9} | {'|H|':>10}")
for k_frac in [2.0, 2.5, 3.0, 3.5, 4.0]:
    resp = freq_response(h_real, k_frac/N)
    print(f"{k_frac:>8} | {k_frac*res:>9.1f} | {abs(resp):>10.6f}")

    
প্রকৃত ফলাফল — k=2 (20 Hz, প্যাসব্যান্ডের একদম শেষ, |H|=1.0) থেকে k=3 (30 Hz, স্টপব্যান্ডের শুরু, |H|≈0.0) পর্যন্ত রেসপন্স হঠাৎ পড়ে না — মাঝে k=2.5 (25 Hz)-এ |H|≈0.538 আর k=3.5 (35 Hz)-এ |H|≈0.173 — একটি ওভারশুট-সহ ট্রানজিশন। এটাই ফ্রিকোয়েন্সি-স্যাম্পলিং পদ্ধতির স্বীকৃত সীমাবদ্ধতা: বিন্দুগুলোর মাঝে রেসপন্সের আকৃতির উপর সরাসরি নিয়ন্ত্রণ থাকে না, শুধু বিন্দুগুলোতেই নিশ্চয়তা থাকে। L31-এর উইন্ডোয়িং পদ্ধতি এই দিক থেকে বেশি প্রেডিক্টেবল, কারণ উইন্ডো ফাংশনের আকৃতি পুরো ট্রানজিশন ব্যান্ডের চরিত্র নির্ধারণ করে।
মূল কথা · Key takeaway

ফ্রিকোয়েন্সি-স্যাম্পলিং পদ্ধতি ডিজাইনারকে ফ্রিকোয়েন্সি-ডোমেইনে সরাসরি স্পেসিফিকেশন লেখার সুবিধা দেয় — বিশেষভাবে দরকারি যখন কাঙ্ক্ষিত রেসপন্স একটি সাধারণ লো-পাস/হাই-পাস আকৃতি নয়, বরং অনিয়মিত (arbitrary)। তবে বিন্দুর সংখ্যা বাড়িয়ে (ঘন স্পেসিফিকেশন) বিন্দুর মাঝের অনিয়ন্ত্রিত আচরণ কমানো যায় — এটি একটি সরাসরি ট্রেড-অফ যা L34-এ ফিল্টার অর্ডার নিয়ে আলোচনার সাথে সংযুক্ত।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ কোড সেলে মিরর বিন্দু Hd_mag[(N-k)%N] = 1.0 সেট না করলে কী হতো?

Hd_mag[k] এবং Hd_mag[N-k] সমান না হলে (মিরর সিমেট্রি ভাঙলে), লিনিয়ার-ফেজ টার্ম যোগ করার পরেও ইনভার্স-DFT সূত্র থেকে পাওয়া h[n]-এর ইমাজিনারি অংশ আর ফ্লোটিং-পয়েন্ট নির্ভুলতার সীমার (10⁻¹⁶) কাছাকাছি থাকত না — বরং একটি প্রকৃত, উল্লেখযোগ্য ইমাজিনারি অংশ থেকে যেত, যার মানে ফিল্টারটি বাস্তব সিগন্যালে সরাসরি প্রয়োগযোগ্য থাকত না।

প্র ০২ এই পদ্ধতিতে ফিল্টারের দৈর্ঘ্য (N) আর স্পেসিফিকেশনের বিন্দু সংখ্যা সবসময় সমান কেন?

কারণ ইনভার্স-DFT সূত্রটি নিজেই N-বিন্দু বিশিষ্ট — এটি ঠিক N-টি ইনপুট মান (H[0] থেকে H[N-1]) নিয়ে ঠিক N-টি আউটপুট মান (h[0] থেকে h[N-1]) তৈরি করে — M5/L22-এর DFT-এর মতোই, যেখানে ইনপুট ও আউটপুট দৈর্ঘ্য সবসময় সমান। তাই স্পেসিফিকেশনে যত বেশি বিন্দু, ফিল্টার তত লম্বা (এবং তত বেশি নিয়ন্ত্রণ, কিন্তু তত বেশি কম্পিউটেশন কস্ট — L34-এ বিস্তারিত)।

প্র ০৩ L31-এর উইন্ডোয়িং পদ্ধতি আর এই পাঠের ফ্রিকোয়েন্সি-স্যাম্পলিং পদ্ধতি — কোনটি বেছে নেবেন যদি কাঙ্ক্ষিত রেসপন্স একটি অস্বাভাবিক, বহু-ব্যান্ডের (multi-band) আকৃতি হয় (যেমন কিছু নির্দিষ্ট ফ্রিকোয়েন্সি রাখা, কিছু কমানো, কোনো সাধারণ লো-পাস/হাই-পাস প্যাটার্ন ছাড়াই)?

ফ্রিকোয়েন্সি-স্যাম্পলিং পদ্ধতি এখানে বেশি স্বাভাবিক পছন্দ — কারণ এটি ফ্রিকোয়েন্সি-ডোমেইনে সরাসরি স্পেসিফিকেশন দেয়, তাই যেকোনো জটিল, বহু-ব্যান্ডের আকৃতি বিন্দু-বাই-বিন্দু বসিয়ে দেওয়া যায়। উইন্ডোয়িং পদ্ধতি (L31) মূলত সাধারণ লো-পাস/হাই-পাস/ব্যান্ড-পাস আকৃতির জন্য সরল সূত্র দেয়, কিন্তু অস্বাভাবিক আকৃতির জন্য এর ইমপালস রেসপন্স হাতে বের করা কঠিন হয়ে পড়ে।

অনুশীলন

  1. চিন্তা করুন: উপরের কোডে passband_bins = [0, 1, 2]-কে passband_bins = [0, 1]-এ পরিবর্তন করলে (প্যাসব্যান্ড সংকুচিত করলে), যাচাই টেবিলে k=2 (20 Hz)-এর spec |H| মান কী হবে বলে আপনার ধারণা?

    k=2 আর প্যাসব্যান্ড তালিকায় নেই, তাই Hd_mag[2] তার ডিফল্ট মান 0.0-ই থাকবে — অর্থাৎ 20 Hz এখন স্টপব্যান্ডের অংশ, এবং যাচাই টেবিলেও computed |H| প্রায় ০ দেখাবে।

  2. পরীক্ষা করুন: কোড সেলে passband_bins = [0, 1, 2]-কে passband_bins = [0, 1]-এ পরিবর্তন করে Run চেপে আপনার হিসেব যাচাই করুন।

    রান করলে k=2 ও k=13 সারিতে spec |H| = 0.000 এবং computed |H|-ও প্রায় ০ (১০⁻¹⁵ মাত্রার) দেখাবে — নিশ্চিত করে যে স্পেসিফিকেশন বদলালে ডিজাইন পাইপলাইন সঠিকভাবে নতুন ফিল্টার তৈরি করে এবং যাচাই টেবিল এখনও মিলে যায়।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
FIR ফিল্টার — লিনিয়ার ফেজ ও উইন্ডোয়িং দিয়ে ডিজাইন