> ## Documentation Index
> Fetch the complete documentation index at: https://docs.camb.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Emotional Voice Control

> Create emotionally nuanced speech with director-level control over tone, pacing, and character voice

export const UseCaseCard = ({src, title, description, prompt, badge, icon, href, resetOnPause = true, exclusive = true}) => {
  const audioRef = React.useRef(null);
  const [playing, setPlaying] = React.useState(false);
  const [progress, setProgress] = React.useState(0);
  const [hovering, setHovering] = React.useState(false);
  const uid = React.useMemo(() => 'u' + Math.random().toString(36).slice(2, 8), []);
  const hasAudio = !!src;
  React.useEffect(() => {
    const audio = audioRef.current;
    if (!audio || !hasAudio) return;
    const onTime = () => setProgress(audio.duration ? audio.currentTime / audio.duration : 0);
    const onEnd = () => {
      setPlaying(false);
      setProgress(0);
    };
    audio.addEventListener('timeupdate', onTime);
    audio.addEventListener('ended', onEnd);
    const onGlobalStop = e => {
      if (e.detail !== uid && !audio.paused) {
        audio.pause();
        audio.currentTime = 0;
        setPlaying(false);
        setProgress(0);
      }
    };
    if (exclusive) window.addEventListener('audio-player-stop', onGlobalStop);
    return () => {
      audio.removeEventListener('timeupdate', onTime);
      audio.removeEventListener('ended', onEnd);
      if (exclusive) window.removeEventListener('audio-player-stop', onGlobalStop);
    };
  }, [hasAudio, exclusive, uid]);
  const toggle = e => {
    if (e) e.stopPropagation();
    const audio = audioRef.current;
    if (!audio) return;
    if (audio.paused) {
      if (exclusive) window.dispatchEvent(new CustomEvent('audio-player-stop', {
        detail: uid
      }));
      audio.currentTime = 0;
      setProgress(0);
      audio.play().catch(() => setPlaying(false));
      setPlaying(true);
    } else {
      audio.pause();
      if (resetOnPause) {
        audio.currentTime = 0;
        setProgress(0);
      }
      setPlaying(false);
    }
  };
  const circleSize = 32;
  const strokeWidth = 2;
  const radius = (circleSize - strokeWidth) / 2;
  const circumference = 2 * Math.PI * radius;
  const strokeDashoffset = circumference * (1 - progress);
  return <div onClick={() => href && (window.location.href = href)} style={{
    borderRadius: '12px',
    border: '1px solid rgba(255,255,255,0.08)',
    background: 'rgba(255,255,255,0.02)',
    padding: '20px',
    display: 'flex',
    flexDirection: 'column',
    gap: '12px',
    cursor: href ? 'pointer' : 'default',
    transition: 'border-color 0.2s ease, background 0.2s ease'
  }} onMouseEnter={e => {
    if (href) {
      e.currentTarget.style.borderColor = 'rgba(236,85,18,0.25)';
      e.currentTarget.style.background = 'rgba(236,85,18,0.03)';
    }
  }} onMouseLeave={e => {
    if (href) {
      e.currentTarget.style.borderColor = 'rgba(255,255,255,0.08)';
      e.currentTarget.style.background = 'rgba(255,255,255,0.02)';
    }
  }}>
      <div style={{
    display: 'flex',
    alignItems: 'flex-start',
    justifyContent: 'space-between',
    gap: '12px'
  }}>
        <div style={{
    flex: 1,
    minWidth: 0
  }}>
          <div style={{
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
    marginBottom: '8px'
  }}>
            {icon && <span style={{
    fontSize: '18px'
  }}>{icon}</span>}
            <span style={{
    fontWeight: 600,
    fontSize: '14px',
    letterSpacing: '-0.01em'
  }}>{title}</span>
            {badge && <span style={{
    fontSize: '10px',
    fontWeight: 600,
    padding: '2px 7px',
    borderRadius: '4px',
    background: 'rgba(236,85,18,0.08)',
    color: '#EC5512',
    fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
    border: '1px solid rgba(236,85,18,0.12)',
    whiteSpace: 'nowrap'
  }}>{badge}</span>}
            {href && <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{
    opacity: 0.3,
    marginLeft: 'auto',
    flexShrink: 0
  }}><path d="M7 17L17 7" /><path d="M7 7h10v10" /></svg>}
          </div>
          {description && <div style={{
    fontSize: '13px',
    opacity: 0.55,
    lineHeight: '1.5',
    marginBottom: prompt ? '6px' : '0'
  }}>{description}</div>}
          {prompt && <div style={{
    fontSize: '12px',
    opacity: 0.4,
    lineHeight: '1.4',
    fontStyle: 'italic'
  }}>"{prompt}"</div>}
        </div>
        {hasAudio && <div style={{
    flexShrink: 0,
    position: 'relative',
    width: '32px',
    height: '32px',
    cursor: 'pointer',
    marginTop: '2px'
  }} onMouseEnter={() => setHovering(true)} onMouseLeave={() => setHovering(false)}>
            <svg width={circleSize} height={circleSize} style={{
    position: 'absolute',
    top: 0,
    left: 0,
    transform: 'rotate(-90deg)'
  }}>
              <circle cx={16} cy={16} r={radius} fill="none" stroke="rgba(255,255,255,0.08)" strokeWidth={strokeWidth} />
              <circle cx={16} cy={16} r={radius} fill="none" stroke={`url(#${uid})`} strokeWidth={strokeWidth} strokeDasharray={circumference} strokeDashoffset={strokeDashoffset} strokeLinecap="round" style={{
    transition: 'stroke-dashoffset 0.15s ease'
  }} />
              <defs><linearGradient id={uid} x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" stopColor="#EC5512" /><stop offset="100%" stopColor="#FF8A5C" /></linearGradient></defs>
            </svg>
            <button onClick={toggle} style={{
    position: 'absolute',
    top: '50%',
    left: '50%',
    transform: 'translate(-50%, -50%)',
    width: '24px',
    height: '24px',
    borderRadius: '50%',
    background: hovering ? 'linear-gradient(145deg, #F06020, #EC5512)' : 'linear-gradient(145deg, #EC5512, #D44A0F)',
    border: 'none',
    cursor: 'pointer',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    boxShadow: hovering ? '0 2px 10px rgba(236,85,18,0.4)' : '0 1px 4px rgba(236,85,18,0.2)',
    transition: 'all 0.2s ease',
    padding: playing ? '0' : '0 0 0 1px'
  }}>
              {playing ? <svg width="9" height="9" viewBox="0 0 24 24" fill="white" stroke="none"><rect x="6" y="4" width="4" height="16" rx="1" /><rect x="14" y="4" width="4" height="16" rx="1" /></svg> : <svg width="9" height="9" viewBox="0 0 24 24" fill="white" stroke="none"><polygon points="7 3 21 12 7 21" /></svg>}
            </button>
          </div>}
      </div>
      {hasAudio && <audio ref={audioRef} preload="none" src={src} />}
    </div>;
};

