[{"data":1,"prerenderedAt":737},["ShallowReactive",2],{"navigation_docs":3,"-react-props":104,"-react-props-surround":734},[4,46,67,91],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":45},"Guide","i-lucide-book-open","\u002Fguide","1.guide",[10,15,20,25,30,35,40],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fguide\u002Fintroduction","1.guide\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"How it works","\u002Fguide\u002Fhow-it-works","1.guide\u002F2.how-it-works","i-lucide-workflow",{"title":21,"path":22,"stem":23,"icon":24},"Data modes","\u002Fguide\u002Fdata-modes","1.guide\u002F3.data-modes","i-lucide-git-fork",{"title":26,"path":27,"stem":28,"icon":29},"Options","\u002Fguide\u002Foptions","1.guide\u002F4.options","i-lucide-settings-2",{"title":31,"path":32,"stem":33,"icon":34},"Gradients","\u002Fguide\u002Fgradients","1.guide\u002F5.gradients","i-lucide-palette",{"title":36,"path":37,"stem":38,"icon":39},"WebSocket protocol","\u002Fguide\u002Fwebsocket-protocol","1.guide\u002F6.websocket-protocol","i-lucide-radio",{"title":41,"path":42,"stem":43,"icon":44},"AI tools","\u002Fguide\u002Fai-tools","1.guide\u002F7.ai-tools","i-lucide-bot",false,{"title":47,"icon":48,"path":49,"stem":50,"children":51,"page":45},"Core","i-lucide-box","\u002Fcore","2.core",[52,57,62],{"title":53,"path":54,"stem":55,"icon":56},"Installation","\u002Fcore\u002Finstallation","2.core\u002F1.installation","i-lucide-download",{"title":58,"path":59,"stem":60,"icon":61},"API reference","\u002Fcore\u002Fapi","2.core\u002F2.api","i-lucide-code",{"title":63,"path":64,"stem":65,"icon":66},"Audio engines","\u002Fcore\u002Faudio-engines","2.core\u002F3.audio-engines","i-lucide-audio-lines",{"title":68,"icon":69,"path":70,"stem":71,"children":72,"page":45},"Vue","i-simple-icons-vuedotjs","\u002Fvue","3.vue",[73,76,81,86],{"title":53,"path":74,"stem":75,"icon":56},"\u002Fvue\u002Finstallation","3.vue\u002F1.installation",{"title":77,"path":78,"stem":79,"icon":80},"Props, events & methods","\u002Fvue\u002Fprops","3.vue\u002F2.props","i-lucide-sliders-horizontal",{"title":82,"path":83,"stem":84,"icon":85},"Composables","\u002Fvue\u002Fcomposables","3.vue\u002F3.composables","i-lucide-plug",{"title":87,"path":88,"stem":89,"icon":90},"Nuxt","\u002Fvue\u002Fnuxt","3.vue\u002F4.nuxt","i-simple-icons-nuxtdotjs",{"title":92,"icon":93,"path":94,"stem":95,"children":96,"page":45},"React","i-simple-icons-react","\u002Freact","4.react",[97,100],{"title":53,"path":98,"stem":99,"icon":56},"\u002Freact\u002Finstallation","4.react\u002F1.installation",{"title":101,"path":102,"stem":103,"icon":80},"Props, callbacks & ref","\u002Freact\u002Fprops","4.react\u002F2.props",{"id":105,"title":101,"body":106,"description":727,"extension":728,"links":729,"meta":730,"navigation":731,"path":102,"seo":732,"stem":103,"__hash__":733},"docs\u002F4.react\u002F2.props.md",{"type":107,"value":108,"toc":717},"minimark",[109,114,137,190,196,207,244,249,252,361,365,433,436,441,444,515,519,522,636,640,657,700,714],[110,111,113],"h2",{"id":112},"props","Props",[115,116,117,118,122,123,127,128,132,133,136],"p",{},"The ",[119,120,121],"code",{},"\u003CFFTVisualizer>"," component accepts ",[124,125,126],"strong",{},"every"," ",[129,130,131],"a",{"href":27},"shared option"," as a prop,\none-to-one, in ",[119,134,135],{},"camelCase",":",[138,139,144],"pre",{"className":140,"code":141,"language":142,"meta":143,"style":143},"language-tsx shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003CFFTVisualizer\n  mode=\"local\"\n  bands={40}\n  gradient=\"sunset\"\n  ledBars\n  smoothing={0.7}\n\u002F>\n","tsx","",[119,145,146,154,160,166,172,178,184],{"__ignoreMap":143},[147,148,151],"span",{"class":149,"line":150},"line",1,[147,152,153],{},"\u003CFFTVisualizer\n",[147,155,157],{"class":149,"line":156},2,[147,158,159],{},"  mode=\"local\"\n",[147,161,163],{"class":149,"line":162},3,[147,164,165],{},"  bands={40}\n",[147,167,169],{"class":149,"line":168},4,[147,170,171],{},"  gradient=\"sunset\"\n",[147,173,175],{"class":149,"line":174},5,[147,176,177],{},"  ledBars\n",[147,179,181],{"class":149,"line":180},6,[147,182,183],{},"  smoothing={0.7}\n",[147,185,187],{"class":149,"line":186},7,[147,188,189],{},"\u002F>\n",[115,191,192,193,195],{},"See ",[129,194,26],{"href":27}," for the full list of names, types, defaults, and meanings.\nUnset props keep the core's default, so you only pass what you want to change.",[115,197,198,199,202,203,206],{},"Custom ",[119,200,201],{},"gradient"," stops are compared ",[124,204,205],{},"by value",", so an inline array is fine — the gradient\ntexture is only rebuilt when the colors actually change:",[138,208,210],{"className":140,"code":209,"language":142,"meta":143,"style":143},"\u003CFFTVisualizer\n  mode=\"local\"\n  gradient={[\n    { stop: 0, color: '#001233' },\n    { stop: 1, color: 'hsl(280, 95%, 75%)' }\n  ]}\n\u002F>\n",[119,211,212,216,220,225,230,235,240],{"__ignoreMap":143},[147,213,214],{"class":149,"line":150},[147,215,153],{},[147,217,218],{"class":149,"line":156},[147,219,159],{},[147,221,222],{"class":149,"line":162},[147,223,224],{},"  gradient={[\n",[147,226,227],{"class":149,"line":168},[147,228,229],{},"    { stop: 0, color: '#001233' },\n",[147,231,232],{"class":149,"line":174},[147,233,234],{},"    { stop: 1, color: 'hsl(280, 95%, 75%)' }\n",[147,236,237],{"class":149,"line":180},[147,238,239],{},"  ]}\n",[147,241,242],{"class":149,"line":186},[147,243,189],{},[245,246,248],"h3",{"id":247},"react-only-props","React-only props",[115,250,251],{},"Four props exist only on the React wrapper:",[253,254,255,274],"table",{},[256,257,258],"thead",{},[259,260,261,265,268,271],"tr",{},[262,263,264],"th",{},"Prop",[262,266,267],{},"Type",[262,269,270],{},"Default",[262,272,273],{},"Description",[275,276,277,298,319,340],"tbody",{},[259,278,279,285,290,295],{},[280,281,282],"td",{},[119,283,284],{},"showStats",[280,286,287],{},[119,288,289],{},"boolean",[280,291,292],{},[119,293,294],{},"true",[280,296,297],{},"Show the small connection\u002Ffps overlay in the corner",[259,299,300,305,310,313],{},[280,301,302],{},[119,303,304],{},"renderStats",[280,306,307],{},[119,308,309],{},"(state: { connected, bands, fps }) => ReactNode",[280,311,312],{},"—",[280,314,315,316,318],{},"Replace the overlay's content with your own (only rendered when ",[119,317,284],{},")",[259,320,321,326,331,333],{},[280,322,323],{},[119,324,325],{},"className",[280,327,328],{},[119,329,330],{},"string",[280,332,312],{},[280,334,335,336,339],{},"Extra class names on the root element (the built-in ",[119,337,338],{},"fft-visualizer"," class is kept)",[259,341,342,347,352,354],{},[280,343,344],{},[119,345,346],{},"style",[280,348,349],{},[119,350,351],{},"CSSProperties",[280,353,312],{},[280,355,356,357,360],{},"Extra styles on the root element (merged over the ",[119,358,359],{},"background"," option)",[110,362,364],{"id":363},"callbacks","Callbacks",[253,366,367,378],{},[256,368,369],{},[259,370,371,373,376],{},[262,372,264],{},[262,374,375],{},"Payload",[262,377,273],{},[275,379,380,392,404,418],{},[259,381,382,387,389],{},[280,383,384],{},[119,385,386],{},"onConnected",[280,388,312],{},[280,390,391],{},"Data source became active",[259,393,394,399,401],{},[280,395,396],{},[119,397,398],{},"onDisconnected",[280,400,312],{},[280,402,403],{},"Data source stopped",[259,405,406,411,415],{},[280,407,408],{},[119,409,410],{},"onError",[280,412,413],{},[119,414,330],{},[280,416,417],{},"Error message (WS error, capture failure, WebGL init failure)",[259,419,420,425,430],{},[280,421,422],{},[119,423,424],{},"onFrame",[280,426,427],{},[119,428,429],{},"(data, left, right)",[280,431,432],{},"The display bar magnitudes, once per processed audio frame",[115,434,435],{},"Callbacks are read through a ref, so inline arrow functions are fine — changing one never\nresubscribes or restarts the visualizer.",[245,437,439],{"id":438},"onframe",[119,440,424],{},[115,442,443],{},"Fires once per processed audio frame with the same 0–255 bar values the shader draws — a\nready-made feed for external hardware (LED strips, flip-dot displays, …) without\nre-implementing any FFT. Listener-gated, so it costs nothing when unused.",[138,445,447],{"className":140,"code":446,"language":142,"meta":143,"style":143},"\u002F\u002F data:  Uint8Array — one 0–255 magnitude per bar (length = `bands`)\n\u002F\u002F left \u002F right: per-channel arrays in stereo mode, else null\n\u002F\u002F               (in stereo, `data` is the per-bar max of both channels)\nfunction onFrame(data: Uint8Array, left: Uint8Array | null, right: Uint8Array | null) {\n  const rows = 14 \u002F\u002F e.g. dots per column on a flip-dot panel\n  data.forEach((v, col) => {\n    const lit = Math.round((v \u002F 255) * rows)\n    \u002F\u002F light dots 0..lit-1 in column `col`\n  })\n}\n\n\u003CFFTVisualizer mode=\"local\" bands={28} onFrame={onFrame} \u002F>\n",[119,448,449,454,459,464,469,474,479,484,490,496,502,509],{"__ignoreMap":143},[147,450,451],{"class":149,"line":150},[147,452,453],{},"\u002F\u002F data:  Uint8Array — one 0–255 magnitude per bar (length = `bands`)\n",[147,455,456],{"class":149,"line":156},[147,457,458],{},"\u002F\u002F left \u002F right: per-channel arrays in stereo mode, else null\n",[147,460,461],{"class":149,"line":162},[147,462,463],{},"\u002F\u002F               (in stereo, `data` is the per-bar max of both channels)\n",[147,465,466],{"class":149,"line":168},[147,467,468],{},"function onFrame(data: Uint8Array, left: Uint8Array | null, right: Uint8Array | null) {\n",[147,470,471],{"class":149,"line":174},[147,472,473],{},"  const rows = 14 \u002F\u002F e.g. dots per column on a flip-dot panel\n",[147,475,476],{"class":149,"line":180},[147,477,478],{},"  data.forEach((v, col) => {\n",[147,480,481],{"class":149,"line":186},[147,482,483],{},"    const lit = Math.round((v \u002F 255) * rows)\n",[147,485,487],{"class":149,"line":486},8,[147,488,489],{},"    \u002F\u002F light dots 0..lit-1 in column `col`\n",[147,491,493],{"class":149,"line":492},9,[147,494,495],{},"  })\n",[147,497,499],{"class":149,"line":498},10,[147,500,501],{},"}\n",[147,503,505],{"class":149,"line":504},11,[147,506,508],{"emptyLinePlaceholder":507},true,"\n",[147,510,512],{"class":149,"line":511},12,[147,513,514],{},"\u003CFFTVisualizer mode=\"local\" bands={28} onFrame={onFrame} \u002F>\n",[110,516,518],{"id":517},"imperative-handle","Imperative handle",[115,520,521],{},"Attach a ref for manual control:",[138,523,525],{"className":140,"code":524,"language":142,"meta":143,"style":143},"import { useRef } from 'react'\nimport { FFTVisualizer, type FFTVisualizerHandle } from '@fft-visualizer\u002Freact'\n\nconst viz = useRef\u003CFFTVisualizerHandle>(null)\n\n\u002F\u002F Manual connection control\nviz.current?.connect()\nviz.current?.disconnect()\nviz.current?.isConnected            \u002F\u002F boolean\n\n\u002F\u002F Feed FFT frames imperatively (mode=\"external\") — copies the data, so it works\n\u002F\u002F even when you reuse one buffer each frame, and avoids a render per frame\nviz.current?.feedData(mono)                 \u002F\u002F Uint8Array\nviz.current?.feedData(mono, left, right)    \u002F\u002F stereo\n\n\u002F\u002F Local-audio device management\nawait viz.current?.getAudioDevices()   \u002F\u002F Promise\u003CAudioDevice[]> (prompts for mic permission)\nviz.current?.audioDevices              \u002F\u002F AudioDevice[]\nviz.current?.activeAudioDeviceId       \u002F\u002F string | undefined\n\n\u003CFFTVisualizer ref={viz} mode=\"local\" \u002F>\n",[119,526,527,532,537,541,546,550,555,560,565,570,574,579,584,590,596,601,607,613,619,625,630],{"__ignoreMap":143},[147,528,529],{"class":149,"line":150},[147,530,531],{},"import { useRef } from 'react'\n",[147,533,534],{"class":149,"line":156},[147,535,536],{},"import { FFTVisualizer, type FFTVisualizerHandle } from '@fft-visualizer\u002Freact'\n",[147,538,539],{"class":149,"line":162},[147,540,508],{"emptyLinePlaceholder":507},[147,542,543],{"class":149,"line":168},[147,544,545],{},"const viz = useRef\u003CFFTVisualizerHandle>(null)\n",[147,547,548],{"class":149,"line":174},[147,549,508],{"emptyLinePlaceholder":507},[147,551,552],{"class":149,"line":180},[147,553,554],{},"\u002F\u002F Manual connection control\n",[147,556,557],{"class":149,"line":186},[147,558,559],{},"viz.current?.connect()\n",[147,561,562],{"class":149,"line":486},[147,563,564],{},"viz.current?.disconnect()\n",[147,566,567],{"class":149,"line":492},[147,568,569],{},"viz.current?.isConnected            \u002F\u002F boolean\n",[147,571,572],{"class":149,"line":498},[147,573,508],{"emptyLinePlaceholder":507},[147,575,576],{"class":149,"line":504},[147,577,578],{},"\u002F\u002F Feed FFT frames imperatively (mode=\"external\") — copies the data, so it works\n",[147,580,581],{"class":149,"line":511},[147,582,583],{},"\u002F\u002F even when you reuse one buffer each frame, and avoids a render per frame\n",[147,585,587],{"class":149,"line":586},13,[147,588,589],{},"viz.current?.feedData(mono)                 \u002F\u002F Uint8Array\n",[147,591,593],{"class":149,"line":592},14,[147,594,595],{},"viz.current?.feedData(mono, left, right)    \u002F\u002F stereo\n",[147,597,599],{"class":149,"line":598},15,[147,600,508],{"emptyLinePlaceholder":507},[147,602,604],{"class":149,"line":603},16,[147,605,606],{},"\u002F\u002F Local-audio device management\n",[147,608,610],{"class":149,"line":609},17,[147,611,612],{},"await viz.current?.getAudioDevices()   \u002F\u002F Promise\u003CAudioDevice[]> (prompts for mic permission)\n",[147,614,616],{"class":149,"line":615},18,[147,617,618],{},"viz.current?.audioDevices              \u002F\u002F AudioDevice[]\n",[147,620,622],{"class":149,"line":621},19,[147,623,624],{},"viz.current?.activeAudioDeviceId       \u002F\u002F string | undefined\n",[147,626,628],{"class":149,"line":627},20,[147,629,508],{"emptyLinePlaceholder":507},[147,631,633],{"class":149,"line":632},21,[147,634,635],{},"\u003CFFTVisualizer ref={viz} mode=\"local\" \u002F>\n",[110,637,639],{"id":638},"standalone-audio-engines","Standalone audio engines",[115,641,642,643,652,653,656],{},"There are no React-specific hooks: the data pipelines are framework-agnostic and live in the\ncore package. Use ",[129,644,645,648,649],{"href":64},[119,646,647],{},"createLocalAudio"," \u002F ",[119,650,651],{},"createWebSocketFft"," directly —\nwith ",[119,654,655],{},"mode=\"external\"",", or to drive your own rendering. Both are callback-driven, which keeps\nper-frame data out of React state.",[138,658,662],{"className":659,"code":660,"language":661,"meta":143,"style":143},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { createLocalAudio, createWebSocketFft } from '@fft-visualizer\u002Fcore'\n","ts",[119,663,664],{"__ignoreMap":143},[147,665,666,670,674,678,681,684,687,690,693,697],{"class":149,"line":150},[147,667,669],{"class":668},"s7zQu","import",[147,671,673],{"class":672},"sMK4o"," {",[147,675,677],{"class":676},"sTEyZ"," createLocalAudio",[147,679,680],{"class":672},",",[147,682,683],{"class":676}," createWebSocketFft",[147,685,686],{"class":672}," }",[147,688,689],{"class":668}," from",[147,691,692],{"class":672}," '",[147,694,696],{"class":695},"sfazB","@fft-visualizer\u002Fcore",[147,698,699],{"class":672},"'\n",[115,701,702,703,706,707,713],{},"The raw WASM FFT processor is also re-exported from ",[119,704,705],{},"@fft-visualizer\u002Freact\u002Fwasm","\n(from ",[129,708,710],{"href":709},"\u002Fcore\u002Faudio-engines#raw-wasm-processor",[119,711,712],{},"@fft-visualizer\u002Fcore\u002Fwasm",").",[346,715,716],{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}",{"title":143,"searchDepth":156,"depth":156,"links":718},[719,722,725,726],{"id":112,"depth":156,"text":113,"children":720},[721],{"id":247,"depth":162,"text":248},{"id":363,"depth":156,"text":364,"children":723},[724],{"id":438,"depth":162,"text":424},{"id":517,"depth":156,"text":518},{"id":638,"depth":156,"text":639},"The React component API — props, callback props, and the imperative handle.","md",null,{},{"icon":80},{"title":101,"description":727},"lnafn0YxJV97j0A_PLFG6DDr2t15uXiEQuL0Ih5mPi0",[735,729],{"title":53,"path":98,"stem":99,"description":736,"icon":56,"children":-1},"Install the package and render your first spectrum in under a minute.",1785144672058]