{"spec_id":"spectrogram-mel","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nspectrogram-mel: Mel-Spectrogram for Audio Analysis\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-06-03\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\n\n\n# Theme-adaptive chrome — Imprint palette\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\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\"\n\n# Imprint sequential colormap for single-polarity continuous data (dB magnitude)\nimprint_seq = [[0.0, \"#009E73\"], [1.0, \"#4467A3\"]]\n\n# Reference line color — theme-adaptive subtle overlay\nREF_COLOR = \"rgba(74,74,68,0.35)\" if THEME == \"light\" else \"rgba(184,183,176,0.35)\"\n\n# Data: synthesize audio 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\nsignal = (\n    0.5 * np.sin(2 * np.pi * 440 * t)\n    + 0.3 * np.sin(2 * np.pi * 880 * t)\n    + 0.2 * np.sin(2 * np.pi * 1320 * t)\n    + 0.4 * np.sin(2 * np.pi * (200 + 600 * t / duration) * t)\n    + 0.15 * np.sin(2 * np.pi * 3000 * t) * np.exp(-t / 2)\n    + 0.1 * np.random.randn(n_samples)\n)\n# Amplitude envelope: short fade-in, extended fade-out\nenvelope = np.ones(n_samples)\nenvelope[: int(0.05 * sample_rate)] = np.linspace(0, 1, int(0.05 * sample_rate))\nenvelope[-int(0.3 * sample_rate) :] = np.linspace(1, 0, int(0.3 * sample_rate))\nsignal *= envelope\n\n# STFT computation (manual, no librosa dependency)\nn_fft = 2048\nhop_length = 512\nwindow = np.hanning(n_fft)\nn_frames = 1 + (n_samples - n_fft) // hop_length\nstft_matrix = np.zeros((n_fft // 2 + 1, n_frames))\nfor i in range(n_frames):\n    start = i * hop_length\n    frame = signal[start : start + n_fft] * window\n    stft_matrix[:, i] = np.abs(np.fft.rfft(frame)) ** 2\n\n# Mel filterbank construction\nn_mels = 128\nf_min, f_max = 0.0, 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)\nfreq_bins = np.floor((n_fft + 1) * hz_points / sample_rate).astype(int)\n\nfilterbank = np.zeros((n_mels, n_fft // 2 + 1))\nfor m in range(1, n_mels + 1):\n    f_left, f_center, f_right = freq_bins[m - 1], freq_bins[m], freq_bins[m + 1]\n    for k in range(f_left, f_center):\n        if f_center != f_left:\n            filterbank[m - 1, k] = (k - f_left) / (f_center - f_left)\n    for k in range(f_center, f_right):\n        if f_right != f_center:\n            filterbank[m - 1, k] = (f_right - k) / (f_right - f_center)\n\n# Mel spectrogram in dB (normalised to 0 dB peak)\nmel_spec = filterbank @ stft_matrix\nmel_spec = np.maximum(mel_spec, 1e-10)\nmel_spec_db = 10.0 * np.log10(mel_spec) - 10.0 * np.log10(mel_spec.max())\n\n# Time and frequency axes\ntime_axis = np.arange(n_frames) * hop_length / sample_rate\nmel_freq_points = np.linspace(mel_min, mel_max, n_mels)\nmel_freqs = 700.0 * (10.0 ** (mel_freq_points / 2595.0) - 1.0)\n\n# Plot\nfig = go.Figure(\n    data=go.Heatmap(\n        z=mel_spec_db,\n        x=time_axis,\n        y=mel_freqs,\n        colorscale=imprint_seq,\n        colorbar={\n            \"title\": {\"text\": \"dB\", \"font\": {\"size\": 12, \"color\": INK}},\n            \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n            \"thickness\": 16,\n            \"len\": 0.85,\n            \"bgcolor\": ELEVATED_BG,\n            \"bordercolor\": INK_SOFT,\n            \"borderwidth\": 1,\n        },\n        zmin=-80,\n        zmax=0,\n        hovertemplate=\"Time: %{x:.2f}s<br>Freq: %{y:.0f} Hz<br>Power: %{z:.1f} dB<extra></extra>\",\n    )\n)\n\nfig.update_layout(\n    autosize=False,\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK},\n    title={\n        \"text\": \"spectrogram-mel · python · plotly · anyplot.ai\", \"font\": {\"size\": 16, \"color\": INK}, \"x\": 0.5, \"xanchor\": \"center\"\n    },\n    xaxis={\n        \"title\": {\"text\": \"Time (s)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"showgrid\": False,\n        \"linecolor\": INK_SOFT,\n        \"zerolinecolor\": INK_SOFT,\n    },\n    yaxis={\n        \"title\": {\"text\": \"Frequency (Hz)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"type\": \"log\",\n        \"tickvals\": [50, 100, 200, 500, 1000, 2000, 4000, 8000],\n        \"ticktext\": [\"50\", \"100\", \"200\", \"500\", \"1k\", \"2k\", \"4k\", \"8k\"],\n        \"showgrid\": False,\n        \"linecolor\": INK_SOFT,\n        \"zerolinecolor\": INK_SOFT,\n        \"range\": [np.log10(mel_freqs[1]), np.log10(mel_freqs[-1])],\n    },\n    margin={\"l\": 80, \"r\": 40, \"t\": 80, \"b\": 60},\n    hoverlabel={\"bgcolor\": ELEVATED_BG, \"font_size\": 10, \"font_family\": \"monospace\", \"font_color\": INK, \"bordercolor\": INK_SOFT},\n)\n\n# Subtle reference lines at perceptually meaningful frequency bands\nfor freq in [440, 1000, 4000]:\n    fig.add_shape(\n        type=\"line\",\n        x0=time_axis[0],\n        x1=time_axis[-1],\n        y0=freq,\n        y1=freq,\n        line={\"color\": REF_COLOR, \"width\": 1, \"dash\": \"dot\"},\n    )\n\n# Annotations guiding viewer through key spectral features\nfor ann in [\n    {\"x\": 0.5, \"y\": np.log10(440), \"text\": \"Harmonics (A4)\", \"ax\": -80, \"ay\": -45},\n    {\"x\": 2.2, \"y\": np.log10(400), \"text\": \"Chirp sweep\", \"ax\": 70, \"ay\": 40},\n    {\"x\": 0.6, \"y\": np.log10(3000), \"text\": \"Decaying tone\", \"ax\": 70, \"ay\": -30},\n    {\"x\": 0.3, \"y\": np.log10(100), \"text\": \"Noise floor\", \"ax\": -65, \"ay\": -30},\n]:\n    fig.add_annotation(\n        x=ann[\"x\"],\n        y=ann[\"y\"],\n        yref=\"y\",\n        text=ann[\"text\"],\n        showarrow=True,\n        arrowhead=2,\n        arrowsize=1.2,\n        arrowwidth=1.5,\n        arrowcolor=INK_SOFT,\n        ax=ann[\"ax\"],\n        ay=ann[\"ay\"],\n        font={\"size\": 11, \"color\": INK, \"family\": \"Arial\"},\n        bordercolor=INK_SOFT,\n        borderwidth=1,\n        borderpad=4,\n        bgcolor=ELEVATED_BG,\n        opacity=0.9,\n    )\n\n# Save — canvas: 800×450 × scale=4 → 3200×1800 px (landscape)\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}