Components/VoiceBeam

VoiceBeam

Audio-reactive glow along the bottom edge of the element it wraps, for voice input, dictation and post-speech processing

VerifiedSince 0.6.2

Usage

TxVoiceBeam wraps exactly one element that carries its own corner radius, clips it, and paints a sound-reactive beam along its bottom edge. Feed it a MediaStream from the microphone, or drive it manually with level.

Loading demo...

Microphone Input

useMicrophone() produces the stream. It requests getUserMedia with echo cancellation, noise suppression and auto gain turned off, so the glow sees the real dynamics of the voice. Call start() from a click; browsers only grant the mic inside a user gesture.

<script setup lang="ts">
import { useMicrophone } from '@talex-touch/tuffex/pro'

const mic = useMicrophone()
const live = computed(() => mic.state.value === 'live')
</script>

<template>
  <TxVoiceBeam :stream="mic.stream.value" :processing="transcribing">
    <TxCard :radius="16" :padding="24" shadow="none">Ask anything…</TxCard>
  </TxVoiceBeam>
  <TxButton :aria-pressed="live" @click="live ? mic.stop() : mic.start()">
    {{ live ? 'Stop' : 'Listen' }}
  </TxButton>
</template>

Types and Palettes

type picks the host preset (default for a ~350 px chat input, pill for a recording pill, mobile for the bottom of a phone screen); every geometry prop still wins over it. colorVariant selects the palette and colors overrides individual lobes.

<template>
  <TxVoiceBeam type="pill" color-variant="ocean" :scale="0.9">
    <div class="pill">Recording…</div>
  </TxVoiceBeam>

  <TxVoiceBeam type="mobile" color-variant="candy" :level="() => 0.8">
    <div class="screen">Listening</div>
  </TxVoiceBeam>
</template>

Best Practices

  • Never rely on the glow as the only sign that the mic is live: keep a text status and a visible mic button state (aria-pressed), and announce changes with role="status".
  • Pass a getter (:level="() => meter.value"), not a reactive number read 60 times a second — the getter is sampled once per frame with no re-render.
  • stream wins over level: a stream with an audio track always drives the beam.
  • Wrap exactly one element that carries the radius, and put content that must stay crisp at position: relative; z-index: 5. Anything with its own overlay (popovers, menus) inside the wrapped element gets clipped — portal it out.
  • Call mic.stop() when the voice UI closes; the composable only stops tracks on unmount.
  • Keep instances few. Each one runs blurred layers and a canvas; the component is not for dense lists.

API Reference

Props

