{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "core-piano-roll-visualizer",
  "type": "registry:ui",
  "title": "Piano Roll Visualizer",
  "description": "Controlled falling notes and a horizontal MIDI river with bounded indexed note windows and fading releases.",
  "dependencies": [
    "maruaudio@>=0.0.3 <0.1.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/ui/piano-roll-visualizer.tsx",
      "content": "/**\n * Source: https://maru.audio/ui-kit/piano-roll-visualizer\n * Copyright © [Maru](https://maru.audio)\n *\n * Maru UI Kit — https://maru.audio/ui-kit\n *\n * Installed from the Maru UI registry. This file is yours now: edit it\n * freely, it will not be overwritten unless you re-run the add command.\n *\n * Keep this item’s companion files and CSS imports together. Dependencies\n * declared by the registry are installed with the source.\n */\nimport { useEffect, useRef, type CSSProperties } from \"react\"\nimport {\n  createPianoRollVisualizer,\n  type PianoRollVisualizerFrame,\n} from \"maruaudio/ui/shared/piano-roll-visualizer-renderer\"\n\nexport interface PianoRollVisualizerProps extends PianoRollVisualizerFrame {\n  readonly width?: number | string\n  readonly height?: number | string\n  readonly className?: string\n  readonly style?: CSSProperties\n  readonly \"aria-label\"?: string\n}\n/** Immersive read-only roll. Notes and absolute playback time stay caller-owned. */\nexport function PianoRollVisualizer(props: PianoRollVisualizerProps) {\n  const canvas = useRef<HTMLCanvasElement>(null)\n  const latest = useRef(props)\n  latest.current = props\n  const redraw = useRef<(() => void) | null>(null)\n  useEffect(() => {\n    const target = canvas.current\n    if (!target) return\n    const renderer = createPianoRollVisualizer(target)\n    const paint = () => {\n      if (document.hidden) return\n      const box = target.getBoundingClientRect()\n      if (!box.width || !box.height) return\n      const ratio = Math.min(\n        2,\n        window.devicePixelRatio || 1,\n        Math.sqrt(1_000_000 / (box.width * box.height))\n      )\n      const width = Math.max(1, Math.floor(box.width * ratio))\n      const height = Math.max(1, Math.floor(box.height * ratio))\n      if (target.width !== width || target.height !== height)\n        renderer.resize(width, height)\n      const theme = getComputedStyle(target)\n      const color = (key: string, fallback: string) =>\n        theme.getPropertyValue(key).trim() || fallback\n      const p = latest.current\n      renderer.draw({\n        ...p,\n        background: p.background ?? color(\"--uikit-surface\", \"#14161d\"),\n        ink: p.ink ?? color(\"--uikit-text\", \"#e8eaf0\"),\n        accent: p.accent ?? color(\"--uikit-accent\", \"#6366f1\"),\n        muted: p.muted ?? color(\"--uikit-text-muted\", \"#747b8c\"),\n      })\n    }\n    redraw.current = paint\n    const observer = new ResizeObserver(paint)\n    observer.observe(target)\n    document.addEventListener(\"visibilitychange\", paint)\n    paint()\n    return () => {\n      observer.disconnect()\n      document.removeEventListener(\"visibilitychange\", paint)\n      redraw.current = null\n      renderer.dispose()\n    }\n  }, [])\n  useEffect(() => {\n    redraw.current?.()\n  })\n  return (\n    <canvas\n      ref={canvas}\n      className={props.className}\n      role=\"img\"\n      aria-label={\n        props[\"aria-label\"] ??\n        (props.orientation === \"horizontal\"\n          ? \"Scrolling MIDI piano roll\"\n          : \"Falling MIDI piano roll\")\n      }\n      style={{\n        display: \"block\",\n        width: props.width ?? \"100%\",\n        height: props.height ?? 360,\n        ...props.style,\n      }}\n    />\n  )\n}\n",
      "type": "registry:component",
      "target": "@ui/piano-roll-visualizer.tsx"
    }
  ]
}