export const AudioGridItem = ({src, title, prompt, badge, resetOnPause = true, exclusive = true}) => {
  const audioRef = React.useRef(null);
  const [playing, setPlaying] = React.useState(false);
  const [progress, setProgress] = React.useState(0);
  const [hovering, setHovering] = React.useState(false);
  const [promptHover, setPromptHover] = React.useState(false);
  const uid = React.useMemo(() => 'g' + Math.random().toString(36).slice(2, 8), []);
  React.useEffect(() => {
    const audio = audioRef.current;
    if (!audio) return;
    const onTime = () => setProgress(audio.duration ? audio.currentTime / audio.duration : 0);
    const onEnd = () => {
      setPlaying(false);
      setProgress(0);
    };
    audio.addEventListener('timeupdate', onTime);
    audio.addEventListener('ended', onEnd);
    const onGlobalStop = e => {
      if (e.detail !== uid && !audio.paused) {
        audio.pause();
        audio.currentTime = 0;
        setPlaying(false);
        setProgress(0);
      }
    };
    if (exclusive) window.addEventListener('audio-player-stop', onGlobalStop);
    return () => {
      audio.removeEventListener('timeupdate', onTime);
      audio.removeEventListener('ended', onEnd);
      if (exclusive) window.removeEventListener('audio-player-stop', onGlobalStop);
    };
  }, []);
  const toggle = e => {
    if (e) e.stopPropagation();
    const audio = audioRef.current;
    if (!audio) return;
    if (audio.paused) {
      if (exclusive) window.dispatchEvent(new CustomEvent('audio-player-stop', {
        detail: uid
      }));
      audio.currentTime = 0;
      setProgress(0);
      audio.play().catch(() => setPlaying(false));
      setPlaying(true);
    } else {
      audio.pause();
      if (resetOnPause) {
        audio.currentTime = 0;
        setProgress(0);
      }
      setPlaying(false);
    }
  };
  const circleSize = 44;
  const strokeWidth = 2.5;
  const radius = (circleSize - strokeWidth) / 2;
  const circumference = 2 * Math.PI * radius;
  const strokeDashoffset = circumference * (1 - progress);
  return <div style={{
    borderRadius: '12px',
    border: '1px solid rgba(236,85,18,0.10)',
    background: 'linear-gradient(160deg, rgba(236,85,18,0.03) 0%, transparent 50%)',
    overflow: 'hidden'
  }}>
      <div style={{
    height: '2px',
    background: 'linear-gradient(90deg, transparent, #EC5512, transparent)',
    opacity: 0.5
  }} />
      <div style={{
    padding: '14px',
    display: 'flex',
    alignItems: 'center',
    gap: '12px'
  }}>
        <div style={{
    flexShrink: 0,
    position: 'relative',
    width: '44px',
    height: '44px',
    cursor: 'pointer'
  }} onMouseEnter={() => setHovering(true)} onMouseLeave={() => setHovering(false)}>
          <svg width={circleSize} height={circleSize} style={{
    position: 'absolute',
    top: 0,
    left: 0,
    transform: 'rotate(-90deg)'
  }}>
            <circle cx={22} cy={22} r={radius} fill="none" stroke="rgba(255,255,255,0.08)" strokeWidth={strokeWidth} />
            <circle cx={22} cy={22} r={radius} fill="none" stroke={`url(#${uid})`} strokeWidth={strokeWidth} strokeDasharray={circumference} strokeDashoffset={strokeDashoffset} strokeLinecap="round" style={{
    transition: 'stroke-dashoffset 0.15s ease'
  }} />
            <defs><linearGradient id={uid} x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" stopColor="#EC5512" /><stop offset="100%" stopColor="#FF8A5C" /></linearGradient></defs>
          </svg>
          <button onClick={toggle} style={{
    position: 'absolute',
    top: '50%',
    left: '50%',
    transform: 'translate(-50%, -50%)',
    width: '34px',
    height: '34px',
    borderRadius: '50%',
    background: hovering ? 'linear-gradient(145deg, #F06020, #EC5512)' : 'linear-gradient(145deg, #EC5512, #D44A0F)',
    border: 'none',
    cursor: 'pointer',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    boxShadow: hovering ? '0 2px 10px rgba(236,85,18,0.45)' : '0 1px 4px rgba(236,85,18,0.25)',
    transition: 'all 0.2s ease',
    padding: playing ? '0' : '0 0 0 1.5px'
  }}>
            {playing ? <svg width="11" height="11" viewBox="0 0 24 24" fill="white" stroke="none"><rect x="6" y="4" width="4" height="16" rx="1" /><rect x="14" y="4" width="4" height="16" rx="1" /></svg> : <svg width="11" height="11" viewBox="0 0 24 24" fill="white" stroke="none"><polygon points="7 3 21 12 7 21" /></svg>}
          </button>
        </div>
        <div style={{
    flex: 1,
    minWidth: 0
  }}>
          <div style={{
    display: 'flex',
    alignItems: 'center',
    gap: '6px',
    flexWrap: 'wrap'
  }}>
            <span style={{
    fontWeight: 600,
    fontSize: '13px',
    letterSpacing: '-0.01em',
    lineHeight: '1.2'
  }}>{title}</span>
            {badge && <span style={{
    fontSize: '10px',
    fontWeight: 600,
    padding: '1px 6px',
    borderRadius: '3px',
    background: 'rgba(236,85,18,0.08)',
    color: '#EC5512',
    fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
    border: '1px solid rgba(236,85,18,0.10)',
    whiteSpace: 'nowrap'
  }}>{badge}</span>}
          </div>
          {prompt && <div onMouseEnter={() => setPromptHover(true)} onMouseLeave={() => setPromptHover(false)} style={{
    position: 'relative',
    fontSize: '11.5px',
    fontStyle: 'italic',
    opacity: 0.4,
    marginTop: '2px',
    lineHeight: '1.35',
    whiteSpace: 'nowrap',
    overflow: 'hidden',
    textOverflow: 'ellipsis',
    cursor: 'default'
  }}>
              "{prompt}"
              {promptHover && <div style={{
    position: 'absolute',
    bottom: 'calc(100% + 6px)',
    left: 0,
    right: 0,
    background: 'rgba(20,20,20,0.95)',
    border: '1px solid rgba(236,85,18,0.2)',
    borderRadius: '6px',
    padding: '6px 8px',
    fontSize: '11px',
    fontStyle: 'italic',
    opacity: 1,
    color: 'rgba(255,255,255,0.75)',
    whiteSpace: 'normal',
    lineHeight: '1.4',
    zIndex: 10,
    boxShadow: '0 4px 12px rgba(0,0,0,0.4)'
  }}>"{prompt}"</div>}
            </div>}
        </div>
      </div>
      <audio ref={audioRef} preload="none" src={src} />
    </div>;
};