PropTypeDefaultDescription
type'default' | 'pill' | 'mobile''default'Host preset that seeds the geometry props.
streamMediaStream | nullnullLive audio to react to; wins over level.
levelnumber | () => number0Manual drive (0-1) used when there is no stream.
sensitivitynumber3.1Input gain on the analysed audio.
thresholdnumber0.015Noise gate (0-1); levels below it read as silence.
attacknumber0.325Seconds the glow takes to rise.
releasenumber0.86Seconds the glow takes to settle.
idlenumber0.23Resting presence (0-1) so the beam never looks dead.
breatheDurationnumber5.2Period of the idle breathing in seconds.
reachnumber1.2How tall the glow grows at full level.
spreadnumber1.05How far the glow widens at full level.
bandsbooleantrueLet low / mid / high bands move the lobes independently.
flownumber48Sideways travel of the spectrum in px/s at full level.
processingbooleanfalseGathers the glow into a travelling beam and holds it lit.
processingDurationnumber1.1Seconds for one pass of the processing beam.
processingLevelnumber0.55How lit the glow is held while processing.
processingEasenumber0.6Seconds of the morph in either direction.
processingTravelnumber1.55How far the processing beam travels to each side.
processingCurvenumber2.1How the sweep eases into each turn.
cornerFollownumber0.45How much the glow rides the corner arcs while processing.
colorVariant'colorful' | 'mono' | 'ocean' | 'sunset' | …'colorful'Palette for the lobes.
colorsstring[]noneUp to seven lobe colours, centre first.
bandColors{ core?, above?, mid?, below? }theme defaultsColours of the band's ridge and chromatic fringes.
theme'dark' | 'light' | 'auto''dark'Background adaptation; auto follows the system preference.
staticColorsbooleanfalseDisables the slow hue drift.
hueRangenumber24 / 40Hue drift range in degrees.
hueDurationnumber12 / 8.5Period of the hue drift in seconds.
activebooleantrueOff fades the beam out and stops the audio analysis.
pausedbooleanfalseFreezes glow, band and analysis on their last frame.
borderRadiusnumberauto-detectedCorner radius in px.
brightness / saturationnumbertheme defaultsGlow multipliers.
glowSizenumber1Bloom blur radius multiplier.
strokeOpacity / innerOpacity / bloomOpacitynumber1Per-layer opacity multipliers.
scalenumber1Multiplies every pixel dimension at once.
bend, bandStrength, bandWidth, bandPosition, bandCurve, bandSpread, bandSkew, bandOffset, bandTail, bandTailPosition, bandTailCurve, bandTailOverflow, bandAberrationnumbertunedShape of the glow's contour and the band along it.
distortion, distortionDetailnumber0.62, 2.3Horizontal warp of the light under the band line.
glowWidth, glowHeight, lobeSpacing, rangeWidth, rangeHeight, softness, coreSize, coreLight, coreLightWidth, coreLightHeight, strokeScale, innerScale, innerHeight, bloomScale, bloomHeightnumbertunedLobes, visible range, core and halo geometry.
strengthnumber1Overall effect opacity (0-1); the children are untouched.
cssstringnoneExtra CSS appended after the generated stylesheet; {id} is substituted per instance.

Slots

SlotPropsDescription
default-The wrapped element; the beam layers render behind and above it.

Events

EventPayloadDescription
level(level: number)Fired every frame with the smoothed level the beam is showing.
activate-Fired when the fade-in completes.
deactivate-Fired when the fade-out completes.

Exposed Methods

No public instance methods.

CSS Variables

VariableSourceDescription
--voice-strengthstrengthBeam-layer opacity (0-1).
--voice-stroke-opacity / --voice-inner-opacity / --voice-bloom-opacitystrokeOpacity / innerOpacity / bloomOpacityPer-layer opacity multipliers.

Overview

  • The component wraps its slot content and clips it to the child's border-top-left-radius (16 px when none is found), so the beam hugs the element edge.
  • One shared requestAnimationFrame loop drives every instance, capped at about 60 fps; an instance scrolled offscreen (256 px margin) unregisters and releases its analyser.
  • One AudioContext is shared per page, one source node per stream (reference-counted) and one analyser per instance. Audio is analysed, never played.
  • prefers-reduced-motion: reduce stops the idle breathing, the colour flow, the hue drift, the distortion warp and the processing sweep; the reaction to sound stays, since it is a meter.
  • Under stream, the analyser reads the RMS level plus three voice bands (80-300, 300-2000, 2000-6000 Hz).

Technologies

  • Manually verified against index.ts, TxVoiceBeam.vue, types.ts and voice-beam.test.ts under packages/tuffex/packages/components/src/voice-beam/.
  • styles.ts, presets.ts, voice-driver.ts, audio.ts and color.ts are verbatim ports of upstream voice-glow (MIT © Jakub Antalik) with strict-TS index hardening only; the Vue shell mirrors the upstream wrapper's fade lifecycle, offscreen pause and radius detection.
  • useMicrophone is the Vue port of the upstream React hook, with the same constraints and track lifecycle.
  • Component source: packages/tuffex/packages/components/src/voice-beam/src/TxVoiceBeam.vue.
  • Types: packages/tuffex/packages/components/src/voice-beam/src/types.ts.
  • Upstream: Jakubantalik/Libraries · voice-glow (MIT).
  • Coverage: packages/tuffex/packages/components/src/voice-beam/__tests__/voice-beam.test.ts verifies preset resolution, unknown-type fallback, the microphone state machine and the manual-level path.
查看源码
packages/tuffex/packages/components/src/voice-beam/index.ts