Pada chapter fftfreq dan Spektrum Frekuensi kita membaca spektrum sinyal yang frekuensinya jatuh tepat di sebuah bin, sehingga puncaknya rapi. Kenyataan pengukuran jarang sekali seberes itu: sensor getaran dengan laju sampling 100 Hz yang merekam 256 sampel hanya menjangkau 2,56 detik, sedangkan komponen getaran mesin bisa berada di 10,3 Hz — jumlah periode dalam jendela rekaman tidak bulat.

Akibatnya energi sinus tidak terkumpul di satu bin, melainkan menyebar ke bin-bin tetangganya: puncak melebar dan muncul ekor di sekitarnya. Fenomena ini disebut kebocoran spektral (spectral leakage). Chapter ini menunjukkan asal kebocoran pada sinus terpotong, lalu meredamnya dengan window function — deret penimbang yang memperhalus ujung sinyal sebelum FFT dihitung.

Yang kita kerjakan pada chapter ini:

  • Mengapa sinus yang tidak genap periode dalam jendela bocor ke bin tetangga
  • Membandingkan spektrum tanpa window vs dengan np.hanning secara kuantitatif
  • Mengenal np.hanning, np.hamming, np.blackman, dan np.kaiser beserta peran parameter beta

Mengapa Sinus Terpotong Bocor #

np.fft.fft bekerja di atas satu asumsi diam-diam: N sampel yang diberikan adalah satu periode utuh dari sinyal yang berulang tanpa henti. Bila sinyal di dalam jendela menuntaskan jumlah periode bulat, ujung jendela bertemu dengan mulus saat “dijahit” ke sampel pertama. Bila tidak — 10,3 Hz selama 2,56 detik berarti 26,368 periode — sambungannya melompat, dan FFT membaca lompatan itu sebagai komponen frekuensi lain yang tersebar. Ingat pula pembagian binnya: bin ke-k mewakili frekuensi k × fs/N; pada fs = 100 dan N = 256 jarak antar bin 0,390625 Hz, sementara 10,3 Hz jatuh di sela bin 26 dan 27.

python
import numpy as np

fs, N = 100.0, 256
t = np.arange(N) / fs
sinyal = np.sin(2 * np.pi * 10.3 * t)   # tidak genap periode dalam jendela
mag = np.abs(np.fft.fft(sinyal))
freq = np.fft.fftfreq(N, d=1/fs)
puncak = int(np.argmax(mag))
print("bin puncak        :", puncak, "=", freq[puncak], "Hz")
print("jarak antar bin   :", fs / N, "Hz")
print("mag sekitar puncak:", mag[puncak - 3:puncak + 4].round(1))

Output yang Dihasilkan:

text
bin puncak        : 26 = 10.15625 Hz
jarak antar bin   : 0.390625 Hz
mag sekitar puncak: [ 11.2  15.8  27.3 101.4  58.9  22.8  14.1]

Mari kita telaah baris demi baris:

  1. Baris 5 (sinyal = np.sin(...)): sinus beramplitudo 1 pada 10,3 Hz; bentuk periodanya dari Fungsi Trigonometri. Dalam 2,56 detik sinyal menempuh 26,368 periode — bukan bilangan bulat.
  2. Baris 8 (puncak = int(np.argmax(mag))): np.argmax mencari indeks magnitude terbesar; hasilnya bin 26, bin yang paling dekat dengan 10,3 Hz.
  3. Baris 9–10: frekuensi bin 26 adalah 10,15625 Hz, hasil 26 × 0,390625.

Perhatikan angkanya: pada sinus yang jatuh tepat di bin, magnitude puncaknya 128 (N/2 × amplitudo 1, seperti di Dari DFT ke FFT). Di sini puncak hanya 101,4, dan enam bin tetangganya memuat 11,2 sampai 58,9 — energi sinus tunggal tumpah ke banyak bin. Inilah wajah kebocoran spektral: puncak mengecil sekaligus melebar.

Meredam Bocoran dengan np.hanning #

Window function mengalikan sinyal dengan deret penimbang yang kecil di ujung dan penuh di tengah. Ujung sinyal diredam mendekati nol sehingga “jahitan” periodiknya menjadi mulus; lompatan di tepi jendela hilang, dan energi terkumpul di sekitar puncak utama. np.hanning(N) menghasilkan jendela berbentuk setengah gelombang kosinus — bentuk yang akrab dari Fungsi Trigonometri.

Kita ukur efeknya dengan satu angka: proporsi magnitude di luar pita pengaman puncak — puncak beserta tiga bin tetangganya tiap sisi — pada separuh frekuensi positif.

python
import numpy as np

fs, N = 100.0, 256
t = np.arange(N) / fs
sinyal = np.sin(2 * np.pi * 10.3 * t)
freq = np.fft.fftfreq(N, d=1/fs)
pos = freq > 0
puncak = 26                               # bin puncak dari blok sebelumnya
luar = pos.copy()
luar[puncak - 3:puncak + 4] = False       # buang puncak + 3 tetangga tiap sisi
for nama, jendela in [("persegi", np.ones(N)), ("hanning", np.hanning(N))]:
    mag = np.abs(np.fft.fft(sinyal * jendela))
    rasio = mag[luar].sum() / mag[pos].sum()
    print(f"{nama:>8}: mag puncak {mag[puncak]:.1f} | rasio luar {rasio:.4f}")

