{"spec_id":"waveform-audio","library":"muix","language":"javascript","code":"// anyplot.ai\n// waveform-audio: Audio Waveform Plot\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 88/100 | Created: 2026-08-24\nimport { LineChart } from \"@mui/x-charts/LineChart\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Simulated spoken utterance: a fundamental tone plus two harmonics, shaped by\n// a handful of overlapping syllable envelopes and a touch of breath noise.\nlet lcgState = 42;\nconst nextRandom = () => {\n  lcgState = (lcgState * 1103515245 + 12345) % 2147483648;\n  return lcgState / 2147483648;\n};\n\nconst sampleRateHz = 22050;\nconst durationSeconds = 2.0;\nconst sampleCount = Math.round(sampleRateHz * durationSeconds);\nconst fundamentalHz = 180;\nconst syllableCenters = [0.22, 0.5, 0.86, 1.18, 1.5, 1.78];\nconst syllableWidth = 0.13;\n\nconst envelopeAt = (time) =>\n  syllableCenters.reduce((sum, center) => {\n    const offset = (time - center) / syllableWidth;\n    return sum + Math.exp(-0.5 * offset * offset);\n  }, 0);\n\nlet peakEnvelope = 0;\nfor (let i = 0; i < sampleCount; i += 1) {\n  peakEnvelope = Math.max(peakEnvelope, envelopeAt(i / sampleRateHz));\n}\n\nconst rawSamples = new Float64Array(sampleCount);\nfor (let i = 0; i < sampleCount; i += 1) {\n  const time = i / sampleRateHz;\n  const envelope = envelopeAt(time) / peakEnvelope;\n  const tone =\n    Math.sin(2 * Math.PI * fundamentalHz * time) +\n    0.5 * Math.sin(2 * Math.PI * fundamentalHz * 2 * time) +\n    0.25 * Math.sin(2 * Math.PI * fundamentalHz * 3 * time);\n  const breath = (nextRandom() - 0.5) * 0.07 * envelope;\n  rawSamples[i] = envelope * (tone / 1.75) + breath;\n}\n\n// Min/max envelope downsampling — the standard DAW technique for rendering\n// tens of thousands of samples without aliasing at a fixed pixel width.\nconst bucketCount = 900;\nconst bucketSize = Math.ceil(sampleCount / bucketCount);\nconst bucketTimes = [];\nconst upperEnvelope = [];\nconst lowerEnvelope = [];\nfor (let start = 0; start < sampleCount; start += bucketSize) {\n  const end = Math.min(start + bucketSize, sampleCount);\n  let bucketMax = -1;\n  let bucketMin = 1;\n  for (let i = start; i < end; i += 1) {\n    const value = rawSamples[i];\n    if (value > bucketMax) bucketMax = value;\n    if (value < bucketMin) bucketMin = value;\n  }\n  bucketTimes.push((start + end) / 2 / sampleRateHz);\n  upperEnvelope.push(bucketMax);\n  lowerEnvelope.push(bucketMin);\n}\n\n// Locate the loudest syllable (tallest envelope bucket) to call it out visually.\nlet peakIndex = 0;\nfor (let i = 1; i < upperEnvelope.length; i += 1) {\n  if (upperEnvelope[i] > upperEnvelope[peakIndex]) peakIndex = i;\n}\nconst peakTime = bucketTimes[peakIndex];\n\nconst title = \"Speech Utterance · waveform-audio · javascript · muix · anyplot.ai\";\nconst titleHeight = 64;\n\n// --- Chart (default-exported component — the harness mounts it) -----------\nexport default function Chart() {\n  return (\n    <div\n      style={{\n        width: window.ANYPLOT_SIZE.width,\n        height: window.ANYPLOT_SIZE.height,\n        display: \"flex\",\n        flexDirection: \"column\",\n      }}\n    >\n      <div\n        style={{\n          height: titleHeight,\n          display: \"flex\",\n          alignItems: \"center\",\n          paddingLeft: 24,\n          fontSize: 22,\n          fontWeight: 500,\n          color: t.ink,\n          fontFamily: \"Roboto, Helvetica, Arial, sans-serif\",\n        }}\n      >\n        {title}\n      </div>\n      <LineChart\n        width={window.ANYPLOT_SIZE.width}\n        height={window.ANYPLOT_SIZE.height - titleHeight}\n        skipAnimation\n        margin={{ top: 20, right: 40, bottom: 60, left: 80 }}\n        grid={{ horizontal: true, vertical: true }}\n        xAxis={[\n          {\n            data: bucketTimes,\n            scaleType: \"linear\",\n            label: \"Time (s)\",\n            valueFormatter: (value) => `${value.toFixed(2)}s`,\n            tickNumber: 10,\n            tickLabelStyle: { fontSize: 14 },\n            labelStyle: { fontSize: 16 },\n          },\n        ]}\n        yAxis={[\n          {\n            min: -1,\n            max: 1,\n            label: \"Amplitude\",\n            tickLabelStyle: { fontSize: 14 },\n            labelStyle: { fontSize: 16 },\n          },\n        ]}\n        series={[\n          {\n            id: \"upper-envelope\",\n            data: upperEnvelope,\n            area: true,\n            showMark: ({ index }) => index === peakIndex,\n            curve: \"linear\",\n            color: t.palette[0],\n            label: \"Amplitude\",\n          },\n          {\n            id: \"lower-envelope\",\n            data: lowerEnvelope,\n            area: true,\n            showMark: false,\n            curve: \"linear\",\n            color: t.palette[0],\n          },\n        ]}\n        slotProps={{ legend: { hidden: true } }}\n        sx={{\n          \"& .MuiAreaElement-root\": { fillOpacity: 0.55 },\n          \"& .MuiLineElement-root\": { strokeWidth: 1.25 },\n          \"& .MuiChartsGrid-horizontalLine\": { stroke: t.grid },\n          \"& .MuiChartsGrid-verticalLine\": { stroke: t.grid, opacity: 0.4 },\n          \"& .MuiMarkElement-root\": {\n            fill: t.palette[0],\n            stroke: t.pageBg,\n            strokeWidth: 2,\n          },\n        }}\n      >\n        <ChartsReferenceLine\n          y={0}\n          lineStyle={{ stroke: t.inkSoft, strokeWidth: 1.5, opacity: 0.6 }}\n        />\n        <ChartsReferenceLine\n          x={peakTime}\n          label=\"Loudest syllable\"\n          labelAlign=\"start\"\n          labelStyle={{ fontSize: 12, fill: t.inkSoft }}\n          lineStyle={{ stroke: t.inkSoft, strokeWidth: 1, strokeDasharray: \"4 4\", opacity: 0.5 }}\n        />\n      </LineChart>\n    </div>\n  );\n}\n"}