export const AudioGrid = ({children, cols = 2}) => <div style={{
  display: 'grid',
  gridTemplateColumns: `repeat(${cols}, 1fr)`,
  gap: '10px',
  marginBottom: '16px'
}}>{children}</div>;

export const AudioPlayer = ({src, title, prompt, badge, resetOnPause = true, exclusive = true}) => {
  const audioRef = React.useRef(null);
  const [playing, setPlaying] = React.useState(false);
  const [progress, setProgress] = React.useState(0);
  const [hovering, setHovering] = React.useState(false);
  const uid = React.useMemo(() => 'p' + Math.random().toString(36).slice(2, 8), []);
  React.useEffect(() => {
    const audio = audioRef.current;
    if (!audio) return;
    const onTime = () => setProgress(audio.duration ? audio.currentTime / audio.duration : 0);
    const onEnd = () => {
      setPlaying(false);
      setProgress(0);
    };
    audio.addEventListener('timeupdate', onTime);
    audio.addEventListener('ended', onEnd);
    const onGlobalStop = e => {
      if (e.detail !== uid && !audio.paused) {
        audio.pause();
        audio.currentTime = 0;
        setPlaying(false);
        setProgress(0);
      }
    };
    if (exclusive) window.addEventListener('audio-player-stop', onGlobalStop);
    return () => {
      audio.removeEventListener('timeupdate', onTime);
      audio.removeEventListener('ended', onEnd);
      if (exclusive) window.removeEventListener('audio-player-stop', onGlobalStop);
    };
  }, []);
  const toggle = e => {
    if (e) e.stopPropagation();
    const audio = audioRef.current;
    if (!audio) return;
    if (audio.paused) {
      if (exclusive) window.dispatchEvent(new CustomEvent('audio-player-stop', {
        detail: uid
      }));
      audio.currentTime = 0;
      setProgress(0);
      audio.play().catch(() => setPlaying(false));
      setPlaying(true);
    } else {
      audio.pause();
      if (resetOnPause) {
        audio.currentTime = 0;
        setProgress(0);
      }
      setPlaying(false);
    }
  };
  const circleSize = 52;
  const strokeWidth = 3;
  const radius = (circleSize - strokeWidth) / 2;
  const circumference = 2 * Math.PI * radius;
  const strokeDashoffset = circumference * (1 - progress);
  return <div style={{
    borderRadius: '14px',
    border: '1px solid rgba(236,85,18,0.12)',
    background: 'linear-gradient(160deg, rgba(236,85,18,0.04) 0%, transparent 40%, rgba(236,85,18,0.02) 100%)',
    overflow: 'hidden',
    marginBottom: '16px'
  }}>
      <div style={{
    height: '2px',
    background: 'linear-gradient(90deg, transparent, #EC5512 30%, #FF8A5C 50%, #EC5512 70%, transparent)'
  }} />
      <div style={{
    padding: '18px 22px 16px',
    display: 'flex',
    alignItems: 'center',
    gap: '16px'
  }}>
        <div style={{
    flexShrink: 0,
    position: 'relative',
    width: '52px',
    height: '52px',
    cursor: 'pointer'
  }} onMouseEnter={() => setHovering(true)} onMouseLeave={() => setHovering(false)}>
          <svg width={circleSize} height={circleSize} style={{
    position: 'absolute',
    top: 0,
    left: 0,
    transform: 'rotate(-90deg)'
  }}>
            <circle cx={26} cy={26} r={radius} fill="none" stroke="rgba(255,255,255,0.08)" strokeWidth={strokeWidth} />
            <circle cx={26} cy={26} r={radius} fill="none" stroke={`url(#${uid})`} strokeWidth={strokeWidth} strokeDasharray={circumference} strokeDashoffset={strokeDashoffset} strokeLinecap="round" style={{
    transition: 'stroke-dashoffset 0.15s ease'
  }} />
            <defs><linearGradient id={uid} x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" stopColor="#EC5512" /><stop offset="100%" stopColor="#FF8A5C" /></linearGradient></defs>
          </svg>
          <button onClick={toggle} style={{
    position: 'absolute',
    top: '50%',
    left: '50%',
    transform: 'translate(-50%, -50%)',
    width: '40px',
    height: '40px',
    borderRadius: '50%',
    background: hovering ? 'linear-gradient(145deg, #F06020, #EC5512)' : 'linear-gradient(145deg, #EC5512, #D44A0F)',
    border: 'none',
    cursor: 'pointer',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    boxShadow: hovering ? '0 2px 12px rgba(236,85,18,0.45)' : '0 1px 4px rgba(236,85,18,0.25)',
    transition: 'all 0.2s ease',
    padding: playing ? '0' : '0 0 0 1.5px'
  }}>
            {playing ? <svg width="14" height="14" viewBox="0 0 24 24" fill="white" stroke="none"><rect x="6" y="4" width="4" height="16" rx="1" /><rect x="14" y="4" width="4" height="16" rx="1" /></svg> : <svg width="14" height="14" viewBox="0 0 24 24" fill="white" stroke="none"><polygon points="7 3 21 12 7 21" /></svg>}
          </button>
        </div>
        <div style={{
    flex: 1,
    minWidth: 0
  }}>
          <div style={{
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
    flexWrap: 'wrap',
    marginBottom: prompt ? '2px' : '4px'
  }}>
            <span style={{
    fontWeight: 600,
    fontSize: '14px',
    letterSpacing: '-0.01em'
  }}>{title}</span>
            {badge && <span style={{
    fontSize: '10px',
    fontWeight: 600,
    padding: '2px 7px',
    borderRadius: '4px',
    background: 'rgba(236,85,18,0.08)',
    color: '#EC5512',
    fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
    letterSpacing: '0.02em',
    border: '1px solid rgba(236,85,18,0.12)',
    whiteSpace: 'nowrap'
  }}>{badge}</span>}
          </div>
          {prompt && <div style={{
    fontSize: '12.5px',
    opacity: 0.5,
    lineHeight: '1.4',
    fontStyle: 'italic'
  }}>"{prompt}"</div>}
        </div>
      </div>
      <audio ref={audioRef} preload="metadata" src={src} />
    </div>;
};

