{"claude_md":"# Chord Synthesizer\n\nPolyphonic chord synthesizer using Web Audio API. Plays a I→vi→IV→V chord progression (C-Am-F-G) using triangle wave oscillators routed through a BiquadFilter and AnalyserNode. Tests complex multi-node audio graph capture.\n\n## Quick Reference\n\n# Chord Synthesizer\n\nPolyphonic synthesizer demonstrating complex Web Audio API routing.\n\n## Core Concepts\n- Multiple simultaneous `OscillatorNode` voices\n- `BiquadFilter` for tone shaping\n- Gain envelopes for natural attack/release\n- Frequency spectrum visualization\n\n## Usage\nPlays C major → A minor → F major → G major chord progression automatically.\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>Chord Synthesizer</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: 24px;\n  padding: 24px;\n}\nh1 { font-size: 22px; font-weight: 700; color: #818cf8; letter-spacing: -0.5px; }\n.subtitle { font-size: 13px; color: #6b7280; }\n\n.progression {\n  display: flex;\n  gap: 12px;\n}\n.chord-card {\n  background: #0d0d1a;\n  border: 1px solid #1e1e3a;\n  border-radius: 12px;\n  padding: 14px 18px;\n  text-align: center;\n  min-width: 88px;\n  transition: all 0.18s;\n}\n.chord-card.active {\n  background: #1a1a3e;\n  border-color: #818cf8;\n  box-shadow: 0 0 22px rgba(129,140,248,0.4);\n}\n.chord-card .roman { font-size: 11px; color: #4b5563; margin-bottom: 4px; letter-spacing: 1px; }\n.chord-card .name { font-size: 24px; font-weight: 700; color: #a5b4fc; }\n.chord-card.active .name { color: #fff; }\n.chord-card .notes { font-size: 10px; color: #4b5563; margin-top: 5px; line-height: 1.6; }\n.chord-card.active .notes { color: #818cf8; }\n\ncanvas {\n  border: 1px solid #1e1e3a;\n  border-radius: 10px;\n  background: #080812;\n  display: block;\n}\n\n.status { font-size: 13px; color: #6b7280; height: 18px; }\n.status.playing { color: #a5b4fc; }\n.status.done { color: #6ee7b7; }\n</style>\n</head>\n<body>\n<h1>&#9836; Chord Synthesizer</h1>\n<p class=\"subtitle\">I → vi → IV → V progression · Triangle wave · BiquadFilter</p>\n\n<div class=\"progression\" id=\"progression\"></div>\n\n<canvas id=\"spectrum\" width=\"560\" height=\"130\"></canvas>\n\n<p class=\"status\" id=\"status\">Initializing audio graph…</p>\n\n<script>\nconst CHORDS = [\n  { roman: 'I',  name: 'C',  freqs: [261.63, 329.63, 392.00], noteNames: 'C4 · E4 · G4' },\n  { roman: 'vi', name: 'Am', freqs: [220.00, 261.63, 329.63], noteNames: 'A3 · C4 · E4' },\n  { roman: 'IV', name: 'F',  freqs: [174.61, 220.00, 261.63], noteNames: 'F3 · A3 · C4' },\n  { roman: 'V',  name: 'G',  freqs: [196.00, 246.94, 293.66], noteNames: 'G3 · B3 · D4' },\n];\n\nconst CHORD_DURATION = 700; // ms\n\n// Build chord cards\nconst progression = document.getElementById('progression');\nCHORDS.forEach((c, i) => {\n  const card = document.createElement('div');\n  card.className = 'chord-card';\n  card.id = 'chord-' + i;\n  card.innerHTML = `\n    <div class=\"roman\">${c.roman}</div>\n    <div class=\"name\">${c.name}</div>\n    <div class=\"notes\">${c.noteNames}</div>\n  `;\n  progression.appendChild(card);\n});\n\n// Canvas spectrum visualizer\nconst canvas = document.getElementById('spectrum');\nconst ctx2d = canvas.getContext('2d');\nlet analyser, freqData, animId;\n\nfunction drawSpectrum() {\n  animId = requestAnimationFrame(drawSpectrum);\n  if (!analyser) return;\n\n  analyser.getByteFrequencyData(freqData);\n\n  const W = canvas.width, H = canvas.height;\n  ctx2d.clearRect(0, 0, W, H);\n\n  // Draw bars\n  const barCount = 56;\n  const barW = Math.floor(W / barCount) - 2;\n  const step = Math.floor(freqData.length / barCount);\n\n  for (let i = 0; i < barCount; i++) {\n    const value = freqData[i * step] / 255;\n    const barH = Math.max(2, value * H);\n    const hue = 220 + value * 60; // blue → purple\n    const lightness = 40 + value * 30;\n    ctx2d.fillStyle = `hsl(${hue}, 80%, ${lightness}%)`;\n    const x = i * (barW + 2) + 4;\n    ctx2d.beginPath();\n    ctx2d.roundRect(x, H - barH, barW, barH, [3, 3, 0, 0]);\n    ctx2d.fill();\n  }\n}\n\nconst statusEl = document.getElementById('status');\n\nasync function playProgression() {\n  const AudioCtx = window.AudioContext || window.webkitAudioContext;\n  if (!AudioCtx) {\n    statusEl.textContent = 'Web Audio API not supported.';\n    return;\n  }\n\n  const audioCtx = new AudioCtx();\n\n  // Master gain\n  const masterGain = audioCtx.createGain();\n  masterGain.gain.setValueAtTime(0.18, audioCtx.currentTime); // quiet to avoid clipping 3 voices\n\n  // BiquadFilter (lowpass for warmth)\n  const filter = audioCtx.createBiquadFilter();\n  filter.type = 'lowpass';\n  filter.frequency.setValueAtTime(3200, audioCtx.currentTime);\n  filter.Q.setValueAtTime(0.8, audioCtx.currentTime);\n\n  // AnalyserNode for spectrum display\n  analyser = audioCtx.createAnalyser();\n  analyser.fftSize = 512;\n  analyser.smoothingTimeConstant = 0.75;\n  freqData = new Uint8Array(analyser.frequencyBinCount);\n\n  // Routing: masterGain → filter → destination\n  //          masterGain → analyser (parallel tap for visualization)\n  masterGain.connect(filter);\n  filter.connect(audioCtx.destination);\n  masterGain.connect(analyser);\n\n  drawSpectrum();\n\n  statusEl.className = 'status playing';\n  statusEl.textContent = 'Playing I → vi → IV → V…';\n\n  const durationSec = CHORD_DURATION / 1000;\n\n  for (let ci = 0; ci < CHORDS.length; ci++) {\n    const chord = CHORDS[ci];\n\n    // Highlight active chord card\n    document.querySelectorAll('.chord-card').forEach(c => c.classList.remove('active'));\n    document.getElementById('chord-' + ci).classList.add('active');\n\n    const t = audioCtx.currentTime;\n    const voiceGains = [];\n\n    // Create one oscillator per note in the chord\n    chord.freqs.forEach(freq => {\n      const osc = audioCtx.createOscillator();\n      const vGain = audioCtx.createGain();\n\n      osc.type = 'triangle';\n      osc.frequency.setValueAtTime(freq, t);\n\n      // Attack/release envelope\n      vGain.gain.setValueAtTime(0, t);\n      vGain.gain.linearRampToValueAtTime(1.0, t + 0.03);\n      vGain.gain.setValueAtTime(1.0, t + durationSec - 0.08);\n      vGain.gain.linearRampToValueAtTime(0, t + durationSec);\n\n      osc.connect(vGain);\n      vGain.connect(masterGain);\n\n      osc.start(t);\n      osc.stop(t + durationSec + 0.01);\n      voiceGains.push(vGain);\n    });\n\n    await new Promise(r => setTimeout(r, CHORD_DURATION + 80));\n  }\n\n  // Stop visualization after short tail\n  await new Promise(r => setTimeout(r, 400));\n  cancelAnimationFrame(animId);\n\n  // Clear spectrum\n  ctx2d.clearRect(0, 0, canvas.width, canvas.height);\n  document.querySelectorAll('.chord-card').forEach(c => c.classList.remove('active'));\n\n  statusEl.className = 'status done';\n  statusEl.textContent = '✓ Progression complete — C · Am · F · G';\n}\n\nsetTimeout(playProgression, 350);\n</script>\n</body>\n</html>"}}