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

# Text to Speech

> Convert text to natural-sounding speech with Fish Audio

export const AudioTranscript = ({voices, page}) => {
  const resolvedVoices = voices?.length ? voices : (() => {
    if (!page) return [];
    const baseUrl = 'https://pub-b995142090474379a930b856ab79b4d4.r2.dev/audio';
    const pageVoices = [{
      id: '8ef4a238714b45718ce04243307c57a7',
      name: 'E-girl'
    }, {
      id: '802e3bc2b27e49c2995d23ef70e6ac89',
      name: 'Energetic Male'
    }, {
      id: '933563129e564b19a115bedd57b7406a',
      name: 'Sarah'
    }, {
      id: 'bf322df2096a46f18c579d0baa36f41d',
      name: 'Adrian'
    }, {
      id: 'b347db033a6549378b48d00acb0d06cd',
      name: 'Selene'
    }, {
      id: '536d3a5e000945adb7038665781a4aca',
      name: 'Ethan'
    }];
    return pageVoices.map(voice => ({
      ...voice,
      url: `${baseUrl}/${page}/${voice.id}.mp3`
    }));
  })();
  const [selectedVoice, setSelectedVoice] = useState(0);
  const [isPlaying, setIsPlaying] = useState(false);
  const [currentTime, setCurrentTime] = useState(0);
  const [duration, setDuration] = useState(0);
  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
  const audioRef = useRef(null);
  const dropdownRef = useRef(null);
  useEffect(() => {
    const audio = audioRef.current;
    if (!audio) return;
    const updateTime = () => setCurrentTime(audio.currentTime);
    const updateDuration = () => setDuration(audio.duration);
    const handleEnded = () => setIsPlaying(false);
    audio.addEventListener('timeupdate', updateTime);
    audio.addEventListener('loadedmetadata', updateDuration);
    audio.addEventListener('ended', handleEnded);
    return () => {
      audio.removeEventListener('timeupdate', updateTime);
      audio.removeEventListener('loadedmetadata', updateDuration);
      audio.removeEventListener('ended', handleEnded);
    };
  }, []);
  useEffect(() => {
    const handleClickOutside = event => {
      if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
        setIsDropdownOpen(false);
      }
    };
    if (isDropdownOpen) {
      document.addEventListener('mousedown', handleClickOutside);
    }
    return () => {
      document.removeEventListener('mousedown', handleClickOutside);
    };
  }, [isDropdownOpen]);
  useEffect(() => {
    if (audioRef.current) {
      audioRef.current.pause();
      audioRef.current.load();
      setIsPlaying(false);
      setCurrentTime(0);
    }
  }, [selectedVoice]);
  const togglePlay = () => {
    if (isPlaying) {
      audioRef.current.pause();
    } else {
      audioRef.current.play();
    }
    setIsPlaying(!isPlaying);
  };
  const handleProgressChange = e => {
    const newTime = parseFloat(e.target.value);
    audioRef.current.currentTime = newTime;
    setCurrentTime(newTime);
  };
  const formatTime = time => {
    if (isNaN(time)) return '0:00';
    const minutes = Math.floor(time / 60);
    const seconds = Math.floor(time % 60);
    return `${minutes}:${seconds.toString().padStart(2, '0')}`;
  };
  const currentVoice = resolvedVoices[selectedVoice];
  return <div className="border rounded-lg bg-card border-gray-200 dark:border-gray-800">
      {}
      <div className="grid grid-cols-3 items-center px-3 py-1.5 bg-muted border-b border-gray-200 dark:border-gray-800">
        <span className="text-xs font-medium">Listen to Page</span>

        <span className="text-xs font-semibold text-muted-foreground text-center">Powered by Fish Audio S2 Pro</span>

        {resolvedVoices.length > 1 ? <div className="relative justify-self-end" ref={dropdownRef}>
            <button onClick={() => setIsDropdownOpen(!isDropdownOpen)} className="flex items-center gap-1.5 px-3 py-1 rounded-full bg-muted hover:bg-gray-200 dark:hover:bg-gray-700 transition-all duration-200 cursor-pointer text-xs">
              <span className="text-muted-foreground">Voice:</span>
              <span className="font-medium">{resolvedVoices[selectedVoice]?.name}</span>
              <svg className={`w-3 h-3 transition-transform duration-200 ${isDropdownOpen ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
              </svg>
            </button>

            {isDropdownOpen && <div className="absolute right-0 mt-1 w-auto bg-white dark:bg-black border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden z-50">
                {resolvedVoices.map((voice, index) => <button key={index} onClick={() => {
    setSelectedVoice(index);
    setIsDropdownOpen(false);
  }} className={`w-full px-3 py-1.5 text-left text-xs hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors flex items-center gap-2 ${index === selectedVoice ? 'bg-gray-100 dark:bg-gray-800 font-medium' : ''}`}>
                    {voice.id && <img src={`https://public-platform.r2.fish.audio/coverimage/${voice.id}`} alt={voice.name} className="w-5 h-5 rounded-full m-0 flex-shrink-0 object-cover" />}
                    <span className="flex-1 whitespace-nowrap">{voice.name}</span>
                  </button>)}
              </div>}
          </div> : <div className="justify-self-end" />}
      </div>

      {}
      <div className="px-3 py-1.5 bg-card">
        <audio ref={audioRef} src={currentVoice?.url} preload="metadata" />

        <div className="flex items-center gap-2">
          {}
          <button onClick={togglePlay} className="flex-shrink-0 w-6 h-6 flex items-center justify-center bg-gray-300 dark:bg-gray-600 text-gray-800 dark:text-gray-200 rounded-full hover:opacity-80 transition-opacity relative overflow-hidden" aria-label={isPlaying ? 'Pause' : 'Play'}>
            <div className="transition-transform duration-300 ease-in-out" style={{
    transform: isPlaying ? 'rotate(180deg)' : 'rotate(0deg)'
  }}>
              {isPlaying ? <svg className="w-3 h-3" fill="currentColor" viewBox="0 0 24 24">
                  <path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z" />
                </svg> : <svg className="w-3 h-3 ml-0.5" fill="currentColor" viewBox="0 0 24 24">
                  <path d="M8 5v14l11-7z" />
                </svg>}
            </div>
          </button>

          {}
          <div className="flex-1 flex items-center gap-2">
            <span className="text-xs font-mono text-gray-500 dark:text-gray-400 min-w-[35px]">
              {formatTime(currentTime)}
            </span>

            <div className="flex-1 relative h-1 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
              <div className="absolute top-0 left-0 h-full bg-gray-400 dark:bg-gray-500 transition-all duration-100" style={{
    width: `${duration ? currentTime / duration * 100 : 0}%`
  }} />
              <input type="range" min="0" max={duration || 0} value={currentTime} onChange={handleProgressChange} className="absolute top-0 left-0 w-full h-full opacity-0 cursor-pointer" />
            </div>
            <span className="text-xs font-mono text-gray-500 dark:text-gray-400 min-w-[35px]">
              {formatTime(duration)}
            </span>
          </div>
        </div>
      </div>
    </div>;
};

