{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "audio-visualizer",
  "type": "registry:component",
  "title": "Audio Visualizer",
  "description": "An interactive 3D real time audio visualizer.",
  "dependencies": [
    "@react-three/fiber",
    "@react-three/drei",
    "three",
    "simplex-noise"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/chatcn/3d/audio-visualizer.tsx",
      "content": "\"use client\";\nimport React, {\n  useRef,\n  useEffect,\n  useState,\n  createContext,\n  useContext,\n} from \"react\";\nimport type { ReactNode } from \"react\";\nimport * as THREE from \"three\";\nimport { Canvas, useFrame } from \"@react-three/fiber\";\nimport { OrbitControls, Grid, Environment, Loader } from \"@react-three/drei\";\nimport {\n  Mic01Icon,\n  MicOff01Icon,\n  Cancel01Icon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { createNoise3D } from \"simplex-noise\";\n\ninterface AudioContextValue {\n  running: boolean;\n  analyser: AnalyserNode | null;\n  data: Uint8Array;\n}\n\nconst AudioCtx = createContext<AudioContextValue | null>(null);\n\nexport const useAudio = () => {\n  const ctx = useContext(AudioCtx);\n  if (!ctx) throw new Error(\"useAudio must be used inside AudioProvider\");\n  return ctx;\n};\n\ninterface AudioProviderProps {\n  running: boolean;\n  children: ReactNode;\n}\n\nexport function AudioProvider({ running, children }: AudioProviderProps) {\n  const [analyser, setAnalyser] = useState<AnalyserNode | null>(null);\n  const [data] = useState<Uint8Array>(new Uint8Array(256));\n  const ctxRef = useRef<AudioContext | null>(null);\n  const streamRef = useRef<MediaStream | null>(null);\n\n  useEffect(() => {\n    if (!running) {\n      if (streamRef.current) {\n        streamRef.current.getTracks().forEach((t) => t.stop());\n        streamRef.current = null;\n      }\n      if (ctxRef.current) {\n        ctxRef.current.close();\n        ctxRef.current = null;\n      }\n      setAnalyser(null);\n      return;\n    }\n\n    const start = async () => {\n      try {\n        const stream = await navigator.mediaDevices.getUserMedia({\n          audio: true,\n        });\n        const c = new AudioContext();\n        const src = c.createMediaStreamSource(stream);\n        const an = c.createAnalyser();\n        an.fftSize = 512;\n        an.smoothingTimeConstant = 0.8;\n        src.connect(an);\n        setAnalyser(an);\n        ctxRef.current = c;\n        streamRef.current = stream;\n      } catch (e) {\n        console.error(\"Microphone access denied\", e);\n      }\n    };\n    start();\n  }, [running]);\n\n  return (\n    <AudioCtx.Provider value={{ running, analyser, data }}>\n      {children}\n    </AudioCtx.Provider>\n  );\n}\n\ninterface AudioVisualizerSphereProps {\n  color?: string;\n}\n\nconst noise3D = createNoise3D();\n\nexport function AudioVisualizerSphere({\n  color = \"#0a0a0a\",\n}: AudioVisualizerSphereProps) {\n  const { running, analyser, data } = useAudio();\n  const ref = useRef<THREE.Mesh>(null!);\n  const baseDirs = useRef<THREE.Vector3[]>([]);\n  const bassRef = useRef(0);\n  const treRef = useRef(0);\n\n  useEffect(() => {\n    if (ref.current && baseDirs.current.length === 0) {\n      const pos = (ref.current.geometry as THREE.SphereGeometry).attributes\n        .position;\n      for (let i = 0; i < pos.count; i++) {\n        const v = new THREE.Vector3(\n          pos.getX(i),\n          pos.getY(i),\n          pos.getZ(i)\n        ).normalize();\n        baseDirs.current.push(v);\n      }\n    }\n  }, []);\n\n  useFrame(() => {\n    const mesh = ref.current;\n    if (!mesh || baseDirs.current.length === 0) return;\n    const geo = mesh.geometry as THREE.SphereGeometry;\n    const pos = geo.attributes.position;\n    const t = performance.now();\n\n    if (!running || !analyser) {\n      bassRef.current = THREE.MathUtils.lerp(bassRef.current, 0, 0.15);\n      treRef.current = THREE.MathUtils.lerp(treRef.current, 0, 0.15);\n    } else {\n      analyser.getByteFrequencyData(data as unknown as Uint8Array<ArrayBuffer>);\n      const half = data.length / 2;\n      const lower = data.slice(0, half);\n      const upper = data.slice(half);\n      const bass = Math.max(...lower) / 256;\n      const treble = upper.reduce((a, b) => a + b, 0) / (upper.length * 256);\n      bassRef.current = THREE.MathUtils.lerp(bassRef.current, bass * 4, 0.08);\n      treRef.current = THREE.MathUtils.lerp(treRef.current, treble * 1.2, 0.08);\n    }\n\n    for (let i = 0; i < pos.count; i++) {\n      const dir = baseDirs.current[i];\n      const blobNoise = noise3D(\n        dir.x * 1.5 + t * 0.0002,\n        dir.y * 1.5,\n        dir.z * 1.5\n      );\n      const audioNoise = noise3D(\n        dir.x + t * 0.001,\n        dir.y + t * 0.001,\n        dir.z + t * 0.001\n      );\n      const d =\n        5 + blobNoise * 0.6 + bassRef.current + audioNoise * 7 * treRef.current;\n      pos.setXYZ(i, dir.x * d, dir.y * d, dir.z * d);\n    }\n\n    pos.needsUpdate = true;\n    geo.computeVertexNormals();\n  });\n\n  return (\n    <mesh ref={ref} position={[0, 10, 0]}>\n      <sphereGeometry args={[5, 128, 128]} />\n      <meshPhysicalMaterial\n        color={color}\n        metalness={0.7}\n        roughness={0.2}\n        clearcoat={0.5}\n        clearcoatRoughness={0.3}\n        reflectivity={0.8}\n      />\n    </mesh>\n  );\n}\n\ninterface AudioVisualizerGridProps\n  extends Partial<React.ComponentProps<typeof Grid>> {\n  enabled?: boolean;\n}\n\nexport function AudioVisualizerGrid({\n  enabled = true,\n  ...props\n}: AudioVisualizerGridProps) {\n  if (!enabled) return null;\n  return (\n    <Grid\n      args={[200, 200]}\n      cellSize={2}\n      cellThickness={1}\n      cellColor=\"#6f6f6f\"\n      sectionSize={8}\n      sectionThickness={2}\n      sectionColor=\"#ffffff\"\n      infiniteGrid={false}\n      position={[0, 0.01, 0]}\n      {...props}\n    />\n  );\n}\nexport function AudioVisualizerFloor() {\n  return (\n    <mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -5.5, 0]}>\n      <planeGeometry args={[200, 200]} />\n      <meshStandardMaterial color=\"#000000\" metalness={0.5} />\n    </mesh>\n  );\n}\n\ninterface ControlsProps {\n  rec: boolean;\n  toggle: () => void;\n  cancel: () => void;\n}\n\nconst Controls: React.FC<ControlsProps> = ({ rec, toggle, cancel }) => (\n  <div className=\"absolute bottom-4 left-1/2 -translate-x-1/2\">\n    <div className=\"flex flex-row items-center justify-center gap-12\">\n      <button\n        onClick={toggle}\n        className={`p-4 rounded-full shadow-2xl transition-all duration-300 ${\n          rec ? \"bg-red-600 hover:bg-red-700\" : \"bg-white hover:bg-gray-100\"\n        }`}\n      >\n        {rec ? (\n          <HugeiconsIcon icon={MicOff01Icon} className=\"w-6 h-6 text-white\" />\n        ) : (\n          <HugeiconsIcon icon={Mic01Icon} className=\"w-6 h-6 text-black\" />\n        )}\n      </button>\n\n      <button\n        onClick={cancel}\n        disabled={!rec}\n        className={`p-4 rounded-full shadow-2xl transition-all duration-300 ${\n          rec ? \"bg-white hover:bg-gray-100\" : \"bg-gray-300 cursor-not-allowed\"\n        }`}\n      >\n        <HugeiconsIcon icon={Cancel01Icon} className=\"w-6 h-6 text-black\" />\n      </button>\n    </div>\n  </div>\n);\n\ninterface AudioVisualizerExperienceProps {\n  background?: string;\n  sphereColor?: string;\n  showGrid?: boolean;\n  orbitProps?: Partial<React.ComponentProps<typeof OrbitControls>>;\n  envProps?: Partial<React.ComponentProps<typeof Environment>>;\n  gridProps?: Partial<React.ComponentProps<typeof Grid>>;\n}\n\nexport function AudioVisualizerExperience({\n  background = \"#000000\",\n  sphereColor = \"#0a0a0a\",\n  showGrid = true,\n  orbitProps = {},\n  envProps = {},\n  gridProps = {},\n}: AudioVisualizerExperienceProps) {\n  const [rec, setRec] = useState(false);\n\n  return (\n    <div\n      className=\"relative w-full h-[30rem] overflow-hidden\"\n      style={{ backgroundColor: background }}\n    >\n      <Canvas\n        camera={{ position: [0, 15, 60], fov: 50 }}\n        dpr={[1, 1.5]}\n        gl={{ antialias: true, toneMapping: THREE.ACESFilmicToneMapping }}\n      >\n        <AudioProvider running={rec}>\n          <AudioVisualizerSphere color={sphereColor} />\n          <AudioVisualizerFloor />\n          <AudioVisualizerGrid enabled={showGrid} {...gridProps} />\n          <ambientLight intensity={0.2} />\n          <directionalLight position={[10, 20, 10]} intensity={0.8} />\n          <Environment {...envProps} />\n        </AudioProvider>\n\n        <OrbitControls {...orbitProps} />\n      </Canvas>\n      <Loader />\n      <Controls\n        rec={rec}\n        toggle={() => setRec(!rec)}\n        cancel={() => setRec(false)}\n      />\n    </div>\n  );\n}\n",
      "type": "registry:component"
    }
  ]
}