{"spec_id":"spectrogram-mel","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nspectrogram-mel: Mel-Spectrogram for Audio Analysis\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-06-03\n\"\"\"\n\nimport os\nimport sys\n\n\n# This file is named matplotlib.py — remove its directory from sys.path so\n# \"import matplotlib\" resolves to the installed package, not this file.\n_here = os.path.abspath(os.path.dirname(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p or \".\") != _here]\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom matplotlib.colors import LinearSegmentedColormap, Normalize\nfrom matplotlib.ticker import FuncFormatter\n\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\n\n# Theme-adaptive chrome tokens — Imprint palette\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint sequential colormap — reversed so high energy (signal) maps to brand green,\n# low energy (noise floor) maps to blue; makes signal features stand out clearly.\nimprint_seq = LinearSegmentedColormap.from_list(\"imprint_seq\", [\"#4467A3\", \"#009E73\"])\n\n# --- Data: synthesize a rich audio signal with melody-like frequency components ---\nnp.random.seed(42)\nsample_rate = 22050\nduration = 4.0\nn_samples = int(sample_rate * duration)\nt = np.linspace(0, duration, n_samples, endpoint=False)\n\n# Melody: frequency sweeps and harmonics simulating speech-like signal\nmelody_freq = 220 + 80 * np.sin(2 * np.pi * 0.5 * t)\naudio_signal = 0.5 * np.sin(2 * np.pi * melody_freq * t)\naudio_signal += 0.3 * np.sin(2 * np.pi * 2 * melody_freq * t)\naudio_signal += 0.15 * np.sin(2 * np.pi * 3 * melody_freq * t)\n\n# Percussive bursts at regular intervals\nfor onset in np.arange(0.0, duration, 0.5):\n    onset_idx = int(onset * sample_rate)\n    burst_len = int(0.05 * sample_rate)\n    end_idx = min(onset_idx + burst_len, n_samples)\n    envelope = np.exp(-np.linspace(0, 8, end_idx - onset_idx))\n    audio_signal[onset_idx:end_idx] += 0.4 * envelope * np.random.randn(end_idx - onset_idx)\n\n# Rising tone in the second half\nrising_freq = np.linspace(500, 2000, n_samples)\nrising_mask = np.zeros(n_samples)\nrising_mask[n_samples // 2 :] = np.linspace(0, 0.3, n_samples - n_samples // 2)\naudio_signal += rising_mask * np.sin(2 * np.pi * rising_freq * t)\n\naudio_signal = audio_signal / np.max(np.abs(audio_signal))\n\n# STFT (n_fft=2048, hop_length=512, n_mels=128 per spec)\nn_fft = 2048\nhop_length = 512\nn_mels = 128\n\nn_frames = 1 + (n_samples - n_fft) // hop_length\nwindow = np.hanning(n_fft)\nstft_matrix = np.zeros((n_fft // 2 + 1, n_frames))\nfor i in range(n_frames):\n    start = i * hop_length\n    frame = audio_signal[start : start + n_fft] * window\n    spectrum = np.fft.rfft(frame)\n    stft_matrix[:, i] = np.abs(spectrum) ** 2\n\n# Mel filter bank (vectorized)\nf_min = 0.0\nf_max = sample_rate / 2.0\nmel_min = 2595.0 * np.log10(1.0 + f_min / 700.0)\nmel_max = 2595.0 * np.log10(1.0 + f_max / 700.0)\nmel_points = np.linspace(mel_min, mel_max, n_mels + 2)\nhz_points = 700.0 * (10.0 ** (mel_points / 2595.0) - 1.0)\nfft_freqs = np.fft.rfftfreq(n_fft, 1.0 / sample_rate)\n\nmel_filterbank = np.zeros((n_mels, len(fft_freqs)))\nfor m in range(n_mels):\n    f_left, f_center, f_right = hz_points[m], hz_points[m + 1], hz_points[m + 2]\n    up_slope = np.where(\n        (fft_freqs >= f_left) & (fft_freqs <= f_center) & (f_center > f_left),\n        (fft_freqs - f_left) / (f_center - f_left),\n        0.0,\n    )\n    down_slope = np.where(\n        (fft_freqs > f_center) & (fft_freqs <= f_right) & (f_right > f_center),\n        (f_right - fft_freqs) / (f_right - f_center),\n        0.0,\n    )\n    mel_filterbank[m] = up_slope + down_slope\n\n# Apply mel filter bank and convert to dB scale\nmel_spectrogram = mel_filterbank @ stft_matrix\nmel_spectrogram = np.maximum(mel_spectrogram, 1e-10)\nmel_spectrogram_db = 10.0 * np.log10(mel_spectrogram)\nmel_spectrogram_db -= mel_spectrogram_db.max()\n\ntime_axis = np.arange(n_frames) * hop_length / sample_rate\nmel_freqs = hz_points[1:-1]\n\n# --- Plot ---\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\nimg = ax.pcolormesh(\n    time_axis,\n    np.arange(n_mels),\n    mel_spectrogram_db,\n    cmap=imprint_seq,\n    shading=\"gouraud\",\n    norm=Normalize(vmin=-80, vmax=0),\n    rasterized=True,\n)\n\n# Y-axis: Hz labels at key mel band edges\ntick_hz_values = [64, 128, 256, 512, 1024, 2048, 4096, 8000]\ntick_mel_indices = []\ntick_labels = []\nfor hz in tick_hz_values:\n    if hz <= f_max:\n        idx = np.argmin(np.abs(mel_freqs - hz))\n        tick_mel_indices.append(idx)\n        tick_labels.append(f\"{hz // 1000}k\" if hz >= 1000 else str(hz))\nax.set_yticks(tick_mel_indices)\nax.set_yticklabels(tick_labels)\n\nax.xaxis.set_major_formatter(FuncFormatter(lambda x, _: f\"{x:.1f}\"))\n\n# Colorbar with dB scale\ncbar = fig.colorbar(img, ax=ax, pad=0.02, aspect=30)\ncbar.set_label(\"Power (dB)\", fontsize=10, color=INK_SOFT)\ncbar.set_ticks([0, -20, -40, -60, -80])\ncbar.set_ticklabels([\"0\", \"−20\", \"−40\", \"−60\", \"−80\"])\ncbar.ax.tick_params(labelsize=8, colors=INK_SOFT)\ncbar.outline.set_edgecolor(INK_SOFT)\ncbar.outline.set_linewidth(0.5)\n\n# Reference lines with text labels — more visible than before, guides the viewer\nspeech_idx = np.argmin(np.abs(mel_freqs - 300))\nharmonic_idx = np.argmin(np.abs(mel_freqs - 1000))\nax.axhline(y=speech_idx, color=INK_SOFT, alpha=0.45, linewidth=0.9, linestyle=\"--\")\nax.axhline(y=harmonic_idx, color=INK_SOFT, alpha=0.45, linewidth=0.9, linestyle=\"--\")\nax.text(\n    time_axis[-1] * 0.02,\n    speech_idx + 1.5,\n    \"speech band\",\n    fontsize=9,\n    color=INK_SOFT,\n    va=\"bottom\",\n    ha=\"left\",\n    bbox={\"facecolor\": ELEVATED_BG, \"edgecolor\": \"none\", \"alpha\": 0.7, \"pad\": 2},\n)\nax.text(\n    time_axis[-1] * 0.02,\n    harmonic_idx + 1.5,\n    \"harmonic region\",\n    fontsize=9,\n    color=INK_SOFT,\n    va=\"bottom\",\n    ha=\"left\",\n    bbox={\"facecolor\": ELEVATED_BG, \"edgecolor\": \"none\", \"alpha\": 0.7, \"pad\": 2},\n)\n\n# Chrome — theme-adaptive\ntitle = \"spectrogram-mel · python · matplotlib · anyplot.ai\"\nax.set_title(title, fontsize=12, fontweight=\"medium\", color=INK, pad=8)\nax.set_xlabel(\"Time (s)\", fontsize=10, color=INK, labelpad=6)\nax.set_ylabel(\"Frequency (Hz)\", fontsize=10, color=INK, labelpad=6)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT, labelcolor=INK_SOFT)\nax.tick_params(axis=\"x\", which=\"both\", length=3, width=0.6, color=INK_SOFT)\nax.tick_params(axis=\"y\", which=\"both\", length=3, width=0.6, color=INK_SOFT)\n\nfor spine in ax.spines.values():\n    spine.set_visible(False)\n\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\nplt.close()\n"}