export const AudioSample = () => {
  const [isPlaying, setIsPlaying] = useState(false);
  const [currentTime, setCurrentTime] = useState(0);
  const [duration, setDuration] = useState(0);
  const audioRef = useRef(null);
  useEffect(() => {
    const audio = audioRef.current;
    if (!audio) return;
    const updateTime = () => setCurrentTime(audio.currentTime);
    const updateDuration = () => setDuration(audio.duration);
    const handleEnded = () => setIsPlaying(false);
    audio.addEventListener('timeupdate', updateTime);
    audio.addEventListener('loadedmetadata', updateDuration);
    audio.addEventListener('ended', handleEnded);
    return () => {
      audio.removeEventListener('timeupdate', updateTime);
      audio.removeEventListener('loadedmetadata', updateDuration);
      audio.removeEventListener('ended', handleEnded);
    };
  }, []);
  const togglePlay = () => {
    if (isPlaying) {
      audioRef.current.pause();
    } else {
      audioRef.current.play();
    }
    setIsPlaying(!isPlaying);
  };
  const skip = seconds => {
    audioRef.current.currentTime = Math.max(0, Math.min(duration, currentTime + seconds));
  };
  const handleProgressChange = e => {
    const newTime = parseFloat(e.target.value);
    audioRef.current.currentTime = newTime;
    setCurrentTime(newTime);
  };
  const formatTime = time => {
    if (isNaN(time)) return '0:00';
    const minutes = Math.floor(time / 60);
    const seconds = Math.floor(time % 60);
    return `${minutes}:${seconds.toString().padStart(2, '0')}`;
  };
  return <div>
      <p className="text-md text-gray-600 dark:text-gray-400 mb-2">Listen to a sample:</p>
      <div className="flex items-center gap-3 p-4 rounded-lg">
      <audio ref={audioRef} src="/snippets/audio_sample.mp3" preload="metadata" />
      
      <button onClick={togglePlay} className="flex-shrink-0 w-10 h-10 flex items-center justify-center bg-primary text-white rounded-full hover:opacity-90 transition-opacity" aria-label={isPlaying ? 'Pause' : 'Play'}>
        {isPlaying ? <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
            <path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z" />
          </svg> : <svg className="w-5 h-5 ml-0.5" fill="currentColor" viewBox="0 0 24 24">
            <path d="M8 5v14l11-7z" />
          </svg>}
      </button>

      <div className="flex-1 flex items-center gap-3">
        <span className="text-sm font-mono text-gray-600 dark:text-gray-400 min-w-[40px]">
          {formatTime(currentTime)}
        </span>

        <button onClick={() => skip(-10)} className="flex-shrink-0 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200" aria-label="Rewind 10 seconds">
          <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
            <path d="M11.99 5V1l-5 5 5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6h-2c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z" />
          </svg>
        </button>

        <div className="flex-1 relative h-2 bg-gray-300 dark:bg-gray-600 rounded-full overflow-hidden">
          <div className="absolute top-0 left-0 h-full bg-blue-500 dark:bg-blue-400 transition-all duration-100" style={{
    width: `${duration ? currentTime / duration * 100 : 0}%`
  }} />
          <input type="range" min="0" max={duration || 0} value={currentTime} onChange={handleProgressChange} className="absolute top-0 left-0 w-full h-full opacity-0 cursor-pointer" />
        </div>

        <button onClick={() => skip(10)} className="flex-shrink-0 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200" aria-label="Forward 10 seconds">
          <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
            <path d="M12 5V1l5 5-5 5V7c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6h2c0 4.42-3.58 8-8 8s-8-3.58-8-8 3.58-8 8-8z" />
          </svg>
        </button>

        <span className="text-sm font-mono text-gray-600 dark:text-gray-400 min-w-[40px]">
          {formatTime(duration)}
        </span>
      </div>
      </div>
    </div>;
};