export const EmotionComparison = () => {
  const emotions = [{
    id: "neutral",
    label: "Neutral",
    badge: "No tag",
    text: "I can't believe we actually did it!",
    audio: "/audio/emotional/neutral.wav",
    instructions: null
  }, {
    id: "happy",
    label: "Happy",
    badge: "happy tone",
    text: "We won the match! This is the best day ever!",
    audio: "/audio/emotional/happy.wav",
    instructions: "happy, excited, celebrating"
  }, {
    id: "sad",
    label: "Sad",
    badge: "sad tone",
    text: "I... I don't know if I can do this anymore...",
    audio: "/audio/emotional/sad.wav",
    instructions: "sad, melancholic"
  }, {
    id: "laughing",
    label: "Laughing",
    badge: "[laughing]",
    text: "That's ridiculous! [laughing] I can't believe you just said that!",
    audio: "/audio/emotional/laughing.wav",
    instructions: null
  }, {
    id: "throat",
    label: "Clear Throat",
    badge: "ahem ahem",
    text: "So what I was going to say is... ahem ahem... never mind.",
    audio: "/audio/emotional/throat.wav",
    instructions: null
  }, {
    id: "sighing",
    label: "Sighing",
    badge: "[sighing]",
    text: "I guess we have to start over. [sighing] Alright, let's begin again.",
    audio: "/audio/emotional/sighing.wav",
    instructions: null
  }];
  const [activeEmotion, setActiveEmotion] = React.useState(emotions[0]);
  const audioRef = React.useRef(null);
  const [playing, setPlaying] = React.useState(false);
  const [progress, setProgress] = React.useState(0);
  const [hovering, setHovering] = React.useState(false);
  const uid = React.useMemo(() => 'e' + Math.random().toString(36).slice(2, 8), []);
  React.useEffect(() => {
    const audio = audioRef.current;
    if (!audio) return;
    const onTime = () => setProgress(audio.duration ? audio.currentTime / audio.duration : 0);
    const onEnd = () => {
      setPlaying(false);
      setProgress(0);
    };
    audio.addEventListener('timeupdate', onTime);
    audio.addEventListener('ended', onEnd);
    const onGlobalStop = e => {
      if (e.detail !== uid && !audio.paused) {
        audio.pause();
        audio.currentTime = 0;
        setPlaying(false);
        setProgress(0);
      }
    };
    window.addEventListener('audio-player-stop', onGlobalStop);
    return () => {
      audio.removeEventListener('timeupdate', onTime);
      audio.removeEventListener('ended', onEnd);
      window.removeEventListener('audio-player-stop', onGlobalStop);
    };
  }, []);
  const playEmotion = emotion => {
    setActiveEmotion(emotion);
    setPlaying(false);
    setProgress(0);
    setTimeout(() => {
      const audio = audioRef.current;
      if (audio) {
        window.dispatchEvent(new CustomEvent('audio-player-stop', {
          detail: uid
        }));
        audio.currentTime = 0;
        audio.play().catch(() => setPlaying(false));
        setPlaying(true);
      }
    }, 50);
  };
  const circleSize = 56;
  const strokeWidth = 3;
  const radius = (circleSize - strokeWidth) / 2;
  const circumference = 2 * Math.PI * radius;
  const strokeDashoffset = circumference * (1 - progress);
  return <div style={{
    borderRadius: '16px',
    border: '1px solid rgba(236,85,18,0.15)',
    background: 'linear-gradient(160deg, rgba(236,85,18,0.05) 0%, transparent 40%, rgba(236,85,18,0.02) 100%)',
    overflow: 'hidden',
    marginBottom: '24px'
  }}>
      <div style={{
    height: '2px',
    background: 'linear-gradient(90deg, transparent, #EC5512 30%, #FF8A5C 50%, #EC5512 70%, transparent)'
  }} />

      {}
      <div style={{
    padding: '20px 24px 16px',
    textAlign: 'center'
  }}>
        <p style={{
    fontSize: '18px',
    fontWeight: 600,
    fontStyle: 'italic',
    opacity: 0.9,
    lineHeight: '1.5'
  }}>"{activeEmotion.text}"</p>
      </div>

      {}
      <div style={{
    padding: '0 16px 16px',
    display: 'flex',
    flexWrap: 'wrap',
    gap: '8px',
    justifyContent: 'center'
  }}>
        {emotions.map(emotion => <button key={emotion.id} onClick={() => playEmotion(emotion)} style={{
    padding: '8px 14px',
    borderRadius: '8px',
    border: activeEmotion.id === emotion.id ? '1px solid #EC5512' : '1px solid rgba(255,255,255,0.1)',
    background: activeEmotion.id === emotion.id ? 'linear-gradient(145deg, #F06020, #EC5512)' : 'rgba(255,255,255,0.03)',
    color: activeEmotion.id === emotion.id ? 'white' : 'rgba(255,255,255,0.7)',
    fontSize: '13px',
    fontWeight: 500,
    cursor: 'pointer',
    transition: 'all 0.2s ease',
    boxShadow: activeEmotion.id === emotion.id ? '0 2px 8px rgba(236,85,18,0.3)' : 'none'
  }} onMouseEnter={e => {
    if (activeEmotion.id !== emotion.id) {
      e.currentTarget.style.borderColor = 'rgba(236,85,18,0.3)';
      e.currentTarget.style.background = 'rgba(236,85,18,0.08)';
    }
  }} onMouseLeave={e => {
    if (activeEmotion.id !== emotion.id) {
      e.currentTarget.style.borderColor = 'rgba(255,255,255,0.1)';
      e.currentTarget.style.background = 'rgba(255,255,255,0.03)';
    }
  }}>
            {emotion.label}
          </button>)}
      </div>

      {}
      <div style={{
    padding: '0 24px 20px',
    display: 'flex',
    alignItems: 'center',
    gap: '16px',
    justifyContent: 'center'
  }}>
        <div style={{
    flexShrink: 0,
    position: 'relative',
    width: '56px',
    height: '56px',
    cursor: 'pointer'
  }} onMouseEnter={() => setHovering(true)} onMouseLeave={() => setHovering(false)}>
          <svg width={circleSize} height={circleSize} style={{
    position: 'absolute',
    top: 0,
    left: 0,
    transform: 'rotate(-90deg)'
  }}>
            <circle cx={28} cy={28} r={radius} fill="none" stroke="rgba(255,255,255,0.08)" strokeWidth={strokeWidth} />
            <circle cx={28} cy={28} r={radius} fill="none" stroke={`url(#${uid})`} strokeWidth={strokeWidth} strokeDasharray={circumference} strokeDashoffset={strokeDashoffset} strokeLinecap="round" style={{
    transition: 'stroke-dashoffset 0.15s ease'
  }} />
            <defs><linearGradient id={uid} x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" stopColor="#EC5512" /><stop offset="100%" stopColor="#FF8A5C" /></linearGradient></defs>
          </svg>
          <button onClick={e => {
    e.stopPropagation();
    const audio = audioRef.current;
    if (!audio) return;
    if (audio.paused) {
      window.dispatchEvent(new CustomEvent('audio-player-stop', {
        detail: uid
      }));
      audio.currentTime = 0;
      audio.play().catch(() => setPlaying(false));
      setPlaying(true);
    } else {
      audio.pause();
      audio.currentTime = 0;
      setPlaying(false);
      setProgress(0);
    }
  }} style={{
    position: 'absolute',
    top: '50%',
    left: '50%',
    transform: 'translate(-50%, -50%)',
    width: '44px',
    height: '44px',
    borderRadius: '50%',
    background: hovering ? 'linear-gradient(145deg, #F06020, #EC5512)' : 'linear-gradient(145deg, #EC5512, #D44A0F)',
    border: 'none',
    cursor: 'pointer',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    boxShadow: hovering ? '0 2px 12px rgba(236,85,18,0.45)' : '0 1px 4px rgba(236,85,18,0.25)',
    transition: 'all 0.2s ease'
  }}>
            {playing ? <svg width="16" height="16" viewBox="0 0 24 24" fill="white" stroke="none"><rect x="6" y="4" width="4" height="16" rx="1" /><rect x="14" y="4" width="4" height="16" rx="1" /></svg> : <svg width="16" height="16" viewBox="0 0 24 24" fill="white" stroke="none"><polygon points="7 3 21 12 7 21" /></svg>}
          </button>
        </div>
      </div>
      <audio ref={audioRef} preload="metadata" src={activeEmotion.audio} />
    </div>;
};

