{"claude_md":"# Dynamic Audio Element\n\nDemonstrates Web Audio API buffer playback by generating float32 PCM samples in JavaScript and playing them via AudioBufferSourceNode. Plays a 4-note arpeggio (A4, C5, E5, G5) through an AnalyserNode for real-time EQ visualization.\n\n## Quick Reference\n\n# Dynamic Audio Element\n\nGenerates PCM audio samples in JavaScript and plays them via AudioBufferSourceNode.\n\n## Core Concepts\n- Float32 PCM sample generation (sine wave math with fade envelope)\n- `AudioBuffer` creation and population\n- `AudioBufferSourceNode` for sample-accurate playback\n- `AnalyserNode` for real-time EQ bar visualization\n\n## Usage\nAutomatic playback of a 4-note arpeggio (A4, C5, E5, G5). Each note is a freshly generated 450ms audio buffer played sequentially.\n\n## Agent Docs (read when relevant)\n\n| File | When to read |\n|------|-------------|\n| `agent_docs/index.html` | index |\n","agent_docs":{"agent_docs/index.html":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Dynamic Audio Element</title>\n<style>\n* { box-sizing: border-box; margin: 0; padding: 0; }\nbody {\n  background: #0a0a0f;\n  color: #e2e8f0;\n  font-family: system-ui, -apple-system, sans-serif;\n  min-height: 100vh;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  gap: 28px;\n  padding: 24px;\n}\nh1 { font-size: 22px; font-weight: 700; color: #06b6d4; letter-spacing: -0.5px; }\n.subtitle { font-size: 13px; color: #6b7280; }\n.notes-row {\n  display: flex;\n  gap: 14px;\n}\n.note-card {\n  background: #0d1117;\n  border: 1px solid #1e293b;\n  border-radius: 12px;\n  padding: 14px 20px;\n  text-align: center;\n  min-width: 80px;\n  transition: all 0.15s;\n}\n.note-card.active {\n  background: #0c2a33;\n  border-color: #06b6d4;\n  box-shadow: 0 0 20px rgba(6,182,212,0.35);\n}\n.note-card .name {\n  font-size: 20px;\n  font-weight: 700;\n  color: #67e8f9;\n}\n.note-card.active .name { color: #fff; }\n.note-card .hz {\n  font-size: 11px;\n  color: #4b5563;\n  margin-top: 4px;\n}\n.eq-bars {\n  display: flex;\n  gap: 5px;\n  align-items: flex-end;\n  height: 60px;\n}\n.bar {\n  width: 14px;\n  border-radius: 3px 3px 0 0;\n  background: #06b6d4;\n  transition: height 0.08s ease;\n  height: 4px;\n  opacity: 0.5;\n}\n.bar.active { opacity: 1; }\n.status {\n  font-size: 13px;\n  color: #6b7280;\n  height: 18px;\n}\n.status.playing { color: #67e8f9; }\n.status.done { color: #6ee7b7; }\n.tech-row {\n  display: flex;\n  gap: 8px;\n  flex-wrap: wrap;\n  justify-content: center;\n}\n.chip {\n  font-size: 11px;\n  padding: 3px 10px;\n  border-radius: 20px;\n  background: #0d1117;\n  border: 1px solid #1e293b;\n  color: #6b7280;\n}\n</style>\n</head>\n<body>\n<h1>&#128267; Dynamic Audio Element</h1>\n<p class=\"subtitle\">Float32 PCM → AudioBuffer → AudioBufferSourceNode arpeggio</p>\n\n<div class=\"notes-row\" id=\"notesRow\"></div>\n\n<div class=\"eq-bars\" id=\"eqBars\"></div>\n\n<p class=\"status\" id=\"status\">Building PCM audio…</p>\n\n<div class=\"tech-row\">\n  <span class=\"chip\">AudioContext</span>\n  <span class=\"chip\">AudioBuffer</span>\n  <span class=\"chip\">AudioBufferSourceNode</span>\n  <span class=\"chip\">AnalyserNode</span>\n</div>\n\n<script>\nconst NOTES = [\n  { name: 'A4', freq: 440.00 },\n  { name: 'C5', freq: 523.25 },\n  { name: 'E5', freq: 659.26 },\n  { name: 'G5', freq: 783.99 },\n];\n\nconst SAMPLE_RATE = 44100;\nconst DURATION = 0.45; // seconds per note\n\n// Build note cards\nconst notesRow = document.getElementById('notesRow');\nNOTES.forEach((n, i) => {\n  const card = document.createElement('div');\n  card.className = 'note-card';\n  card.id = 'nc-' + i;\n  card.innerHTML = `<div class=\"name\">${n.name}</div><div class=\"hz\">${n.freq} Hz</div>`;\n  notesRow.appendChild(card);\n});\n\n// Build EQ bars\nconst eqBars = document.getElementById('eqBars');\nconst BAR_COUNT = 12;\nconst bars = [];\nfor (let i = 0; i < BAR_COUNT; i++) {\n  const b = document.createElement('div');\n  b.className = 'bar';\n  eqBars.appendChild(b);\n  bars.push(b);\n}\n\nlet analyser, dataArray, eqAnimId = null;\n\nfunction animateEq(active) {\n  if (!active) {\n    if (eqAnimId) cancelAnimationFrame(eqAnimId);\n    bars.forEach(b => { b.style.height = '4px'; b.classList.remove('active'); });\n    return;\n  }\n  bars.forEach(b => b.classList.add('active'));\n  function frame() {\n    if (analyser) {\n      analyser.getByteFrequencyData(dataArray);\n      const step = Math.floor(dataArray.length / BAR_COUNT);\n      bars.forEach((b, i) => {\n        const v = dataArray[i * step] / 255;\n        b.style.height = (4 + v * 52) + 'px';\n      });\n    } else {\n      bars.forEach(b => { b.style.height = (6 + Math.random() * 50) + 'px'; });\n    }\n    eqAnimId = requestAnimationFrame(frame);\n  }\n  frame();\n}\n\nconst statusEl = document.getElementById('status');\n\nasync function playArpeggio() {\n  const AudioCtx = window.AudioContext || window.webkitAudioContext;\n  if (!AudioCtx) {\n    statusEl.textContent = 'Web Audio API not supported in this browser.';\n    return;\n  }\n\n  const audioCtx = new AudioCtx();\n\n  analyser = audioCtx.createAnalyser();\n  analyser.fftSize = 256;\n  dataArray = new Uint8Array(analyser.frequencyBinCount);\n  analyser.connect(audioCtx.destination);\n\n  statusEl.className = 'status playing';\n  statusEl.textContent = 'Playing arpeggio…';\n  animateEq(true);\n\n  const numSamples = Math.floor(SAMPLE_RATE * DURATION);\n  const fadeLen = Math.floor(SAMPLE_RATE * 0.03); // 30ms fade\n\n  for (let i = 0; i < NOTES.length; i++) {\n    const note = NOTES[i];\n\n    // Highlight card\n    document.querySelectorAll('.note-card').forEach(c => c.classList.remove('active'));\n    document.getElementById('nc-' + i).classList.add('active');\n\n    // Generate float32 PCM samples\n    const audioBuffer = audioCtx.createBuffer(1, numSamples, SAMPLE_RATE);\n    const data = audioBuffer.getChannelData(0);\n    for (let j = 0; j < numSamples; j++) {\n      let amp = 0.6;\n      if (j < fadeLen) amp *= j / fadeLen;\n      else if (j > numSamples - fadeLen) amp *= (numSamples - j) / fadeLen;\n      data[j] = amp * Math.sin(2 * Math.PI * note.freq * j / SAMPLE_RATE);\n    }\n\n    // Play via AudioBufferSourceNode → analyser → destination\n    const source = audioCtx.createBufferSource();\n    source.buffer = audioBuffer;\n    source.connect(analyser);\n    source.start();\n\n    await new Promise(r => setTimeout(r, DURATION * 1000 + 80));\n  }\n\n  animateEq(false);\n  document.querySelectorAll('.note-card').forEach(c => c.classList.remove('active'));\n  statusEl.className = 'status done';\n  statusEl.textContent = '✓ Arpeggio complete — A4 C5 E5 G5';\n}\n\nsetTimeout(playArpeggio, 300);\n</script>\n</body>\n</html>"}}