{"spec_id":"waveform-audio","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nwaveform-audio: Audio Waveform Plot\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 87/100 | Updated: 2026-06-03\n\"\"\"\n\nimport importlib.util\nimport os\nimport sys\n\nimport numpy as np\n\n\n# Prevent this file (pygal.py) from shadowing the installed pygal package\n_pygal_spec = importlib.util.find_spec(\"pygal\")\nif _pygal_spec and _pygal_spec.origin != __file__:\n    import pygal\n    from pygal.style import Style\nelse:\n    _cwd = sys.path.pop(0)\n    import pygal\n    from pygal.style import Style\n\n    sys.path.insert(0, _cwd)\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Series colors — Imprint palette position 1 for waveform; position 2 for envelope\n# (lower envelope duplicates position 2 for visual continuity); semantic red for peak peak;\n# neutral muted for zero reference line\nCHART_COLORS = (\n    \"#009E73\",  # Waveform — Imprint position 1\n    \"#C475FD\",  # Decay envelope upper — Imprint position 2\n    \"#C475FD\",  # Decay envelope lower — matches upper for visual continuity\n    \"#AE3030\",  # Peak transient — semantic red (maximum amplitude marker)\n    INK_MUTED,  # Zero reference line — neutral chrome\n)\n\n# Data - synthesized A3 note (220 Hz) with harmonics and decay envelope\nnp.random.seed(42)\nsample_rate = 44100\nduration = 0.15\nt = np.linspace(0, duration, int(sample_rate * duration))\n\nfundamental = 220\nattack_time = 0.005\nenvelope = np.exp(-2.0 * t / duration) * (1.0 - np.exp(-t / attack_time))\nsignal = (\n    np.sin(2 * np.pi * fundamental * t)\n    + 0.5 * np.sin(2 * np.pi * 2 * fundamental * t)\n    + 0.25 * np.sin(2 * np.pi * 3 * fundamental * t)\n    + 0.12 * np.sin(2 * np.pi * 5 * fundamental * t)\n)\nsignal = signal / np.max(np.abs(signal))\namplitude = signal * envelope\namplitude = amplitude / np.max(np.abs(amplitude)) * 0.92\n\n# Downsample for pygal SVG rendering — enough points for smooth waveform shape\nn_points = 1200\nindices = np.linspace(0, len(t) - 1, n_points, dtype=int)\nt_down = t[indices]\namp_down = amplitude[indices]\nenv_down = envelope[indices] / np.max(np.abs(envelope)) * 0.92\n\n# Per-point dict format for rich interactive tooltips — pygal-distinctive feature\nwaveform_data = [\n    {\n        \"value\": (round(float(t_down[i]), 5), round(float(amp_down[i]), 4)),\n        \"label\": f\"t={float(t_down[i]):.4f}s  amp={float(amp_down[i]):.3f}\",\n    }\n    for i in range(n_points)\n]\n\nenvelope_upper = [\n    {\n        \"value\": (round(float(t_down[i]), 5), round(float(env_down[i]), 4)),\n        \"label\": f\"envelope: +{float(env_down[i]):.3f}\",\n    }\n    for i in range(n_points)\n]\nenvelope_lower = [\n    {\n        \"value\": (round(float(t_down[i]), 5), round(float(-env_down[i]), 4)),\n        \"label\": f\"envelope: {float(-env_down[i]):.3f}\",\n    }\n    for i in range(n_points)\n]\n\n# Peak amplitude marker — highlights the attack transient\npeak_idx = int(np.argmax(np.abs(amp_down)))\npeak_marker = [\n    {\n        \"value\": (round(float(t_down[peak_idx]), 5), round(float(amp_down[peak_idx]), 4)),\n        \"label\": f\"Peak: {float(amp_down[peak_idx]):.3f} at {float(t_down[peak_idx]):.4f}s\",\n    }\n]\n\n# Zero reference line\nzero_line = [\n    {\"value\": (0.0, 0.0), \"label\": \"zero baseline\"},\n    {\"value\": (round(float(t_down[-1]), 5), 0.0), \"label\": \"zero baseline\"},\n]\n\n# Title — 44 chars, under 67-char baseline, so default font size applies\ntitle = \"waveform-audio · python · pygal · anyplot.ai\"\n\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=CHART_COLORS,\n    title_font_size=66,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    value_font_size=36,\n    stroke_width=2.5,\n    opacity=0.65,\n    opacity_hover=0.95,\n    transition=\"200ms ease-in\",\n)\n\nx_labels = [round(i * 0.02, 2) for i in range(8)]\n\nchart = pygal.XY(\n    width=3200,\n    height=1800,\n    style=custom_style,\n    title=title,\n    x_title=\"Time (s)\",\n    y_title=\"Amplitude\",\n    show_dots=False,\n    fill=True,\n    stroke_style={\"width\": 2.5},\n    show_legend=True,\n    legend_at_bottom=True,\n    legend_box_size=36,\n    range=(-1.0, 1.0),\n    show_x_guides=False,\n    show_y_guides=True,\n    x_labels=x_labels,\n    x_labels_major_every=1,\n    x_value_formatter=lambda x: f\"{x:.2f}\",\n    value_formatter=lambda x: f\"{x:.3f}\",\n    print_values=False,\n    margin_top=80,\n    margin_bottom=100,\n    margin_left=100,\n    margin_right=60,\n    spacing=25,\n    show_minor_x_labels=False,\n    explicit_size=True,\n    js=[],\n    interpolate=\"cubic\",\n)\n\nchart.add(\"Waveform\", waveform_data)\nchart.add(\n    \"Decay envelope\",\n    envelope_upper,\n    stroke_style={\"width\": 4.5, \"dasharray\": \"12,6\", \"linecap\": \"round\"},\n    show_dots=False,\n    fill=False,\n)\nchart.add(\n    None,\n    envelope_lower,\n    stroke_style={\"width\": 4.5, \"dasharray\": \"12,6\", \"linecap\": \"round\"},\n    show_dots=False,\n    fill=False,\n)\n# Peak transient — larger dot (18) for visibility against dense waveform\nchart.add(\"Peak transient\", peak_marker, stroke_style={\"width\": 0}, dots_size=18, show_dots=True, fill=False)\nchart.add(\n    None, zero_line, stroke_style={\"width\": 3.0, \"dasharray\": \"10,5\", \"linecap\": \"round\"}, show_dots=False, fill=False\n)\n\n# Save\nchart.render_to_png(f\"plot-{THEME}.png\")\n\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}