MARS 8.1 Beta and `mars-instruct` both support expressive text controls, covering pronunciation overrides, non-verbal sounds, emotion tags, delivery hints, and SSML-style pauses. This page covers the syntax for each model.

## Hear the Examples

## MARS 8.1 Beta Text Controls

With `mars-8.1-flash-beta` and `mars-8.1-pro-beta`, you can add pronunciation and non-verbal controls directly in the text.

### Pronunciation Control (English)

Use CMU pronunciation dictionary phonemes in uppercase, wrapped in brackets, to override default English pronunciations.

```python Python theme={null}
response = client.text_to_speech.tts(
    text="Please [W AY1 N D] the clock before the strong [W IH1 N D] starts.",
    voice_id=147320,
    language="en-us",
    speech_model="mars-8.1-flash-beta",
    output_configuration=StreamTtsOutputConfiguration(format="wav")
)
```

```javascript TypeScript theme={null}
const response = await client.textToSpeech.tts({
  text: 'He plays the [B EY1 S] guitar while catching a [B AE1 S] fish.',
  voice_id: 147320,
  language: 'en-us',
  speech_model: 'mars-8.1-flash-beta',
  output_configuration: { format: 'wav' }
});
```

### Non-verbal Symbols

Insert supported tags directly in the text to add expressive non-verbal sounds.