## Overview

Transform any text into natural, expressive speech using Fish Audio's advanced TTS models. Choose from pre-made voices or use your own cloned voices.

Discover the world's best cloned voices models on our [Discovery](https://fish.audio/discovery) page.

## Quick Start

### Web Interface

The easiest way to generate speech:

<Steps>
  <Step title="Visit Playground">
    Go to [fish.audio](https://fish.audio) and log in
  </Step>

  <Step title="Enter Your Text">
    Type or paste the text you want to convert
  </Step>

  <Step title="Choose a Voice">
    Select from available voices or use your own
  </Step>

  <Step title="Generate">
    Click "Generate" and download your audio
  </Step>
</Steps>

## Using the SDK

<Tabs>
  <Tab title="Python">
    <Steps>
      <Step title="Install the SDK">
        ```bash theme={null}
        pip install fish-audio-sdk
        ```
      </Step>

      <Step title="Basic Usage">
        Generate speech with just a few lines of code:

        ```python theme={null}
        from fishaudio import FishAudio
        from fishaudio.utils import save

        # Initialize client
        client = FishAudio(api_key="your_api_key_here")

        # Generate speech
        audio = client.tts.convert(
            text="Hello, world!",
            reference_id="your_voice_model_id"
        )
        save(audio, "output.mp3")

        print("✓ Audio saved to output.mp3")
        ```
      </Step>
    </Steps>
  </Tab>

  <Tab title="JavaScript">
    <Steps>
      <Step title="Install the SDK">
        ```bash theme={null}
        npm install fish-audio
        ```
      </Step>

      <Step title="Basic Usage">
        Generate speech with just a few lines of code:

        ```javascript theme={null}
        import { FishAudioClient } from "fish-audio";
        import { writeFile } from "fs/promises";

        // Initialize session
        const fishAudio = new FishAudioClient({ apiKey: "your_api_key_here" });

        const audio = await fishAudio.textToSpeech.convert({
            text: "Hello, world!",
            reference_id: "your_voice_model_id",
        });

        const buffer = Buffer.from(await new Response(audio).arrayBuffer());
        await writeFile("output.mp3", buffer);

        console.log("✓ Audio saved to output.mp3");
        ```
      </Step>
    </Steps>
  </Tab>
</Tabs>

## Voice Options

### Using Pre-made Voices

Browse and select voices from the playground:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    # Use a voice from the playground
    audio = client.tts.convert(
        text="Welcome to Fish Audio!",
        reference_id="7f92f8afb8ec43bf81429cc1c9199cb1"
    )
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    # Use a voice from the playground
    const audio = await fishAudio.textToSpeech.convert({
        text: "Welcome to Fish Audio!",
        reference_id: "7f92f8afb8ec43bf81429cc1c9199cb1",
    });
    ```
  </Tab>
</Tabs>

### Using Your Cloned Voice

Use voices you've created:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    # Use your own cloned voice
    audio = client.tts.convert(
        text="This is my custom voice speaking",
        reference_id="your_model_id"
    )
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    # Use your own cloned voice
    const audio = await fishAudio.textToSpeech.convert({
        text: "This is my custom voice speaking",
        reference_id: "your_model_id",
    });
    ```
  </Tab>
</Tabs>

### Using Reference Audio

Provide reference audio directly:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from fishaudio.types import ReferenceAudio

    # Use reference audio on-the-fly
    with open("voice_sample.wav", "rb") as f:
        audio = client.tts.convert(
            text="Hello from reference audio",
            references=[
                ReferenceAudio(
                    audio=f.read(),
                    text="Sample text from the audio"
                )
            ]
        )
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    // Use reference audio on-the-fly
    const fileBuffer = await readFile("voice_sample.wav");
    const voiceFile = new File([fileBuffer], "voice_sample.wav");

    const audio = await fishAudio.textToSpeech.convert({
        text: "Hello from reference audio",
        references: [
            { audio: voiceFile, text: "Sample text from the audio" }
        ]
    });
    ```
  </Tab>
</Tabs>

## Model Selection

Choose the right model for your needs:

| Model      | Best For        | Quality   | Speed   |
| ---------- | --------------- | --------- | ------- |
| **s1**     | Prototyping     | Excellent | Fast    |
| **s2-pro** | Latest features | Excellent | Fastest |

Specify a model in your request:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    # Using the latest model (default)
    audio = client.tts.convert(text="Hello world")
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    // Using the latest S2-Pro model
    const audio = await fishAudio.textToSpeech.convert(
        { text: "Hello world" },
        "s2-pro"
    );
    ```
  </Tab>
</Tabs>

## Advanced Options

### Audio Formats

Choose your output format:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    audio = client.tts.convert(
        text="Your text here",
        format="mp3",  # Options: "mp3", "wav", "pcm", "opus"
        mp3_bitrate=128  # For MP3: 64, 128, or 192
    )
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    const audio = await fishAudio.textToSpeech.convert({
        text: "Your text here",
        format: "mp3", // Options: "mp3", "wav", "pcm", "opus"
        mp3_bitrate: 128, // For MP3: 64, 128, or 192
    });
    ```
  </Tab>
</Tabs>

### Chunk Length

Control text processing chunks:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    audio = client.tts.convert(
        text="Long text content...",
        chunk_length=200  # 100-300 characters per chunk
    )
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    const audio = await fishAudio.textToSpeech.convert({
        text: "Long text content...",
        chunk_length: 200, // 100-300 characters per chunk
    });
    ```
  </Tab>
</Tabs>

### Latency Mode

Optimize for speed or quality:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    audio = client.tts.convert(
        text="Quick response needed",
        latency="balanced"  # "normal" or "balanced"
    )
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    const audio = await fishAudio.textToSpeech.convert({
        text: "Quick response needed",
        latency: "balanced", // "normal" or "balanced"
    });
    ```
  </Tab>
</Tabs>

<Note>
  Balanced mode reduces latency to \~300ms but may slightly decrease stability.
</Note>

## Direct API Usage

For direct API calls without the SDK:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import httpx
    import ormsgpack

    # Prepare request
    request_data = {
        "text": "Hello, world!",
        "reference_id": "your_model_id",
        "format": "mp3"
    }

    # Make API call
    with httpx.Client() as client:
        response = client.post(
            "https://api.fish.audio/v1/tts",
            content=ormsgpack.packb(request_data),
            headers={
                "authorization": "Bearer YOUR_API_KEY",
                "content-type": "application/msgpack",
                "model": "s2-pro"
            }
        )
        
        # Save audio
        with open("output.mp3", "wb") as f:
            f.write(response.content)
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    import { encode } from "@msgpack/msgpack";
    import { writeFile } from "fs/promises";

    const body = encode({
        text: "Hello, world!",
        reference_id: "your_model_id",
        format: "mp3",
    });

    const res = await fetch("https://api.fish.audio/v1/tts", {
        method: "POST",
        headers: {
            Authorization: "Bearer <YOUR_API_KEY>",
            "Content-Type": "application/msgpack",
            model: "s2-pro",
        },
        body,
    });

    const buffer = Buffer.from(await res.arrayBuffer());
    await writeFile("output.mp3", buffer);
    ```
  </Tab>
</Tabs>

## Streaming Audio

Stream audio for real-time applications:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    # Stream audio chunks
    audio_stream = client.tts.stream(
        text="Streaming this text in real-time",
        reference_id="model_id"
    )

    with open("stream_output.mp3", "wb") as f:
        for chunk in audio_stream:
            f.write(chunk)
            # Process chunk immediately for real-time playback
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    // Use a Websocket to stream real-time audio

    import { FishAudioClient, RealtimeEvents } from "fish-audio";
    import { writeFile } from "fs/promises";
    import path from "path";

    // Simple async generator that yields text chunks
    async function* makeTextStream() {
        const chunks = [
            "Hello from Fish Audio! ",
            "This is a realtime text-to-speech test. ",
            "We are streaming multiple chunks over WebSocket.",
        ];
        for (const chunk of chunks) {
            yield chunk;
        }
    }

    const fishAudio = new FishAudioClient({ apiKey: process.env.FISH_API_KEY });

    // For realtime, set text to "" and stream the content via makeTextStream
    const request = { text: "" };

    const connection = await fishAudio.textToSpeech.convertRealtime(request, makeTextStream());

    // Collect audio and write to a file when the stream ends
    const chunks: Buffer[] = [];
    connection.on(RealtimeEvents.OPEN, () => console.log("WebSocket opened"));
    connection.on(RealtimeEvents.AUDIO_CHUNK, (audio: unknown): void => {
        if (audio instanceof Uint8Array || Buffer.isBuffer(audio)) {
            chunks.push(Buffer.from(audio));
        }
    });
    connection.on(RealtimeEvents.ERROR, (err) => console.error("WebSocket error:", err));
    connection.on(RealtimeEvents.CLOSE, async () => {
        const outPath = path.resolve(process.cwd(), "out.mp3");
        await writeFile(outPath, Buffer.concat(chunks));
        console.log("Saved to", outPath);
    });
    ```
  </Tab>
</Tabs>

## Adding Emotions

<Tip>
  The `(parenthesis)` syntax below applies to the S1 model. S2 uses `[bracket]` syntax with natural language descriptions and is not limited to a fixed set of tags. See the [Models Overview](/developer-guide/models-pricing/models-overview#s2-natural-language-control) for details.
</Tip>

Make your speech more expressive:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    # Add emotion markers to your text
    emotional_text = """
    (excited) I just won the lottery!
    (sad) But then I lost the ticket.
    (laughing) Just kidding, I found it!
    """

    audio = client.tts.convert(
        text=emotional_text,
        reference_id="model_id"
    )
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    // Add emotion markers to your text
    const emotionalText = `(excited) I just won the lottery!
    (sad) But then I lost the ticket.
    (laughing) Just kidding, I found it!`;

    const audio = await fishAudio.textToSpeech.convert({
        text: emotionalText,
        reference_id: "model_id",
    });
    ```
  </Tab>
</Tabs>

Available emotions:

* Basic: `(happy)`, `(sad)`, `(angry)`, `(excited)`, `(calm)`
* Tones: `(shouting)`, `(whispering)`, `(soft tone)`
* Effects: `(laughing)`, `(sighing)`, `(crying)`

For more precise control over pronunciation and additional paralanguage features like pauses and breathing, see [Fine-grained Control](/developer-guide/core-features/fine-grained-control).

## Best Practices

### Text Preparation

**Do:**

* Use proper punctuation for natural pauses
* Add emotion markers for expression
* Break long texts into paragraphs
* Use consistent formatting

**Don't:**

* Use ALL CAPS (unless shouting)
* Mix multiple languages randomly
* Include special characters unnecessarily
* Forget punctuation

### Performance Tips

1. **Batch Processing:** Process multiple texts efficiently
2. **Cache Models:** Store frequently used model IDs
3. **Optimize Chunk Size:** Use 200 characters for best balance
4. **Handle Errors:** Implement retry logic for network issues

### Quality Optimization

For best results:

* Use high-quality reference audio for cloning
* Choose appropriate emotion markers
* Test different latency modes
* Monitor API rate limits

## Troubleshooting

### Common Issues

**No audio output:**

* Check API key validity
* Verify model ID exists
* Ensure proper audio format

**Poor quality:**

* Use better reference audio
* Try normal latency mode
* Check text formatting

**Slow generation:**

* Use balanced latency mode
* Reduce chunk length
* Check network connection

## Code Examples

### Batch Processing

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from fishaudio.utils import save

    texts = [
        "First announcement",
        "Second announcement",
        "Third announcement"
    ]

    for i, text in enumerate(texts):
        audio = client.tts.convert(
            text=text,
            reference_id="model_id"
        )
        save(audio, f"output_{i}.mp3")
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    const texts = [
        "First announcement",
        "Second announcement",
        "Third announcement",
    ];

    for (let i = 0; i < texts.length; i++) {
        const audio = await fishAudio.textToSpeech.convert({
            text: texts[i],
            reference_id: "model_id",
        });
        const buffer = Buffer.from(await new Response(audio).arrayBuffer());
        await writeFile(`output_${i}.mp3`, buffer);
    }
    ```
  </Tab>
</Tabs>

### Error Handling

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import time
    from fishaudio.exceptions import FishAudioError

    def generate_with_retry(text, max_retries=3):
        for attempt in range(max_retries):
            try:
                audio = client.tts.convert(
                    text=text,
                    reference_id="model_id"
                )
                return audio
            except FishAudioError as e:
                if attempt < max_retries - 1:
                    time.sleep(2 ** attempt)  # Exponential backoff
                else:
                    raise e
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    async function generateWithRetry(text, maxRetries = 3) {
        for (let attempt = 0; attempt < maxRetries; attempt++) {
            try {
                const audio = await fishAudio.textToSpeech.convert({
                    text,
                    reference_id: "model_id",
                });
                const buffer = Buffer.from(await new Response(audio).arrayBuffer());
                return buffer;
            } catch (err) {
                if (attempt < maxRetries - 1) {
                    const delayMs = 2 ** attempt * 1000;
                    await new Promise((r) => setTimeout(r, delayMs));
                } else {
                    throw err;
                }
            }
        }
    }

    const buffer = await generateWithRetry("Hello with retry");
    await writeFile("retry_output.mp3", buffer);
    ```
  </Tab>
</Tabs>

## API Reference

### Request Parameters

| Parameter         | Type    | Description          | Default  |
| ----------------- | ------- | -------------------- | -------- |
| **text**          | string  | Text to convert      | Required |
| **reference\_id** | string  | Model/voice ID       | None     |
| **format**        | string  | Audio format         | "mp3"    |
| **chunk\_length** | integer | Characters per chunk | 200      |
| **normalize**     | boolean | Normalize text       | true     |
| **latency**       | string  | Speed vs quality     | "normal" |

### Response

Returns audio data in the specified format as binary stream.

## Get Support

Need help with text-to-speech?

* [API Reference](/api-reference/introduction)
* **Discord Community:** [Join our Discord](https://discord.gg/fish-audio)
* **Email Support:** [support@fish.audio](mailto:support@fish.audio)