Output yang Dihasilkan:

text
 persegi: mag puncak 101.4 | rasio luar 0.4680
 hanning: mag puncak 58.4 | rasio luar 0.0119

Uraian blok di atas:

  1. Baris 7 (pos = freq > 0): analisis dibatasi ke frekuensi positif agar puncak cermin pada frekuensi negatif tidak ikut terhitung.
  2. Baris 9–10 (luar = ...): luar menyalin mask pos, lalu mematikan tujuh bin di sekitar puncak; rasio menjadi tolok ukur bocoran yang adil.
  3. Baris 11–14: jendela "persegi" (semua nilai 1, artinya tanpa window) dibandingkan dengan np.hanning(N) pada sinyal yang sama.

Hasilnya tegas: rasio bocoran turun dari 0,4680 menjadi 0,0119 — sekitar 39 kali lebih kecil. Harganya terlihat di kolom mag puncak: 101,4 menjadi 58,4, karena jendela mengecilkan rata-rata amplitudo.

Hanning, Hamming, Blackman, dan Kaiser #

NumPy menyediakan empat window function yang sering dipakai. Semuanya menerima panjang M dan mengembalikan array float64 berisi penimbang dari 0 sampai 1.

python
import numpy as np

M = 10
print("hanning :", np.hanning(M).round(3))
print("hamming :", np.hamming(M).round(3))
print("blackman:", np.blackman(M).round(3))
print("kaiser  :", np.kaiser(M, 6).round(3))

Output yang Dihasilkan:

text
hanning : [0.    0.117 0.413 0.75  0.97  0.97  0.75  0.413 0.117 0.   ]
hamming : [0.08  0.188 0.46  0.77  0.972 0.972 0.77  0.46  0.188 0.08 ]
blackman: [-0.     0.051  0.258  0.63   0.951  0.951  0.63   0.258  0.051 -0.   ]
kaiser  : [0.015 0.138 0.401 0.732 0.967 0.967 0.732 0.401 0.138 0.015]

Semua jendela menekan ujung, dengan gaya berbeda: np.hanning sampai nol persis, np.hamming berhenti di sekitar 0,08, np.blackman paling dalam, dan np.kaiser(M, beta) bentuknya diatur lewat parameter beta. Nilai -0. pada blackman hanyalah negatif-nol hasil pembulatan, artefak yang sudah kita kenal di Dari DFT ke FFT. Ringkasan karakternya (kualitatif; angka desibel adalah nilai buku teks untuk jendela panjang):

WindowMainlobeSidelobeCatatan pemakaian
persegi (tanpa window)paling sempittertinggi (≈ −13 dB)pembanding awal
np.hanningsedangrendah (≈ −31 dB)pilihan umum serbaguna
np.hammingsedanglebih rendah (≈ −42 dB)sidelobe jauh lebih kecil
np.blackmanlebarsangat rendah (≈ −58 dB)saat sidelobe kritis
np.kaiser(M, beta)diatur betadiatur betafleksibel lewat satu parameter

Tabel itu memperlihatkan pertukaran yang tidak bisa dihindari: jendela yang menekan sidelobe lebih dalam selalu memperlebar mainlobe (puncak utama), sehingga dua sinus berdekatan lebih sulit dipisahkan.

np.kaiser mengemas pertukaran itu ke dalam satu angka: beta = 0 menghasilkan jendela persegi, beta makin besar menekan sidelobe makin dalam dengan mainlobe yang ikut melebar. Kita uji pada sinyal 10,3 Hz yang sama:

python
import numpy as np

fs, N = 100.0, 256
t = np.arange(N) / fs
sinyal = np.sin(2 * np.pi * 10.3 * t)
freq = np.fft.fftfreq(N, d=1/fs)
pos = freq > 0
puncak = 26
luar = pos.copy()
luar[puncak - 3:puncak + 4] = False
for beta in [0, 4, 8, 14]:
    mag = np.abs(np.fft.fft(sinyal * np.kaiser(N, beta)))
    rasio = mag[luar].sum() / mag[pos].sum()
    print(f"kaiser beta={beta:>2}: rasio = {rasio:.4f}")

Output yang Dihasilkan:

text
kaiser beta= 0: rasio = 0.4680
kaiser beta= 4: rasio = 0.1255
kaiser beta= 8: rasio = 0.0031
kaiser beta=14: rasio = 0.0017

Windowing adalah langkah pra-FFT yang murah: satu perkalian array sebelum np.fft.fft dipanggil. Pilih jendela sesuai kebutuhan — mainlobe sempit untuk memisahkan frekuensi berdekatan, sidelobe rendah untuk membaca komponen kecil dekat komponen besar.