{"spec_id":"waveform-audio","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nwaveform-audio: Audio Waveform Plot\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-06-03\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom matplotlib.patches import Patch\n\n\n# Theme tokens — Imprint palette + theme-adaptive chrome\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nBRAND = \"#009E73\"  # Imprint palette position 1 — ALWAYS first series\n\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.15,\n        \"grid.linewidth\": 0.8,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Data\nnp.random.seed(42)\nsample_rate = 22050\nduration = 1.0\nnum_samples = int(sample_rate * duration)\ntime = np.linspace(0, duration, num_samples)\n\nbase_freq = 220\nsegments = [\n    np.linspace(0, 1, int(num_samples * 0.05)),\n    np.ones(int(num_samples * 0.15)),\n    np.exp(-3 * np.linspace(0, 1, int(num_samples * 0.3))),\n    np.linspace(0.05, 0.8, int(num_samples * 0.1)),\n    np.ones(int(num_samples * 0.1)),\n    np.exp(-2 * np.linspace(0, 1, int(num_samples * 0.3))),\n]\namplitude_envelope = np.concatenate(segments)\nif len(amplitude_envelope) < num_samples:\n    amplitude_envelope = np.pad(\n        amplitude_envelope, (0, num_samples - len(amplitude_envelope)), constant_values=amplitude_envelope[-1]\n    )\namplitude_envelope = amplitude_envelope[:num_samples]\n\nsignal = (\n    0.6 * np.sin(2 * np.pi * base_freq * time)\n    + 0.25 * np.sin(2 * np.pi * base_freq * 2 * time)\n    + 0.1 * np.sin(2 * np.pi * base_freq * 3 * time)\n    + 0.05 * np.sin(2 * np.pi * base_freq * 5 * time)\n)\nsignal *= amplitude_envelope\nsignal += np.random.normal(0, 0.01, num_samples)\nsignal = np.clip(signal, -1.0, 1.0)\n\n# Bin samples for seaborn's percentile-band rendering\n# Each chunk covers ~3.6 ms; seaborn computes min-to-max range at each bin natively\nchunk_size = 80\nnum_chunks = num_samples // chunk_size\ntime_chunked = time[: num_chunks * chunk_size].reshape(num_chunks, chunk_size)\nsignal_chunked = signal[: num_chunks * chunk_size].reshape(num_chunks, chunk_size)\n\nenv_time = time_chunked.mean(axis=1)\nenv_max = signal_chunked.max(axis=1)\nenv_min = signal_chunked.min(axis=1)\n\n# Classify bins as Loud vs Quiet via smoothed RMS\nkernel = np.ones(5) / 5\nenv_max_smooth = np.convolve(env_max, kernel, mode=\"same\")\nenv_min_smooth = np.convolve(env_min, kernel, mode=\"same\")\nsmooth_kernel = np.ones(15) / 15\nrms = np.sqrt(np.convolve((env_max_smooth - env_min_smooth) ** 2, smooth_kernel, mode=\"same\"))\nrms_threshold = np.median(rms) * 1.1\nsegment_label = np.where(rms > rms_threshold, \"Loud\", \"Quiet\")\n\n# Long-form DataFrame: every sample labeled with its time-bin center\n# seaborn lineplot errorbar=('pi', 100) => 0th–100th percentile = envelope min/max\ndf_long = pd.DataFrame({\"Time (s)\": np.repeat(env_time, chunk_size), \"Amplitude\": signal_chunked.flatten()})\n\n# Plot\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400)\n\n# Primary waveform: seaborn-native percentile band renders the full amplitude range at each\n# time bin — mean line sits near zero (oscillating signal) with the envelope as fill width\nsns.lineplot(\n    data=df_long,\n    x=\"Time (s)\",\n    y=\"Amplitude\",\n    color=BRAND,\n    errorbar=(\"pi\", 100),\n    linewidth=0.8,\n    err_kws={\"alpha\": 0.4},\n    ax=ax,\n)\n\n# Quiet regions: secondary matplotlib overlay with muted tone to distinguish dynamics\nquiet_mask = segment_label == \"Quiet\"\nquiet_sections = np.ma.clump_unmasked(np.ma.masked_where(~quiet_mask, quiet_mask))\nfor sl in quiet_sections:\n    start = max(0, sl.start - 1)\n    stop = min(len(env_time), sl.stop + 1)\n    ax.fill_between(\n        env_time[start:stop],\n        env_max[start:stop],\n        env_min[start:stop],\n        color=INK_MUTED,\n        alpha=0.35,\n        linewidth=0,\n        zorder=3,\n    )\n\n# Zero-line reference\nax.axhline(y=0, color=INK_SOFT, linewidth=0.8, alpha=0.5, zorder=2)\n\n# Annotations for musical dynamics — kept as storytelling (reviewed strength)\nax.annotate(\n    \"Attack + Sustain\", xy=(0.10, 0.78), fontsize=8, color=INK_SOFT, fontstyle=\"italic\", ha=\"center\", va=\"bottom\"\n)\nax.annotate(\"Decay\", xy=(0.38, 0.22), fontsize=8, color=INK_MUTED, fontstyle=\"italic\", ha=\"center\", va=\"bottom\")\nax.annotate(\"Second Phrase\", xy=(0.72, 0.67), fontsize=8, color=INK_SOFT, fontstyle=\"italic\", ha=\"center\", va=\"bottom\")\n\n# Legend\nlegend_elements = [\n    Patch(facecolor=BRAND, alpha=0.5, label=\"Loud\"),\n    Patch(facecolor=INK_MUTED, alpha=0.35, label=\"Quiet\"),\n]\nax.legend(\n    handles=legend_elements, loc=\"upper right\", fontsize=8, framealpha=0.85, facecolor=ELEVATED_BG, edgecolor=INK_SOFT\n)\n\n# Style\ntitle = \"waveform-audio · python · seaborn · anyplot.ai\"\nax.set_xlabel(\"Time (s)\", fontsize=10, color=INK)\nax.set_ylabel(\"Amplitude\", fontsize=10, color=INK)\nax.set_title(title, fontsize=12, fontweight=\"medium\", color=INK)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\nax.set_ylim(-1.05, 1.05)\nax.set_xlim(0, duration)\nsns.despine(ax=ax)\n\n# Save — no bbox_inches='tight': figsize×dpi yields exact 3200×1800 px canvas\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}