```python Python theme={null}
response = client.text_to_speech.tts(
    text="[laughter] You really got me. I didn't see that coming at all.",
    voice_id=147320,
    language="en-us",
    speech_model="mars-8.1-flash-beta",
    output_configuration=StreamTtsOutputConfiguration(format="wav")
)
```

```javascript TypeScript theme={null}
const response = await client.textToSpeech.tts({
  text: '[laughter] You really got me. I didn\'t see that coming at all.',
  voice_id: 147320,
  language: 'en-us',
  speech_model: 'mars-8.1-flash-beta',
  output_configuration: { format: 'wav' }
});
```

## Supported tags for mars 8.1 :

Supported Tags: `[laughter]`, `[sigh]`, `[confirmation]`, `[question]`, `[surprise]`, `[dissatisfaction]`.

## Emotion Tags for Mars 8 Instruct:

`mars-instruct` accepts a richer set of tags for emotion, delivery, and sound effects.

### Emotion Tone Tags

For emotional tone (happy, sad, angry), use tags and match your text content to the emotion:

| Tag       | Example Text                                            |
| --------- | ------------------------------------------------------- |
| `[happy]` | "\[happy] We won the match! This is the best day ever!" |
| `[sad]`   | "\[sad] I... I don't know if I can do this anymore..."  |

