{"spec_id":"waveform-audio","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nwaveform-audio: Audio Waveform Plot\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-06-03\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent self-import: remove this script's own directory from sys.path so that\n# \"import plotly\" resolves to the installed package, not this file.\n_here = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p) != _here]\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# Imprint palette — first series is always #009E73\nBRAND = \"#009E73\"\nANYPLOT_AMBER = \"#DDCC77\"  # warning / decay semantic role\n\n# Data\nnp.random.seed(42)\nsample_rate = 22050\nduration = 1.5\nnum_samples = int(sample_rate * duration)\ntime = np.linspace(0, duration, num_samples)\n\n# Synthesize audio: fundamental tone + harmonics with amplitude envelope\nfundamental_freq = 220\nsignal = (\n    0.6 * np.sin(2 * np.pi * fundamental_freq * time)\n    + 0.25 * np.sin(2 * np.pi * fundamental_freq * 2 * time)\n    + 0.1 * np.sin(2 * np.pi * fundamental_freq * 3 * time)\n    + 0.05 * np.sin(2 * np.pi * fundamental_freq * 5 * time)\n)\n\n# Amplitude envelope: attack-sustain-decay shape\nenvelope = np.ones_like(time)\nattack_end = int(0.05 * num_samples)\ndecay_start = int(0.7 * num_samples)\nenvelope[:attack_end] = np.linspace(0, 1, attack_end)\nenvelope[decay_start:] = np.linspace(1, 0.15, num_samples - decay_start)\n\n# Slight tremolo and noise for realism\ntremolo = 1.0 + 0.08 * np.sin(2 * np.pi * 5.5 * time)\namplitude = signal * envelope * tremolo\namplitude += np.random.normal(0, 0.02, num_samples)\namplitude = np.clip(amplitude, -1.0, 1.0)\n\n# Downsample with larger blocks for cleaner sustain region visibility\nblock_size = 40\nnum_blocks = num_samples // block_size\ntime_blocks = np.array([(time[i * block_size] + time[(i + 1) * block_size - 1]) / 2 for i in range(num_blocks)])\namp_max = np.array([amplitude[i * block_size : (i + 1) * block_size].max() for i in range(num_blocks)])\namp_min = np.array([amplitude[i * block_size : (i + 1) * block_size].min() for i in range(num_blocks)])\n\n# Data-derived inner envelope: 70th percentile of absolute amplitude per block\n# Captures the RMS-like energy content rather than an arbitrary fixed scaling factor\namp_p70 = np.array(\n    [np.percentile(np.abs(amplitude[i * block_size : (i + 1) * block_size]), 70) for i in range(num_blocks)]\n)\n\n# Phase boundaries\nattack_time = duration * 0.05\ndecay_time = duration * 0.7\n\n# Plot\nfig = go.Figure()\n\n# Outer waveform envelope (Imprint brand green fill)\nfig.add_trace(\n    go.Scatter(\n        x=np.concatenate([time_blocks, time_blocks[::-1]]),\n        y=np.concatenate([amp_max, amp_min[::-1]]),\n        fill=\"toself\",\n        fillcolor=\"rgba(0,158,115,0.18)\",\n        line={\"color\": \"rgba(0,158,115,0.5)\", \"width\": 0.5, \"shape\": \"spline\"},\n        name=\"Waveform\",\n        hovertemplate=\"Time: %{x:.3f}s<br>Amplitude: %{y:.3f}<extra></extra>\",\n    )\n)\n\n# Inner envelope: data-derived 70th-percentile energy bounds for visual depth\nfig.add_trace(\n    go.Scatter(\n        x=np.concatenate([time_blocks, time_blocks[::-1]]),\n        y=np.concatenate([amp_p70, -amp_p70[::-1]]),\n        fill=\"toself\",\n        fillcolor=\"rgba(0,158,115,0.32)\",\n        line={\"width\": 0, \"shape\": \"spline\"},\n        showlegend=False,\n        hoverinfo=\"skip\",\n    )\n)\n\n# Peak envelope lines for crisp edge definition\nfig.add_trace(\n    go.Scatter(\n        x=time_blocks,\n        y=amp_max,\n        mode=\"lines\",\n        line={\"color\": BRAND, \"width\": 1.2, \"shape\": \"spline\"},\n        showlegend=False,\n        hoverinfo=\"skip\",\n    )\n)\nfig.add_trace(\n    go.Scatter(\n        x=time_blocks,\n        y=amp_min,\n        mode=\"lines\",\n        line={\"color\": BRAND, \"width\": 1.2, \"shape\": \"spline\"},\n        showlegend=False,\n        hoverinfo=\"skip\",\n    )\n)\n\n# Zero reference line\nfig.add_hline(y=0, line_dash=\"solid\", line_color=GRID, line_width=1)\n\n# Phase region shading (subtle — semantic color cues, not data)\nfig.add_vrect(x0=0, x1=attack_time, fillcolor=\"rgba(0,158,115,0.06)\", line_width=0)\nfig.add_vrect(x0=decay_time, x1=duration, fillcolor=\"rgba(221,204,119,0.08)\", line_width=0)\n\n# Phase boundary markers\nfor t_boundary in [attack_time, decay_time]:\n    fig.add_vline(x=t_boundary, line_dash=\"dot\", line_color=INK_MUTED, line_width=0.8)\n\n# Phase annotations — semantic color coding: green=start, muted=stable, amber=fade\nfig.add_annotation(\n    x=attack_time / 2,\n    y=1.04,\n    text=\"ATTACK\",\n    showarrow=False,\n    font={\"size\": 11, \"color\": BRAND, \"family\": \"Arial Black, sans-serif\"},\n    yref=\"y\",\n)\nfig.add_annotation(\n    x=(attack_time + decay_time) / 2,\n    y=1.04,\n    text=\"SUSTAIN\",\n    showarrow=False,\n    font={\"size\": 11, \"color\": INK_MUTED, \"family\": \"Arial Black, sans-serif\"},\n    yref=\"y\",\n)\nfig.add_annotation(\n    x=(decay_time + duration) / 2,\n    y=1.04,\n    text=\"DECAY\",\n    showarrow=False,\n    font={\"size\": 11, \"color\": ANYPLOT_AMBER, \"family\": \"Arial Black, sans-serif\"},\n    yref=\"y\",\n)\n\n# Layout with canonical canvas, theme-adaptive chrome, and correct font sizes\nfig.update_layout(\n    autosize=False,\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK},\n    title={\n        \"text\": \"waveform-audio · python · plotly · anyplot.ai\",\n        \"font\": {\"size\": 16, \"color\": INK, \"family\": \"Arial Black, sans-serif\"},\n        \"x\": 0.02,\n        \"xanchor\": \"left\",\n        \"y\": 0.97,\n        \"yanchor\": \"top\",\n    },\n    xaxis={\n        \"title\": {\"text\": \"Time (seconds)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"showgrid\": False,\n        \"zeroline\": False,\n        \"linecolor\": INK_SOFT,\n        \"linewidth\": 1,\n        \"ticks\": \"outside\",\n        \"tickcolor\": INK_SOFT,\n        \"ticklen\": 5,\n    },\n    yaxis={\n        \"title\": {\"text\": \"Amplitude\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"range\": [-1.12, 1.12],\n        \"showgrid\": True,\n        \"gridcolor\": GRID,\n        \"gridwidth\": 1,\n        \"zeroline\": False,\n        \"linecolor\": INK_SOFT,\n        \"linewidth\": 1,\n        \"ticks\": \"outside\",\n        \"tickcolor\": INK_SOFT,\n        \"ticklen\": 5,\n        \"dtick\": 0.5,\n    },\n    showlegend=False,\n    margin={\"l\": 80, \"r\": 40, \"t\": 80, \"b\": 60},\n)\n\n# Save — canonical plotly landscape: width=800, height=450, scale=4 → 3200×1800 px\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}