Skip to content

Commit ea69c7f

Browse files
i-anasopclaude
andcommitted
tune: calmer robot hover (no jelly) + more grounded voice
Cut the float bob amplitude, removed the hop, and softened the squash so the robot settles into a gentle hover instead of bouncing. Eased the voice pitch down (neural 1.16->1.07, browser pitch 1.5->1.18) for a more composed, serious tone while staying youthful. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent cc7cb89 commit ea69c7f

2 files changed

Lines changed: 9 additions & 10 deletions

File tree

components/reflection/ImmersiveRoom3D.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -198,14 +198,13 @@ function CompanionModel({ state }: { state: OrbState }) {
198198

199199
useFrame((st, dt) => {
200200
const t = st.clock.elapsedTime;
201-
// floaty idle bob with a springy squash-&-stretch + an occasional happy hop
202-
const bob = Math.sin(t * 1.6);
203-
const hop = Math.pow(Math.max(0, Math.sin(t * 0.55)), 8); // a little jump now and then
201+
// a gentle, settled hover — subtle bob with the faintest squash, no jelly
202+
const bob = Math.sin(t * 1.25);
204203
if (root.current) {
205-
const y = 1.05 + bob * 0.045 + hop * 0.09;
204+
const y = 1.05 + bob * 0.018;
206205
root.current.position.y = y;
207-
const sx = 1 + bob * 0.025 - hop * 0.05;
208-
const sy = 1 - bob * 0.025 + hop * 0.07;
206+
const sx = 1 + bob * 0.008;
207+
const sy = 1 - bob * 0.008;
209208
root.current.scale.set(0.64 * sx, 0.64 * sy, 0.64 * sx);
210209
root.current.rotation.z = damp(root.current.rotation.z, Math.sin(t * 0.7) * 0.055 + (state === 'speaking' ? Math.sin(t * 2.0) * 0.02 : 0), 4, dt);
211210
root.current.position.z = damp(root.current.position.z, state === 'listening' ? -1.16 : -1.26, 5, dt);

lib/voice.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,9 @@ export function useVoice(opts: {
124124
const voices = voicesRef.current.length ? voicesRef.current : synth.getVoices();
125125
const voice = chosenVoiceRef.current ?? pickWarmVoice(voices);
126126
if (voice) u.voice = voice;
127-
// child-like: a higher pitch + slightly quicker pace = a cute, young voice
128-
u.rate = 1.04;
129-
u.pitch = 1.5;
127+
// youthful but composed — a gentle lift, not a cartoon
128+
u.rate = 1.0;
129+
u.pitch = 1.18;
130130
// Chrome quietly pauses long utterances after ~15s — keep nudging it.
131131
const keepAlive = setInterval(() => { try { if (synth.speaking) synth.resume(); else clearInterval(keepAlive); } catch { clearInterval(keepAlive); } }, 9000);
132132
const done = () => { clearInterval(keepAlive); onEnd?.(); };
@@ -180,7 +180,7 @@ export function useVoice(opts: {
180180
type PitchAudio = HTMLAudioElement & { preservesPitch?: boolean; mozPreservesPitch?: boolean; webkitPreservesPitch?: boolean };
181181
const pa = audio as PitchAudio;
182182
pa.preservesPitch = false; pa.mozPreservesPitch = false; pa.webkitPreservesPitch = false;
183-
audio.playbackRate = 1.16; // higher + a touch quicker → cute kid voice
183+
audio.playbackRate = 1.07; // a slight lift — youthful but still grounded
184184
audioRef.current = audio;
185185
audio.onended = () => { stopAudio(); finish(); };
186186
audio.onerror = () => { stopAudio(); if (!settled) browserSpeak(text, finish); };

0 commit comments

Comments
 (0)