**Important:** The text content and punctuation must match the emotion for best results.

### Sound Effect Tags

Sound effect tags go **within** your sentence where the action naturally occurs:

| Tag          | Example                                                           | Notes                          |
| ------------ | ----------------------------------------------------------------- | ------------------------------ |
| `[laughing]` | "That's ridiculous! \[laughing] I can't believe that!"            | Produces laughter sound        |
| `[sighing]`  | "I guess we have to start over. \[sighing] Alright, let's begin." | Produces sigh sound            |
| `ahem ahem`  | "So what I was going to say is... ahem ahem... never mind."       | Produces throat-clearing sound |

## Delivery Tags

Delivery tags provide tone guidance for the words that follow them.

| Tag                              | Effect                             |
| -------------------------------- | ---------------------------------- |
| `[shouting, angry, threatening]` | Agitated, confrontational delivery |
| `[whispering, secretive]`        | Quiet, intimate delivery           |
| `[empathetic, helpful]`          | Caring, supportive delivery        |
| `[happy, excited, promotional]`  | Upbeat, promotional delivery       |
| `[patient, teaching]`            | Educational, measured delivery     |

## Emotion Tag Gradation Guide

### How To Use This

* Read each tag list from left to right.
* Left side means more balanced, subtle, or restrained.
* Right side means more extreme, forceful, or obvious.
* If you want the strongest controllable result, start from the rightmost tag.
* If you want a more natural or less exaggerated result, move one or two steps left.

This is a practical TTS guide, not a dictionary guide. Some tags are ordered by how strongly they tend to push delivery, not just by literal meaning.

### Examples Of Use

1. `[angry] Who stole my cash!`
2. `[trembling] I don't know who did it...`
3. `[cheerful] Welcome back. I saved you a seat.`
4. `[commanding] Stop right there and listen carefully.`

### Tag Ladders

Balanced -> Extreme

1. Nervousness: `[uneasy]` -> `[nervous]` -> `[anxious]` -> `[trembling]`
2. Fear: `[fearful]` -> `[scared]` -> `[terrified]` -> `[panicked]`
3. Anger: `[irritated]` -> `[angry]` -> `[furious]` -> `[enraged]`
4. Sadness: `[down]` -> `[sad]` -> `[melancholic]` -> `[depressed]`
5. Joy: `[cheerful]` -> `[happy]` -> `[joyful]` -> `[delighted]`
6. Excitement: `[energetic]` -> `[excited]` -> `[thrilled]` -> `[hyped]`
7. Calmness: `[relaxed]` -> `[calm]` -> `[peaceful]` -> `[serene]`
8. Confidence: `[assured]` -> `[confident]` -> `[certain]` -> `[bold]`
9. Doubt: `[uncertain]` -> `[doubtful]` -> `[hesitant]` -> `[skeptical]`
10. Surprise: `[surprised]` -> `[startled]` -> `[shocked]` -> `[astonished]`
11. Disgust: `[grossed_out]` -> `[disgusted]` -> `[repulsed]` -> `[revolted]`
12. Pride: `[satisfied]` -> `[accomplished]` -> `[proud]` -> `[fulfilled]`
13. Shame: `[embarrassed]` -> `[guilty]` -> `[ashamed]` -> `[humiliated]`
14. Love: `[warm]` -> `[affectionate]` -> `[loving]` -> `[tender]`
15. Flirtation: `[charming]` -> `[playful]` -> `[flirty]` -> `[teasing]`
16. Sarcasm: `[dry]` -> `[ironic]` -> `[sarcastic]` -> `[mocking]`
17. Determination: `[focused]` -> `[determined]` -> `[driven]` -> `[resolute]`
18. Frustration: `[annoyed]` -> `[irritated]` -> `[frustrated]` -> `[exasperated]`
19. Relief: `[calmed]` -> `[reassured]` -> `[relieved]` -> `[grateful]`
20. Curiosity: `[interested]` -> `[curious]` -> `[inquiring]` -> `[intrigued]`
21. Boredom: `[dull]` -> `[uninterested]` -> `[bored]` -> `[apathetic]`
22. Awe: `[inspired]` -> `[amazed]` -> `[awed]` -> `[wonderstruck]`
23. Suspicion: `[wary]` -> `[suspicious]` -> `[guarded]` -> `[distrustful]`
24. Urgency: `[urgent]` -> `[rushed]` -> `[intense]` -> `[pressured]`
25. Authority: `[firm]` -> `[authoritative]` -> `[directive]` -> `[commanding]`
26. Politeness: `[polite]` -> `[courteous]` -> `[respectful]` -> `[formal]`
27. Gratitude: `[appreciative]` -> `[thankful]` -> `[grateful]` -> `[warm]`
28. Confusion: `[uncertain]` -> `[puzzled]` -> `[confused]` -> `[lost]`
29. Hopelessness: `[resigned]` -> `[defeated]` -> `[hopeless]` -> `[despairing]`
30. Playfulness: `[lighthearted]` -> `[playful]` -> `[fun]` -> `[silly]`

