Skip to content

Commit f805877

Browse files
AudioShaderVisualizer
1 parent 082d433 commit f805877

File tree

6 files changed

+489
-222
lines changed

6 files changed

+489
-222
lines changed

app/ui/_components.tsx

Lines changed: 145 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ import {
3030
AudioRadialVisualizer,
3131
audioRadialVisualizerVariants,
3232
} from '@/components/livekit/audio-visualizer/audio-radial-visualizer/audio-radial-visualizer';
33-
import { AudioShaderVisualizer } from '@/components/livekit/audio-visualizer/audio-shader-visualizer/audio-shader-visualizer';
33+
import {
34+
AudioShaderVisualizer,
35+
audioShaderVisualizerVariants,
36+
} from '@/components/livekit/audio-visualizer/audio-shader-visualizer/audio-shader-visualizer';
3437
import { Button, buttonVariants } from '@/components/livekit/button';
3538
import { ChatEntry } from '@/components/livekit/chat-entry';
3639
import {
@@ -52,6 +55,9 @@ type audioBarVisualizerVariantsSizeType = VariantProps<typeof audioBarVisualizer
5255
type audioRadialVisualizerVariantsSizeType = VariantProps<
5356
typeof audioRadialVisualizerVariants
5457
>['size'];
58+
type audioShaderVisualizerVariantsSizeType = VariantProps<
59+
typeof audioShaderVisualizerVariants
60+
>['size'];
5561

5662
export function useMicrophone() {
5763
const { startSession } = useSession();
@@ -563,93 +569,166 @@ export const COMPONENTS = {
563569
},
564570

565571
AudioShaderVisualizer: () => {
572+
const { startSession, endSession } = useSession();
573+
const { localParticipant } = useLocalParticipant();
566574
const [presetIndex, setPresetIndex] = useState(3);
567575

568-
// speed
569-
const [a, setA] = useState(50);
570-
// // color scale
571-
const [h, setH] = useState(0.1);
572-
// // color position
573-
const [i, setI] = useState(0.15);
574-
// blur
575-
const [f, setF] = useState(0.1);
576576
// shape
577-
const [g, setG] = useState(1.0);
577+
const [shape, setShape] = useState(1.0);
578+
// color scale
579+
const [colorScale, setColorScale] = useState(0.1);
580+
// color position
581+
const [colorPosition, setColorPosition] = useState(0.15);
582+
583+
const sizes = ['icon', 'sm', 'md', 'lg', 'xl'];
584+
const states = [
585+
'disconnected',
586+
'connecting',
587+
'initializing',
588+
'listening',
589+
'thinking',
590+
'speaking',
591+
] as AgentState[];
592+
593+
const [size, setSize] = useState<audioShaderVisualizerVariantsSizeType>('lg');
594+
const [state, setState] = useState<AgentState>(states[0]);
578595

579596
const {
580597
// state,
581598
audioTrack,
582599
} = useVoiceAssistant();
583600

584-
useMicrophone();
601+
useEffect(() => {
602+
if (state === 'speaking') {
603+
startSession();
604+
localParticipant.setMicrophoneEnabled(true, undefined);
605+
} else {
606+
endSession();
607+
localParticipant.setMicrophoneEnabled(false, undefined);
608+
}
609+
}, [startSession, endSession, state, localParticipant]);
585610

586611
const fields = [
587-
['speed', a, setA, 0, 250, 10],
588-
['color position', i, setI, 0, 1, 0.01],
589-
['color scale', h, setH, 0, 1, 0.01],
590-
['blur', f, setF, 0, 2, 0.01],
591-
['shape', g, setG, 1, 5, 1],
612+
['color position', colorPosition, setColorPosition, 0, 1, 0.01],
613+
['color scale', colorScale, setColorScale, 0, 1, 0.01],
592614
] as const;
593615

594616
return (
595617
<Container componentName="AudioShaderVisualizer">
596618
<StartAudio label="Start Audio" />
597619
<RoomAudioRenderer />
598-
<div className="grid grid-cols-2 gap-4">
620+
621+
<div className="flex gap-4">
622+
<div className="flex-1">
623+
<label className="font-mono text-xs uppercase" htmlFor="state">
624+
State
625+
</label>
626+
<Select value={state} onValueChange={(value) => setState(value as AgentState)}>
627+
<SelectTrigger id="state" className="w-full">
628+
<SelectValue placeholder="Select a state" />
629+
</SelectTrigger>
630+
<SelectContent>
631+
{states.map((state) => (
632+
<SelectItem key={state} value={state}>
633+
{state}
634+
</SelectItem>
635+
))}
636+
</SelectContent>
637+
</Select>
638+
</div>
639+
640+
<div className="flex-1">
641+
<label className="font-mono text-xs uppercase" htmlFor="size">
642+
Size
643+
</label>
644+
<Select
645+
value={size as string}
646+
onValueChange={(value) => setSize(value as audioShaderVisualizerVariantsSizeType)}
647+
>
648+
<SelectTrigger id="size" className="w-full">
649+
<SelectValue placeholder="Select a size" />
650+
</SelectTrigger>
651+
<SelectContent>
652+
{sizes.map((size) => (
653+
<SelectItem key={size} value={size as string}>
654+
{size.toUpperCase()}
655+
</SelectItem>
656+
))}
657+
</SelectContent>
658+
</Select>
659+
</div>
660+
661+
<div className="flex-1">
662+
<StoryTitle>Preset</StoryTitle>
663+
<Select
664+
value={String(presetIndex)}
665+
onValueChange={(value) => setPresetIndex(parseInt(value))}
666+
>
667+
<SelectTrigger id="presetIndex" className="w-full">
668+
<SelectValue placeholder="Select a preset" />
669+
</SelectTrigger>
670+
<SelectContent>
671+
<SelectItem value="0">Preset 1</SelectItem>
672+
<SelectItem value="1">Preset 2</SelectItem>
673+
<SelectItem value="2">Preset 3</SelectItem>
674+
<SelectItem value="3">Preset 4</SelectItem>
675+
</SelectContent>
676+
</Select>
677+
</div>
678+
679+
<div className="flex-1">
680+
<label className="font-mono text-xs uppercase" htmlFor="shape">
681+
Shape
682+
</label>
683+
<Select value={shape.toString()} onValueChange={(value) => setShape(parseInt(value))}>
684+
<SelectTrigger id="shape" className="w-full">
685+
<SelectValue placeholder="Select a shape" />
686+
</SelectTrigger>
687+
<SelectContent>
688+
<SelectItem value="1">Circle</SelectItem>
689+
<SelectItem value="2">Line</SelectItem>
690+
</SelectContent>
691+
</Select>
692+
</div>
693+
</div>
694+
695+
<div className="py-12">
599696
<AudioShaderVisualizer
600-
speed={a}
601-
blur={f}
602-
shape={g}
603-
colorScale={h}
604-
colorPosition={i}
605-
audioTrack={audioTrack}
606-
presetIndex={presetIndex}
607-
// className="bg-amber-100"
697+
size={size}
698+
state={state}
699+
shape={shape}
700+
colorScale={colorScale}
701+
colorPosition={colorPosition}
702+
audioTrack={audioTrack as TrackReferenceOrPlaceholder}
703+
className="mx-auto bg-black"
608704
/>
609-
<div>
610-
<div className="mb-4">
611-
<StoryTitle>Preset</StoryTitle>
612-
<Select
613-
value={String(presetIndex)}
614-
onValueChange={(value) => setPresetIndex(parseInt(value))}
615-
>
616-
<SelectTrigger id="presetIndex" className="w-full">
617-
<SelectValue placeholder="Select a preset" />
618-
</SelectTrigger>
619-
<SelectContent>
620-
<SelectItem value="0">Preset 1</SelectItem>
621-
<SelectItem value="1">Preset 2</SelectItem>
622-
<SelectItem value="2">Preset 3</SelectItem>
623-
<SelectItem value="3">Preset 4</SelectItem>
624-
</SelectContent>
625-
</Select>
626-
</div>
705+
</div>
627706

628-
{fields.map(([name, value, setValue, min = 0.1, max = 10, step = 0.1]) => {
629-
// Use 0-1 range for color phase channels
630-
const isColorPhase = name.toString().startsWith('colorPhase');
631-
632-
return (
633-
<div key={name}>
634-
<div className="flex items-center justify-between">
635-
<StoryTitle>{name}</StoryTitle>
636-
<div className="text-muted-foreground mb-2 text-xs">
637-
{isColorPhase ? Number(value).toFixed(2) : String(value)}
638-
</div>
707+
<div className="grid grid-cols-2 gap-4">
708+
{fields.map(([name, value, setValue, min = 0.1, max = 10, step = 0.1]) => {
709+
// Use 0-1 range for color phase channels
710+
const isColorPhase = name.toString().startsWith('colorPhase');
711+
712+
return (
713+
<div key={name}>
714+
<div className="flex items-center justify-between">
715+
<StoryTitle>{name}</StoryTitle>
716+
<div className="text-muted-foreground mb-2 text-xs">
717+
{isColorPhase ? Number(value).toFixed(2) : String(value)}
639718
</div>
640-
<input
641-
type="range"
642-
value={String(value)}
643-
min={min}
644-
max={max}
645-
step={step}
646-
onChange={(e) => setValue(parseFloat(e.target.value))}
647-
className="w-full"
648-
/>
649719
</div>
650-
);
651-
})}
652-
</div>
720+
<input
721+
type="range"
722+
value={String(value)}
723+
min={min}
724+
max={max}
725+
step={step}
726+
onChange={(e) => setValue(parseFloat(e.target.value))}
727+
className="w-full"
728+
/>
729+
</div>
730+
);
731+
})}
653732
</div>
654733
</Container>
655734
);

components/livekit/audio-visualizer/audio-bar-visualizer/audio-bar-visualizer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export function AudioBarVisualizer({
8484
case 'connecting':
8585
return 2000 / _barCount;
8686
case 'initializing':
87-
return 500;
87+
return 2000;
8888
case 'listening':
8989
return 500;
9090
case 'thinking':

0 commit comments

Comments
 (0)