diff --git a/public/demo/bunny.png b/public/demo/bunny.png deleted file mode 100644 index 557aa016..00000000 Binary files a/public/demo/bunny.png and /dev/null differ diff --git a/public/demo/bunny.svg b/public/demo/bunny.svg new file mode 100644 index 00000000..b13cebbc --- /dev/null +++ b/public/demo/bunny.svg @@ -0,0 +1,288 @@ + + + + + + + + + diff --git a/public/demo/carla.svg b/public/demo/carla.svg new file mode 100644 index 00000000..4f747ea2 --- /dev/null +++ b/public/demo/carla.svg @@ -0,0 +1,537 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/demo/publish.svg b/public/demo/publish.svg new file mode 100644 index 00000000..44f486c9 --- /dev/null +++ b/public/demo/publish.svg @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + diff --git a/src/components/voice/cat.tsx b/src/components/voice/cat.tsx index c6b46b0f..8f43ee6a 100644 --- a/src/components/voice/cat.tsx +++ b/src/components/voice/cat.tsx @@ -24,7 +24,10 @@ const BLINK_MS = 120; type Tap = { analyser: AnalyserNode; samples: Float32Array; spectrum: Float32Array }; -export default function Cat(props: { voice?: AudioNode; class?: string }) { +// With no voice, `babble` makes up speech: bursts of syllables, each a random loudness and +// brightness, through the same lip sync as the real thing. Reduced motion keeps the cat still, +// apart from lip sync to a real voice. +export default function Cat(props: { voice?: AudioNode; babble?: boolean; class?: string }) { const [shape, setShape] = createSignal("rest"); const [blink, setBlink] = createSignal(false); let tap: Tap | undefined; @@ -57,13 +60,34 @@ export default function Cat(props: { voice?: AudioNode; class?: string }) { let mouthAt = 0; let blinkAt = last + 2500; let frame = 0; + const still = matchMedia("(prefers-reduced-motion: reduce)").matches; + const babble = props.babble && !still; + let syllable = { open: 0, tone: 1, closeAt: 0, endAt: 0 }; + let syllables = 0; const tick = (now: number) => { const dt = now - last; last = now; let target = 0; - if (tap) { + let tone = 1; + if (babble && !tap) { + if (now >= syllable.endAt) { + // Between phrases, a breath. + const pause = syllables-- <= 0; + if (pause) syllables = 3 + Math.floor(Math.random() * 8); + const ms = pause ? 700 + Math.random() * 1500 : 140 + Math.random() * 120; + syllable = { + open: pause ? 0 : 0.2 + Math.random() * 0.8, + tone: 0.7 + Math.random() * 0.5, + // Close a little before the next one so syllables read apart. + closeAt: now + ms * 0.65, + endAt: now + ms, + }; + } + target = now < syllable.closeAt ? syllable.open : 0; + tone = syllable.tone; + } else if (tap) { const { analyser, samples, spectrum } = tap; analyser.getFloatTimeDomainData(samples); target = level(samples); @@ -73,14 +97,15 @@ export default function Cat(props: { voice?: AudioNode; class?: string }) { bright = bright ? ease(bright, hz, 0.3, dt) : hz; usual = usual ? ease(usual, hz, 0.02, dt) : hz; } + if (usual) tone = bright / usual; } open = follow(open, target, dt); if (now - mouthAt >= MOUTH_MS) { mouthAt = now; - setShape(pick(open, usual ? bright / usual : 1)); + setShape(pick(open, tone)); } - setBlink(now >= blinkAt); + setBlink(!still && now >= blinkAt); if (now >= blinkAt + BLINK_MS) blinkAt = now + 2000 + Math.random() * 4000; frame = requestAnimationFrame(tick); diff --git a/src/pages/demo.mdx b/src/pages/demo.mdx index 50889804..b71d8e7e 100644 --- a/src/pages/demo.mdx +++ b/src/pages/demo.mdx @@ -4,6 +4,8 @@ title: Demos description: Live demos of the MoQ protocol in action. --- +import Cat from "@/components/voice/cat"; + # Demos Click on any of them. That's what they're here for. Everything is open source! @@ -19,7 +21,7 @@ Click on any of them. That's what they're here for. Everything is open source!
- CARLA + CARLA
CARLA
Steer simulated vehicles in CARLA over MoQ. Cameras stream on demand, with controls and telemetry on the same connection.
@@ -27,7 +29,7 @@ Click on any of them. That's what they're here for. Everything is open source!
- BBB + BBB
B I G B U C K B U N N Y
Watch a "live" broadcast of everybody's favorite rodent friend. The video player is available as a (tree-shakable) Javascript library or \ web component.
@@ -35,7 +37,7 @@ Click on any of them. That's what they're here for. Everything is open source!
- Voice AI +
Voice AI
Talk to a Pipecat voice agent over MoQ, with a live transcript of both sides. It sends its reply faster than real-time, and you can interrupt it whenever.
@@ -51,7 +53,7 @@ Click on any of them. That's what they're here for. Everything is open source!
- Publish + Publish
Publish
Go live! Share your camera, microphone, or screen with anyone you give the watch link to. Browser demos use a private broadcast in the try project.