### Practical Rule Of Thumb

* Use the leftmost tag when you want the emotion to be present but not overpower the sentence.
* Use the middle tags when you want clear emotional color without sounding theatrical.
* Use the rightmost tag when you need the emotion to come through strongly and consistently.

Example:

* Nervousness, subtle: `[uneasy]`
* Nervousness, clear: `[anxious]`
* Nervousness, strongest: `[trembling]`

### How To Generalize This To New Emotions

This same principle generalizes well to new emotions:

* Start with 3 to 4 tags for the same emotional family.
* Arrange them from balanced to extreme.
* Test them on the same sentence.
* Keep the tag that gives the clearest emotional control without distorting the sentence too much.
* When in doubt, the most extreme tag often gives the strongest controllability.

General rule:

`same emotion family + left-to-right intensity ladder + same test sentence = reliable controllable TTS`

## Combining Tags

For precise control, combine multiple embedded emotion and delivery tags:

```python Python theme={null}
response = client.text_to_speech.tts(
    text="[sighing, secretive] I have a secret to tell you... [happy, excited] We're going to Paris!",
    voice_id=147320,
    language="en-us",
    speech_model="mars-instruct",
    output_configuration=StreamTtsOutputConfiguration(format="wav")
)
```

```javascript TypeScript theme={null}
const response = await client.textToSpeech.tts({
  text: '[sighing, secretive] I have a secret to tell you... [happy, excited] We\'re going to Paris!',
  voice_id: 147320,
  language: 'en-us',
  speech_model: 'mars-instruct',
  output_configuration: { format: 'wav' }
});
```

<AudioPlayer src="/audio/emotional/combined.wav" title="Hear the Example" prompt="[sighing] I have a secret to tell you... [happy] We're going to Paris!" badge="combined methods" />

## Pauses

Add SSML-style breaks anywhere in your text for dramatic pauses:

```text theme={null}
You... must... understand... this. <break time='600ms'/> The future begins NOW.
```

<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '10px', marginBottom: '16px' }}>
  <UseCaseCard src="/audio/emotional/audiobook.wav" title="Audiobook Narration" description="Warm, grandfatherly storytelling voice" icon="📚" badge="ahem ahem" />

  <UseCaseCard src="/audio/emotional/game.wav" title="Game Dialogue" description="Shouting, angry, threatening villain" icon="🎮" badge="angry tone" />

  <UseCaseCard src="/audio/emotional/marketing.wav" title="Marketing Voiceover" description="Energetic, enthusiastic promotion" icon="📣" badge="happy tone" />

  <UseCaseCard src="/audio/emotional/education.wav" title="Educational Content" description="Patient, clear teaching style" icon="🎓" badge="patient tone" />

  <UseCaseCard src="/audio/emotional/assistant.wav" title="Virtual Assistant" description="Empathetic, helpful response" icon="🤖" badge="[sighing]" />

  <UseCaseCard src="/audio/emotional/dubbing.wav" title="Film Dubbing" description="Dramatic delivery with pauses" icon="🎬" badge="dramatic + pause" />
</div>

## Best Practices

1. **Use specific tags** - Place concise delivery tags near the sentence they should affect
2. **Match content to emotion** - Text and punctuation should reflect the emotional tone
3. **Place sound effects naturally** - Tags like `[laughing]`, `[sighing]` work best within sentences
4. **Keep tags short** - Tags like `[happy]`, `[sad]`, or `[whispering]` work best when focused
5. **Add pauses** - Use `<break time='600ms'/>` for dramatic effect

## Next Steps

<CardGroup cols={2}>
  <Card title="Text to Speech" icon="audio-lines" href="/tutorials/tts-with-sdk">
    Get started with basic TTS using the Python or TypeScript SDK.
  </Card>

  <Card title="Choosing a Model" icon="bolt" href="/choosing-a-model">
    Compare mars-instruct with mars-flash and mars-pro.
  </Card>

  <Card title="Voice Cloning" icon="mic" href="/tutorials/voice-cloning">
    Create custom voices for your emotional speech.
  </Card>

  <Card title="TTS with Accents" icon="globe" href="/tutorials/tts-with-accents">
    Generate speech in 140+ language accents.
  </Card>
</CardGroup>
