{"spec_id":"waveform-audio","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// waveform-audio: Audio Waveform Plot\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 93/100 | Created: 2026-08-24\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Simulated spoken utterance: syllable amplitude envelope * multi-harmonic\n// voice carrier, with fixed-seed jitter for a natural, non-synthetic texture.\nlet seed = 42;\nfunction lcgRandom() {\n  seed = (seed * 1664525 + 1013904223) >>> 0;\n  return seed / 4294967296;\n}\n\nconst sampleRate = 8000; // Hz\nconst durationSeconds = 1.5;\nconst sampleCount = Math.round(sampleRate * durationSeconds); // 12,000 samples\n\nconst f0 = 180; // fundamental (voice pitch), Hz\nconst syllables = [\n  { start: 0.05, end: 0.35, peak: 0.85 },\n  { start: 0.42, end: 0.68, peak: 0.65 },\n  { start: 0.78, end: 1.02, peak: 0.95 },\n  { start: 1.12, end: 1.42, peak: 0.55 },\n];\n\nfunction syllableEnvelope(time) {\n  let envelope = 0;\n  for (const syllable of syllables) {\n    if (time >= syllable.start && time <= syllable.end) {\n      const phase = (time - syllable.start) / (syllable.end - syllable.start);\n      const shape = Math.sin(Math.PI * phase) ** 1.5; // smooth rise/fall\n      envelope = Math.max(envelope, shape * syllable.peak);\n    }\n  }\n  return envelope;\n}\n\nconst rawAmplitude = new Float32Array(sampleCount);\nfor (let i = 0; i < sampleCount; i++) {\n  const time = i / sampleRate;\n  const envelope = syllableEnvelope(time);\n  const carrier =\n    Math.sin(2 * Math.PI * f0 * time) +\n    0.5 * Math.sin(2 * Math.PI * f0 * 2 * time) +\n    0.25 * Math.sin(2 * Math.PI * f0 * 3 * time);\n  const jitter = (lcgRandom() - 0.5) * 0.08;\n  rawAmplitude[i] = Math.max(-1, Math.min(1, envelope * (carrier / 1.75 + jitter)));\n}\n\n// --- Min/max envelope downsampling (avoids aliasing at this zoom level) ----\nconst bucketCount = 900;\nconst samplesPerBucket = sampleCount / bucketCount;\nconst envelopeData = [];\nfor (let bucket = 0; bucket < bucketCount; bucket++) {\n  const startIdx = Math.floor(bucket * samplesPerBucket);\n  const endIdx = Math.floor((bucket + 1) * samplesPerBucket);\n  let bucketMin = Infinity;\n  let bucketMax = -Infinity;\n  for (let i = startIdx; i < endIdx; i++) {\n    const value = rawAmplitude[i];\n    if (value < bucketMin) bucketMin = value;\n    if (value > bucketMax) bucketMax = value;\n  }\n  const bucketTime = startIdx / sampleRate;\n  envelopeData.push([bucketTime, bucketMax], [bucketTime, bucketMin]);\n}\n\n// --- Chart -------------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"area\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n  },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: {\n    text: \"waveform-audio · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  xAxis: {\n    title: { text: \"Time (s)\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    labels: {\n      style: { color: t.inkSoft, fontSize: \"14px\" },\n      formatter() {\n        return this.value.toFixed(1);\n      },\n    },\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    gridLineColor: t.grid,\n    min: 0,\n    max: durationSeconds,\n  },\n  yAxis: {\n    title: {\n      text: \"Amplitude (normalized)\",\n      style: { color: t.inkSoft, fontSize: \"16px\" },\n    },\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n    gridLineColor: t.grid,\n    min: -1,\n    max: 1,\n    tickInterval: 0.5,\n    plotLines: [{ value: 0, color: t.inkSoft, width: 1.5, zIndex: 3 }],\n  },\n  legend: { enabled: false },\n  tooltip: {\n    enabled: true,\n    backgroundColor: t.elevatedBg,\n    borderColor: t.inkSoft,\n    style: { color: t.ink, fontSize: \"13px\" },\n    formatter() {\n      return `t=${this.x.toFixed(3)}s, a=${this.y.toFixed(2)}`;\n    },\n  },\n  plotOptions: {\n    series: { animation: false },\n    area: {\n      threshold: 0,\n      lineWidth: 1.5,\n      lineColor: \"#00694A\",\n      fillOpacity: 0.55,\n      marker: { enabled: false },\n      states: { hover: { lineWidthPlus: 0, halo: { size: 0 } } },\n    },\n  },\n  series: [{ name: \"Amplitude\", data: envelopeData, color: t.palette[0] }],\n});\n"}