{"version":3,"sources":["../src/components/visualization/multiband-audio-visualizer.tsx"],"sourcesContent":["/*\n *  Copyright (c) 2024. Rapida\n *\n *  Permission is hereby granted, free of charge, to any person obtaining a copy\n *  of this software and associated documentation files (the \"Software\"), to deal\n *  in the Software without restriction, including without limitation the rights\n *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n *  copies of the Software, and to permit persons to whom the Software is\n *  furnished to do so, subject to the following conditions:\n *\n *  The above copyright notice and this permission notice shall be included in\n *  all copies or substantial portions of the Software.\n *\n *  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n *  THE SOFTWARE.\n *\n *  Author: Prashant <prashant@rapida.ai>\n *\n */\nimport { cn } from \"@/rapida/styles\";\nimport { useEffect, useState, useMemo } from \"react\";\n\n/**\n * @beta\n */\nexport type AgentState =\n  | \"disconnected\"\n  | \"connecting\"\n  | \"initializing\"\n  | \"listening\"\n  | \"thinking\"\n  | \"speaking\";\n\ntype MultibandAudioVisualizerComponentProps = {\n  classNames?: string;\n  state: AgentState;\n  barWidth: number;\n  minBarHeight: number;\n  maxBarHeight: number;\n  barColor?: string;\n  frequencies: Float32Array[] | number[][];\n};\n\nexport const MultibandAudioVisualizerComponent = ({\n  classNames,\n  state,\n  barWidth,\n  minBarHeight,\n  maxBarHeight,\n  barColor = \"bg-white\",\n  frequencies,\n}: MultibandAudioVisualizerComponentProps) => {\n  // Calculate summed frequencies with enhanced sensitivity for voice\n  const summedFrequencies = useMemo(() => {\n    return frequencies.map((bandFrequencies) => {\n      if (!bandFrequencies || bandFrequencies.length === 0) return 0;\n\n      const freqArray = bandFrequencies as number[];\n      // Calculate RMS (Root Mean Square) for more accurate energy representation\n      const sumSquares = freqArray.reduce((acc, val) => acc + val * val, 0);\n      const rms = Math.sqrt(sumSquares / freqArray.length);\n\n      // Apply a curve to make the visualization more responsive to voice\n      // This enhances mid-range values while keeping peaks visible\n      return Math.min(1, Math.pow(rms, 0.7) * 1.2);\n    });\n  }, [frequencies]);\n\n  const [thinkingIndex, setThinkingIndex] = useState(\n    Math.floor(summedFrequencies.length / 2),\n  );\n  const [thinkingDirection, setThinkingDirection] = useState<\"left\" | \"right\">(\n    \"right\",\n  );\n\n  useEffect(() => {\n    if (state !== \"thinking\") {\n      setThinkingIndex(Math.floor(summedFrequencies.length / 2));\n      return;\n    }\n    const timeout = setTimeout(() => {\n      if (thinkingDirection === \"right\") {\n        if (thinkingIndex === summedFrequencies.length - 1) {\n          setThinkingDirection(\"left\");\n          setThinkingIndex((prev) => prev - 1);\n        } else {\n          setThinkingIndex((prev) => prev + 1);\n        }\n      } else {\n        if (thinkingIndex === 0) {\n          setThinkingDirection(\"right\");\n          setThinkingIndex((prev) => prev + 1);\n        } else {\n          setThinkingIndex((prev) => prev - 1);\n        }\n      }\n    }, 200);\n\n    return () => clearTimeout(timeout);\n  }, [state, summedFrequencies.length, thinkingDirection, thinkingIndex]);\n\n  // Determine if the visualizer is active (has meaningful audio input)\n  const isActive = useMemo(() => {\n    return state === \"listening\" || state === \"speaking\";\n  }, [state]);\n\n  return (\n    <div className={cn(classNames, \"flex flex-row items-center\")}>\n      {summedFrequencies.map((frequency, index) => {\n        const isCenter = index === Math.floor(summedFrequencies.length / 2);\n\n        // Calculate height with minimum threshold for visibility\n        const minThreshold = 0.05;\n        const effectiveFrequency = Math.max(frequency, minThreshold);\n        const height =\n          minBarHeight + effectiveFrequency * (maxBarHeight - minBarHeight);\n\n        // Add subtle scale effect based on frequency intensity\n        const scale = isActive && frequency > 0.3 ? 1 + frequency * 0.1 : 1;\n\n        return (\n          <div\n            className={cn(\n              barColor,\n              \"rounded-full\",\n              isCenter && state === \"listening\" && frequency < 0.1\n                ? \"animate-pulse\"\n                : \"\",\n            )}\n            key={\"frequency-\" + index}\n            style={{\n              height: `${height}px`,\n              width: `${barWidth}px`,\n              transform: `scaleY(${scale})`,\n              transition:\n                \"height 0.08s ease-out, transform 0.08s ease-out, background-color 0.2s ease-out\",\n              willChange: \"height, transform\",\n            }}\n          />\n        );\n      })}\n    </div>\n  );\n};\n"],"mappings":";;;;;AAyBA,SAAS,WAAW,UAAU,eAAe;AAqGnC;AA9EH,IAAM,oCAAoC,CAAC;AAAA,EAChD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AACF,MAA8C;AAE5C,QAAM,oBAAoB,QAAQ,MAAM;AACtC,WAAO,YAAY,IAAI,CAAC,oBAAoB;AAC1C,UAAI,CAAC,mBAAmB,gBAAgB,WAAW,EAAG,QAAO;AAE7D,YAAM,YAAY;AAElB,YAAM,aAAa,UAAU,OAAO,CAAC,KAAK,QAAQ,MAAM,MAAM,KAAK,CAAC;AACpE,YAAM,MAAM,KAAK,KAAK,aAAa,UAAU,MAAM;AAInD,aAAO,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,GAAG,IAAI,GAAG;AAAA,IAC7C,CAAC;AAAA,EACH,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,CAAC,eAAe,gBAAgB,IAAI;AAAA,IACxC,KAAK,MAAM,kBAAkB,SAAS,CAAC;AAAA,EACzC;AACA,QAAM,CAAC,mBAAmB,oBAAoB,IAAI;AAAA,IAChD;AAAA,EACF;AAEA,YAAU,MAAM;AACd,QAAI,UAAU,YAAY;AACxB,uBAAiB,KAAK,MAAM,kBAAkB,SAAS,CAAC,CAAC;AACzD;AAAA,IACF;AACA,UAAM,UAAU,WAAW,MAAM;AAC/B,UAAI,sBAAsB,SAAS;AACjC,YAAI,kBAAkB,kBAAkB,SAAS,GAAG;AAClD,+BAAqB,MAAM;AAC3B,2BAAiB,CAAC,SAAS,OAAO,CAAC;AAAA,QACrC,OAAO;AACL,2BAAiB,CAAC,SAAS,OAAO,CAAC;AAAA,QACrC;AAAA,MACF,OAAO;AACL,YAAI,kBAAkB,GAAG;AACvB,+BAAqB,OAAO;AAC5B,2BAAiB,CAAC,SAAS,OAAO,CAAC;AAAA,QACrC,OAAO;AACL,2BAAiB,CAAC,SAAS,OAAO,CAAC;AAAA,QACrC;AAAA,MACF;AAAA,IACF,GAAG,GAAG;AAEN,WAAO,MAAM,aAAa,OAAO;AAAA,EACnC,GAAG,CAAC,OAAO,kBAAkB,QAAQ,mBAAmB,aAAa,CAAC;AAGtE,QAAM,WAAW,QAAQ,MAAM;AAC7B,WAAO,UAAU,eAAe,UAAU;AAAA,EAC5C,GAAG,CAAC,KAAK,CAAC;AAEV,SACE,oBAAC,SAAI,WAAW,GAAG,YAAY,4BAA4B,GACxD,4BAAkB,IAAI,CAAC,WAAW,UAAU;AAC3C,UAAM,WAAW,UAAU,KAAK,MAAM,kBAAkB,SAAS,CAAC;AAGlE,UAAM,eAAe;AACrB,UAAM,qBAAqB,KAAK,IAAI,WAAW,YAAY;AAC3D,UAAM,SACJ,eAAe,sBAAsB,eAAe;AAGtD,UAAM,QAAQ,YAAY,YAAY,MAAM,IAAI,YAAY,MAAM;AAElE,WACE;AAAA,MAAC;AAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA,YAAY,UAAU,eAAe,YAAY,MAC7C,kBACA;AAAA,QACN;AAAA,QAEA,OAAO;AAAA,UACL,QAAQ,GAAG,MAAM;AAAA,UACjB,OAAO,GAAG,QAAQ;AAAA,UAClB,WAAW,UAAU,KAAK;AAAA,UAC1B,YACE;AAAA,UACF,YAAY;AAAA,QACd;AAAA;AAAA,MARK,eAAe;AAAA,IAStB;AAAA,EAEJ,CAAC,GACH;AAEJ;","names":[]}