codexfrequency-modulation-waveform-and-instantaneous-frequency.py
frequency-modulation-waveform-and-instantaneous-frequency.py
"""Illustrate a specified FM phase; emit the matching PNG in the current directory.
Tested with Python 3.14, NumPy 2.3.5 and Matplotlib 3.10.7.
Run from _media with MPLBACKEND=Agg and a writable MPLCONFIGDIR.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def main():
t = np.linspace(0.0, 2.0, 10001)
fc, fm, deviation = 20.0, 1.0, 5.0
beta = deviation / fm
phase = 2 * np.pi * fc * t + beta * np.sin(2 * np.pi * fm * t)
waveform = np.cos(phase)
instantaneous_frequency = fc + deviation * np.cos(2 * np.pi * fm * t)
plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
"axes.spines.right": False})
fig, axes = plt.subplots(2, 1, figsize=(9.8, 6.4), dpi=110,
sharex=True, layout="constrained",
gridspec_kw={"height_ratios": [1.45, 1]})
fig.set_facecolor("white")
fig.suptitle("Frequency modulation: constant amplitude, changing timing", fontsize=15)
axes[0].set_title(r"Original model: $f_c=20$ Hz, $f_m=1$ Hz, $\Delta f=5$ Hz, $\beta=5$",
fontsize=11)
for ax in axes:
ax.set_facecolor("white")
ax.set_xlim(0, 2)
ax.set_xticks(np.arange(0, 2.01, 0.25))
ax.grid(axis="x", color="#e4e7eb", linewidth=0.8)
for center in (0.0, 1.0, 2.0):
ax.axvspan(max(0, center - 0.12), min(2, center + 0.12),
color="#e2f0fa", zorder=0)
for center in (0.5, 1.5):
ax.axvspan(center - 0.12, center + 0.12, color="#fff0db", zorder=0)
axes[0].plot(t, waveform, color="#205a8c", lw=1.25)
for bound in (-1, 1):
axes[0].axhline(bound, color="#59646e", ls="--", lw=1)
axes[0].set_ylim(-1.45, 1.55)
axes[0].set_yticks([-1, 0, 1])
axes[0].set_ylabel(r"Real waveform $s(t)/A$")
axes[0].text(0.04, 1.15, r"Envelope bounds: $\pm A$", color="#434b53")
axes[0].text(0.78, 1.15, "closer cycles", color="#205a8c")
axes[0].text(1.29, 1.15, "wider cycles", color="#91520e")
axes[1].plot(t, instantaneous_frequency, color="#217865", lw=2.1)
axes[1].axhline(fc, color="#59646e", ls="--", lw=1)
axes[1].set_ylim(13.5, 27.7)
axes[1].set_yticks([15, 20, 25])
axes[1].set_ylabel(r"$f_i(t)=\phi'(t)/(2\pi)$ [Hz]")
axes[1].set_xlabel("Time [s]")
axes[1].text(0.57, 26.1, r"$f_i(t)=20+5\cos(2\pi t)$ Hz", color="#217865")
fig.savefig(Path(__file__).stem + ".png", facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()