> ## 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.

# TTS with Accents

> Generate natural speech across the MARS language accents

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>;

## Overview

Generate speech with different language accents. The `language` parameter controls the accent/pronunciation style of the output — see [Language Support](/language-support) for the full per-model locale list.

The TTS API speaks the text you provide - it does **not** translate. To speak in a different language, provide input text in that language. For automatic translation + speech, use the [Translated TTS API](/api-reference/endpoint/create-translated-tts).

### Hear the Difference

Same text, different accents - all with the same voice:

<AudioGrid cols={2}>
  <AudioGridItem src="/audio/demo-accent-en-us.wav" title="English (US)" prompt="Welcome to our service. We're glad to have you here." badge="en-us" />

  <AudioGridItem src="/audio/demo-accent-en-gb.wav" title="English (UK)" prompt="Welcome to our service. We're glad to have you here." badge="en-gb" />

  <AudioGridItem src="/audio/demo-accent-fr-fr.wav" title="French" prompt="Welcome to our service. We're glad to have you here." badge="fr-fr" />

  <AudioGridItem src="/audio/demo-accent-es-es.wav" title="Spanish" prompt="Welcome to our service. We're glad to have you here." badge="es-es" />
</AudioGrid>

### Prerequisites

<Steps>
  <Step title="Create an account">
    Sign up at [CAMB.AI Studio](https://studio.camb.ai) if you haven't already.
  </Step>

  <Step title="Get your API key">
    Go to **Settings → API Keys** in Studio and copy your key. See [Authentication](/getting-started/authentication) for details.
  </Step>

  <Step title="Install the SDK">
    <CodeGroup>
      ```bash Python theme={null}
      pip install camb-sdk
      ```

      ```bash TypeScript theme={null}
      npm install @camb-ai/sdk
      ```
    </CodeGroup>

    Skip this step if you're using the [direct API](/tutorials/direct-api).
  </Step>

  <Step title="Set your API key to use in your code">
    ```bash theme={null}
    export CAMB_API_KEY="your_api_key_here"
    ```
  </Step>
</Steps>

***

## Code

<CodeGroup>
  ```python Python theme={null}
  import os
  import asyncio
  from camb.client import AsyncCambAI, save_async_stream_to_file
  from camb.types import StreamTtsOutputConfiguration

  client = AsyncCambAI(api_key=os.getenv("CAMB_API_KEY"))

  # Languages to generate
  LANGUAGES = [
      ("en-us", "English"),
      ("es-es", "Spanish"),
      ("fr-fr", "French"),
      ("pt-br", "Portuguese"),
  ]

  async def generate_language(text, lang_code, lang_name):
      """Generate TTS for a single language"""
      print(f"Generating {lang_name}...")
      response = client.text_to_speech.tts(
          text=text,
          language=lang_code,
          speech_model="mars-8.1-flash-beta",
          voice_id=144300,  # Replace with your voice ID
          output_configuration=StreamTtsOutputConfiguration(format="wav")
      )
      filename = f"output_{lang_code}.wav"
      await save_async_stream_to_file(response, filename)
      print(f"  Saved: {filename}")
      return filename

  async def main():
      text = "Welcome to our service. We're glad to have you here."

      print(f"Generating TTS in {len(LANGUAGES)} languages...\n")

      # Generate all languages concurrently
      tasks = [
          generate_language(text, code, name)
          for code, name in LANGUAGES
      ]
      files = await asyncio.gather(*tasks)

      print(f"\nDone! Generated {len(files)} audio files.")

  asyncio.run(main())
  ```

  ```javascript TypeScript theme={null}
  import { CambClient, saveStreamToFile } from '@camb-ai/sdk';

  const client = new CambClient({
      apiKey: process.env.CAMB_API_KEY
  });

  // Languages to generate
  const LANGUAGES = [
      { code: 'en-us', name: 'English' },
      { code: 'es-es', name: 'Spanish' },
      { code: 'fr-fr', name: 'French' },
      { code: 'pt-br', name: 'Portuguese' },
  ];

  async function generateLanguage(text, langCode, langName) {
      console.log(`Generating ${langName}...`);
      const response = await client.textToSpeech.tts({
          text: text,
          language: langCode,
          speech_model: 'mars-8.1-flash-beta',
          voice_id: 144300,  // Replace with your voice ID
          output_configuration: {
              format: 'wav'
          },
      });
      const filename = `output_${langCode}.wav`;
      await saveStreamToFile(response, filename);
      console.log(`  Saved: ${filename}`);
      return filename;
  }

  async function main() {
      const text = "Welcome to our service. We're glad to have you here.";

      console.log(`Generating TTS in ${LANGUAGES.length} languages...\n`);

      // Generate all languages concurrently
      const promises = LANGUAGES.map(lang =>
          generateLanguage(text, lang.code, lang.name)
      );
      const files = await Promise.all(promises);

      console.log(`\nDone! Generated ${files.length} audio files.`);
  }

  main();
  ```
</CodeGroup>

***

## Supported Accents

Over 140 language accents are supported across CAMB.AI TTS. Common codes:

| Accent              | Code    |
| ------------------- | ------- |
| English (US)        | `en-us` |
| English (UK)        | `en-gb` |
| Spanish (Spain)     | `es-es` |
| Spanish (Mexico)    | `es-mx` |
| French              | `fr-fr` |
| German              | `de-de` |
| Italian             | `it-it` |
| Portuguese (Brazil) | `pt-br` |
| Japanese            | `ja-jp` |
| Korean              | `ko-kr` |
| Chinese (Mandarin)  | `zh-cn` |
| Hindi               | `hi-in` |
| Arabic              | `ar-sa` |
| Russian             | `ru-ru` |

See [API Reference](/api-reference/endpoint/get-target-languages) for the broader TTS language list.

If you are using `POST /tts-stream`, check the streaming API reference for endpoint-specific support and model-specific constraints.

***

## Use Cases

<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '10px', marginBottom: '16px' }}>
  <UseCaseCard src="/audio/demo-accent-en-us.wav" title="Accent Variation" description="Same content, localized pronunciation for different markets" icon="🌍" badge="en-us" />

  <UseCaseCard src="/audio/demo-accent-en-gb.wav" title="Character Voices" description="Give characters distinct accents in games or audiobooks" icon="🎭" badge="en-gb" />

  <UseCaseCard src="/audio/demo-accent-fr-fr.wav" title="Language Learning" description="Hear native pronunciation for educational content" icon="📚" badge="fr-fr" />

  <UseCaseCard src="/audio/demo-accent-es-es.wav" title="Localized Content" description="Produce region-specific audio for global audiences" icon="📡" badge="es-es" />
</div>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Emotional Voice Control" icon="sparkles" href="/tutorials/emotional-voice-control">
    Add emotional expression and character to your speech with mars-instruct.
  </Card>

  <Card title="Translated TTS" icon="languages" href="/tutorials/translated-tts">
    Translate text and generate speech in the target language.
  </Card>

  <Card title="Voice Cloning" icon="mic" href="/tutorials/voice-cloning">
    Clone a voice from reference audio.
  </Card>

  <Card title="Text to Sound Effects" icon="volume-2" href="/tutorials/text-to-sound">
    Generate sound effects and music from text.
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference/endpoint/create-tts-stream">
    Full TTS streaming API specification.
  </Card>
</CardGroup>
