codexrectangular-hann-window-comparison.py
rectangular-hann-window-comparison.py
"""Compare exact finite-window coefficients and DTFTs (Python 3.14).
Uses the repository's NumPy/Matplotlib dependencies. Writes an opaque PNG to
cwd so Make can generate it directly in the mirrored media directory.
"""
import numpy as np
import matplotlib.pyplot as plt
N = 64
n = np.arange(N)
rectangular = np.ones(N)
hann = 0.5 - 0.5 * np.cos(2 * np.pi * n / N)
bin_offset = np.linspace(0, 8, 6401)
phasors = np.exp(-2j * np.pi * np.outer(bin_offset / N, n))
fig, axes = plt.subplots(1, 2, figsize=(9, 3.8), layout="constrained")
fig.patch.set_facecolor("white")
colors = ["#1565a8", "#ce6519"]
for window, label, color in zip(
[rectangular, hann], ["Rectangular", "Periodic Hann"], colors
):
axes[0].plot(n, window, color=color, lw=2, label=label)
response = np.abs(phasors @ window) / np.sum(window)
db = 20 * np.log10(np.maximum(response, 1e-5))
axes[1].plot(bin_offset, db, color=color, lw=1.6, label=label)
axes[0].set(title="Time weights: 64 samples", xlabel="Sample index n", ylabel="Window coefficient")
axes[0].set_xlim(0, N - 1)
axes[0].set_ylim(-0.04, 1.08)
axes[0].legend(loc="lower center", framealpha=1)
axes[1].set(title="Peak-normalized spectral response", xlabel="Frequency offset (DFT bins)", ylabel="Magnitude (dB)")
axes[1].set_xlim(0, 8)
axes[1].set_ylim(-85, 3)
axes[1].set_xticks(range(9))
axes[1].axvline(1, color=colors[0], ls=":", lw=1, alpha=0.8)
axes[1].axvline(2, color=colors[1], ls=":", lw=1, alpha=0.8)
axes[1].legend(loc="upper right", framealpha=1)
for ax in axes:
ax.set_facecolor("white")
ax.grid(alpha=0.2)
ax.tick_params(labelsize=9)
fig.suptitle("Rectangular versus Hann: sidelobe suppression widens the main lobe", fontsize=12)
fig.savefig("rectangular-hann-window-comparison.png", dpi=120, facecolor="white", transparent=False)
plt.close(fig)