[{"data":1,"prerenderedAt":480},["ShallowReactive",2],{"navigation_docs":3,"-guide-how-it-works":81,"-guide-how-it-works-surround":475},[4,41,62],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":40},"Guide","i-lucide-book-open","\u002Fguide","1.guide",[10,15,20,25,30,35],{"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",false,{"title":42,"icon":43,"path":44,"stem":45,"children":46,"page":40},"Core","i-lucide-box","\u002Fcore","2.core",[47,52,57],{"title":48,"path":49,"stem":50,"icon":51},"Installation","\u002Fcore\u002Finstallation","2.core\u002F1.installation","i-lucide-download",{"title":53,"path":54,"stem":55,"icon":56},"API reference","\u002Fcore\u002Fapi","2.core\u002F2.api","i-lucide-code",{"title":58,"path":59,"stem":60,"icon":61},"Audio engines","\u002Fcore\u002Faudio-engines","2.core\u002F3.audio-engines","i-lucide-audio-lines",{"title":63,"icon":64,"path":65,"stem":66,"children":67,"page":40},"Vue","i-simple-icons-vuedotjs","\u002Fvue","3.vue",[68,71,76],{"title":48,"path":69,"stem":70,"icon":51},"\u002Fvue\u002Finstallation","3.vue\u002F1.installation",{"title":72,"path":73,"stem":74,"icon":75},"Props, events & methods","\u002Fvue\u002Fprops","3.vue\u002F2.props","i-lucide-sliders-horizontal",{"title":77,"path":78,"stem":79,"icon":80},"Composables","\u002Fvue\u002Fcomposables","3.vue\u002F3.composables","i-lucide-plug",{"id":82,"title":16,"body":83,"description":468,"extension":469,"links":470,"meta":471,"navigation":472,"path":17,"seo":473,"stem":18,"__hash__":474},"docs\u002F1.guide\u002F2.how-it-works.md",{"type":84,"value":85,"toc":459},"minimark",[86,108,113,123,150,154,213,223,230,239,340,353,393,404,407,433,439],[87,88,89,90,94,95,99,100,107],"p",{},"FFT Visualizer is, at its core, a ",[91,92,93],"strong",{},"renderer",": give it an array of magnitudes and it draws\na spectrum on the GPU. Everything before that — capturing audio and turning it into\nfrequency data — is ",[96,97,98],"em",{},"pluggable",", and ",[91,101,102,103],{},"where the FFT runs depends on the ",[104,105,106],"code",{},"mode"," you pick.",[109,110,112],"h2",{"id":111},"the-pipeline","The pipeline",[114,115,121],"pre",{"className":116,"code":118,"language":119,"meta":120},[117],"language-text"," mode=\"local\"     mic \u002F tab audio\n                        │  getUserMedia() \u002F getDisplayMedia()\n                        ▼\n                  Web Audio:  AudioContext ▸ MediaStreamSource ▸ AnalyserNode\n                        │  getFloatTimeDomainData()  ← raw waveform (e.g. 2048 samples)\n                        ▼\n                  Rust \u002F WASM  FftProcessor.process()\n                    Hann window ▸ real FFT ▸ magnitude\n                    ▸ log-spaced bands ▸ A-weighting\n                        │\n                        ▼\n                  ┌───────────────────────────────┐\n mode=\"websocket\" │  Uint8Array — 0–255,          │  ◀── a backend server streams this\n mode=\"external\"  │  one value per frequency band │  ◀── you push this via props\n                  └───────────────────────────────┘\n                        │\n                        ▼  noise floor ▸ smoothing ▸ peak-hold ▸ aggregate to `bands`\n                        ▼\n                  WebGL fragment shader — one draw call\n                        │\n                        ▼\n                      bars on screen\n","text","",[104,122,118],{"__ignoreMap":120},[87,124,125,126,129,130,133,134,137,138,141,142,145,146,149],{},"The ",[104,127,128],{},"Uint8Array"," of 0–255 magnitudes is the ",[91,131,132],{},"hand-off point",". In ",[104,135,136],{},"local"," mode the\nvisualizer produces it; in ",[104,139,140],{},"websocket"," and ",[104,143,144],{},"external"," modes it arrives ready-made and the\nFFT is ",[91,147,148],{},"not"," part of the visualizer at all.",[109,151,153],{"id":152},"where-the-fft-runs","Where the FFT runs",[155,156,157,173],"table",{},[158,159,160],"thead",{},[161,162,163,167,170],"tr",{},[164,165,166],"th",{},"Mode",[164,168,169],{},"Who computes the FFT",[164,171,172],{},"Runs where",[174,175,176,189,201],"tbody",{},[161,177,178,183,186],{},[179,180,181],"td",{},[104,182,136],{},[179,184,185],{},"The bundled Rust\u002FWASM processor",[179,187,188],{},"In the browser",[161,190,191,195,198],{},[179,192,193],{},[104,194,140],{},[179,196,197],{},"Your backend (Python \u002F Node \u002F Rust examples)",[179,199,200],{},"On a server or device",[161,202,203,207,210],{},[179,204,205],{},[104,206,144],{},[179,208,209],{},"Your own code (Web Audio, another analyser…)",[179,211,212],{},"Wherever you like",[87,214,215,216,218,219,222],{},"So if you're wondering \"is the audio→FFT conversion part of FFTVisualizer?\" — only in\n",[104,217,136],{}," mode. See ",[220,221,21],"a",{"href":22}," for the API of each.",[109,224,226,227,229],{"id":225},"inside-local-mode","Inside ",[104,228,136],{}," mode",[87,231,232,233,235,236,238],{},"When you set ",[104,234,106],{}," to ",[104,237,136],{},", the visualizer runs the whole chain client-side:",[240,241,242,261,269,286,334],"ol",{},[243,244,245,248,249,252,253,256,257,260],"li",{},[91,246,247],{},"Capture"," — ",[104,250,251],{},"getUserMedia()"," (microphone) or ",[104,254,255],{},"getDisplayMedia()"," (tab\u002Fsystem audio)\nreturns a ",[104,258,259],{},"MediaStream",".",[243,262,263,248,266,260],{},[91,264,265],{},"Web Audio graph",[104,267,268],{},"AudioContext → MediaStreamAudioSourceNode → AnalyserNode",[243,270,271,274,275,278,279,282,283,285],{},[91,272,273],{},"Grab raw samples"," — each animation frame it reads\n",[104,276,277],{},"analyser.getFloatTimeDomainData()",", which returns the ",[91,280,281],{},"raw waveform"," (the audio\nsamples themselves), ",[96,284,148],{}," frequency data.",[243,287,288,291,292,295,296],{},[91,289,290],{},"FFT in Rust\u002FWASM"," — those samples go to ",[104,293,294],{},"FftProcessor.process()",", which:\n",[297,298,299,306,313,320,327],"ul",{},[243,300,301,302,305],{},"applies a ",[91,303,304],{},"Hann window"," (reduces spectral leakage),",[243,307,308,309,312],{},"runs a ",[91,310,311],{},"real FFT",",",[243,314,315,316,319],{},"takes the ",[91,317,318],{},"magnitude"," of each complex bin (normalised by FFT size),",[243,321,322,323,326],{},"groups bins into ",[91,324,325],{},"exponentially (log-)spaced frequency bands",", so bass and treble\nget perceptually fair spacing instead of linear,",[243,328,329,330,333],{},"applies ",[91,331,332],{},"A-weighting"," (the loudness curve that matches how the ear weights\nfrequencies).",[243,335,336,337,339],{},"The result is a ",[104,338,128],{}," of 0–255 values — one per band — ready for the shader.",[87,341,342,343,346,347,349,350,352],{},"The WASM module is ",[91,344,345],{},"lazy-loaded",": it's only fetched the first time you actually capture\naudio locally, so ",[104,348,140],{},"\u002F",[104,351,144],{}," users never download it.",[354,355,357,360,361,364,365,368,369,372,373,376,377,380,381,385,386,389,390,392],"note",{"title":356},"Why not use the browser's built-in FFT?",[104,358,359],{},"AnalyserNode"," can compute the FFT for you (",[104,362,363],{},"getByteFrequencyData","). FFT Visualizer\ndeliberately grabs raw ",[91,366,367],{},"time-domain"," samples and runs its ",[91,370,371],{},"own"," FFT instead, for two\nreasons: ",[91,374,375],{},"identical output everywhere"," — the exact same Rust code runs in the browser\n(WASM) ",[96,378,379],{},"and"," in the ",[220,382,384],{"href":383},"\u002Fguide\u002Fwebsocket-protocol#reference-servers","backend reference servers",",\nso a local mic demo and a remote stream look the same — and ",[91,387,388],{},"control"," the browser doesn't\nexpose: custom windowing, log-spaced bands, A-weighting and a fixed frequency range\n(100 Hz–18 kHz). The ",[104,391,359],{}," here is used purely as a convenient way to read a buffer\nof samples; its own FFT is ignored.",[109,394,396,397,400,401],{"id":395},"two-resolutions-bins-vs-bands","Two resolutions: ",[104,398,399],{},"bins"," vs ",[104,402,403],{},"bands",[87,405,406],{},"There are two spectrum sizes in the chain, and it's worth keeping them straight:",[297,408,409,416],{},[243,410,411,415],{},[91,412,413],{},[104,414,399],{}," — the number of frequency bands the FFT processor (or your server) emits.",[243,417,418,422,423,426,427,429,430,432],{},[91,419,420],{},[104,421,403],{}," — how many bars the visualizer actually ",[96,424,425],{},"displays",". It aggregates the incoming\n",[104,428,399],{}," down to ",[104,431,403],{}," before rendering.",[87,434,435,436,260],{},"They're often the same (e.g. 80 → 80), but you can render fewer bars than you receive — for\nexample feed 80-bin data but display ",[104,437,438],{},"bands: 28",[440,441,442,443,446,447,450,451,454,455,458],"tip",{},"Every mode converges on the ",[91,444,445],{},"same"," 0–255 array before rendering — so the look you\nconfigure is driven by identical data whether it came from a local mic, a remote server, or\nyour own pipeline. Want that array back out — to drive LEDs, a flip-dot display, or any\nexternal hardware? The ",[104,448,449],{},"frame"," event (",[220,452,42],{"href":453},"\u002Fcore\u002Fapi#events",", ",[220,456,63],{"href":457},"\u002Fvue\u002Fprops#the-frame-event",")\nhands you exactly what the shader draws, every frame.",{"title":120,"searchDepth":460,"depth":460,"links":461},2,[462,463,464,466],{"id":111,"depth":460,"text":112},{"id":152,"depth":460,"text":153},{"id":225,"depth":460,"text":465},"Inside local mode",{"id":395,"depth":460,"text":467},"Two resolutions: bins vs bands","The signal pipeline from raw audio to rendered bars — and where the FFT actually runs.","md",null,{},{"icon":19},{"title":16,"description":468},"znvFTdAnFY__XRplYxaR4RvDKb_fbF2mZx57iG5tuxU",[476,478],{"title":11,"path":12,"stem":13,"description":477,"icon":14,"children":-1},"What FFT Visualizer is, and why it renders differently from other audio visualizers.",{"title":21,"path":22,"stem":23,"description":479,"icon":24,"children":-1},"The three ways to get audio data into the visualizer — local, WebSocket, and external.",1784963432760]