- MCP Server ToolsUpdated
- Managed MCP with chat()Updated
- Manual MCPUpdated
- MCP Type GenerationUpdated
- MCP Client InputUpdated
- MCP Server Tools: AdvancedNew
- MCP EventsNew
- MCP AppsUpdated
- Serve Tools over HTTPUpdated
- Serve Resources and PromptsUpdated
- Ask for InputUpdated
- Draft Text from a ToolUpdated
- MCP Server TasksUpdated
- MCP Server AuthUpdated
- MCP Server SessionsUpdated
- MCP Server on stdioUpdated
Persistence (Advanced)
- GenerationsUpdated
- Realtime Voice ChatUpdated
- Text-to-SpeechUpdated
- TranscriptionUpdated
- Transcription: AdvancedNew
- Voice CreationUpdated
- Audio RecordingUpdated
- Audio GenerationUpdated
- Image GenerationUpdated
- Image Generation: AdvancedNew
- Video GenerationUpdated
- Video Generation: AdvancedNew
- Live GenerationUpdated
- World GenerationUpdated
- Generation HooksUpdated
- OverviewUpdated
- Quick StartUpdated
- ProvidersUpdated
- HarnessesUpdated
- Harness AuthUpdated
- WorkspaceUpdated
- ToolsUpdated
- PolicyUpdated
- Lifecycle & SnapshotsUpdated
- Portable SnapshotsUpdated
- Keep Files After ReloadUpdated
- Save a Named VersionUpdated
- Branch From a VersionUpdated
- Send a Frozen FileUpdated
- Let the Agent Save and ForkUpdated
- Pick Which Files to KeepUpdated
- What a Snapshot StoresUpdated
- Instance DurabilityUpdated
- Durable RunsUpdated
- EventsUpdated
Sandboxes (Advanced)
- Runtime ContextUpdated
- Bring Your Own KeyUpdated
- Debug LoggingUpdated
- Multimodal ContentUpdated
- Files APIUpdated
- Per-Model Type SafetyUpdated
- Runtime Adapter SwitchingUpdated
- Tree-ShakingUpdated
- Extend AdapterUpdated
- Extend Adapter: AdvancedNew
- Typed Pre-Configured OptionsUpdated
- Test with a Fake ModelUpdated
- Prompt CachingUpdated
- Mid-Conversation ChangesUpdated
- Approval Flow ProcessingUpdated
Audio Generation
generateAudio() sends a text prompt to an audio model and returns the clip. useGenerateAudio plays it in the browser.
For a spoken voice, use Text-to-Speech instead.
Generate a track#
You need a React app with a server route, and a fal.ai API key. This takes about 5 minutes.
- Install the packages:
npm i @tanstack/ai @tanstack/ai-react @tanstack/ai-falpnpm add @tanstack/ai @tanstack/ai-react @tanstack/ai-falyarn add @tanstack/ai @tanstack/ai-react @tanstack/ai-falbun add @tanstack/ai @tanstack/ai-react @tanstack/ai-fal- Put your key in .env:
FAL_KEY=your-fal-api-key- Add a POST route at /api/generate/audio:
import {
generateAudio,
generationParamsFromRequest,
toServerSentEventsResponse,
} from '@tanstack/ai'
import { falAudio } from '@tanstack/ai-fal'
export async function POST(request: Request) {
const { input, threadId } = await generationParamsFromRequest('audio', request)
const stream = generateAudio({
adapter: falAudio('fal-ai/minimax-music/v2.6'),
prompt: input.prompt,
threadId,
duration: input.duration,
stream: true,
})
return toServerSentEventsResponse(stream)
}- Call the route from a component:
import { fetchServerSentEvents, useGenerateAudio } from '@tanstack/ai-react'
export function AudioGenerator() {
const { generate, result, isLoading, error, reset } = useGenerateAudio({
connection: fetchServerSentEvents('/api/generate/audio'),
threadId: 'audio',
})
const src = result?.audio.url
? result.audio.url
: result?.audio.b64Json
? `data:${result.audio.contentType ?? 'audio/mpeg'};base64,${result.audio.b64Json}`
: undefined
return (
<div>
<button
onClick={() =>
generate({
prompt: 'City Pop, 80s retro, groovy synth bass, warm female vocal, 104 BPM',
})
}
disabled={isLoading}
>
{isLoading ? 'Generating...' : 'Generate'}
</button>
{error && <p role="alert">{error.message}</p>}
{src && <audio src={src} controls />}
{result && <button onClick={reset}>Clear</button>}
</div>
)
}Click Generate. When the track is ready, an audio player shows up.
How it works#
- generateAudio() sends the prompt to the model that the adapter names.
- The result has one audio object. It holds a url (fal) or base64 bytes in b64Json (Gemini), plus contentType when the provider sends it.
- With stream: true, the route sends the result to the hook as Server-Sent Events. The hook stores it on result.
To call a TanStack Start server function instead of a route, pass fetcher to the hook. See Generations: Transports in full. The hook options and return values are in Generation Hooks.
Note
A long track can outlive a page reload, and a fal URL expires. To keep the status, the result, and the audio file, add Generation Persistence.
Options#
| Option | Type | Description |
|---|---|---|
| adapter | AudioAdapter | The audio adapter and model (required) |
| prompt | string | What the audio sounds like (required) |
| duration | number | Length in seconds. Not every model uses it. |
| modelOptions | object | Model-specific options. Typed when you pass the model id as a string literal. |
| debug | DebugOption | Log every chunk from the provider SDK: true, false, or a DebugConfig. See Debug Logging. |
Pick a provider#
| Provider | Adapter | Package | Models | Key |
|---|---|---|---|---|
| Google Gemini | geminiAudio | @tanstack/ai-gemini | lyria-3-pro-preview, lyria-3-clip-preview | GOOGLE_API_KEY |
| fal.ai | falAudio | @tanstack/ai-fal | MiniMax Music, DiffRhythm, Google Lyria 2, Stable Audio 2.5, MMAudio, ElevenLabs sound effects, Thinksound, and more | FAL_KEY |
Google Lyria (music)#
Lyria makes full songs with vocals and instruments:
- lyria-3-pro-preview makes compositions with several verses.
- lyria-3-clip-preview makes 30-second clips.
import { generateAudio } from '@tanstack/ai'
import { geminiAudio } from '@tanstack/ai-gemini'
const result = await generateAudio({
adapter: geminiAudio('lyria-3-pro-preview'),
prompt: 'Uplifting indie pop with layered vocals and jangly guitars',
threadId: 'thread-1',
})
console.log(result.audio.b64Json) // base64 audio bytes
console.log(result.audio.contentType) // for example "audio/mpeg"fal.ai music (MiniMax Music 2.6)#
MiniMax Music 2.6 makes a full song (vocals, backing music, and arrangement) from one prompt:
import { generateAudio } from '@tanstack/ai'
import { falAudio } from '@tanstack/ai-fal'
const result = await generateAudio({
adapter: falAudio('fal-ai/minimax-music/v2.6'),
prompt: 'City Pop, 80s retro, groovy synth bass, warm female vocal, 104 BPM',
threadId: 'thread-1',
})
console.log(result.audio.url) // URL of the audio file
console.log(result.audio.contentType) // for example "audio/wav"fal.ai music with your own lyrics (DiffRhythm)#
import { generateAudio } from '@tanstack/ai'
import { falAudio } from '@tanstack/ai-fal'
const result = await generateAudio({
adapter: falAudio('fal-ai/diffrhythm'),
prompt: 'An upbeat electronic track with synths',
threadId: 'thread-1',
modelOptions: {
lyrics: '[verse]\nHello world\n[chorus]\nLa la la',
},
})fal.ai sound effects#
import { generateAudio } from '@tanstack/ai'
import { falAudio } from '@tanstack/ai-fal'
const result = await generateAudio({
adapter: falAudio('fal-ai/elevenlabs/sound-effects/v2'),
prompt: 'Thunderclap followed by heavy rain',
threadId: 'thread-1',
duration: 5,
})Advanced#
MiniMax Music v2 lyrics#
MiniMax Music v2 takes lyric guidance in the lyrics_prompt field:
import { generateAudio } from '@tanstack/ai'
import { falAudio } from '@tanstack/ai-fal'
const result = await generateAudio({
adapter: falAudio('fal-ai/minimax-music/v2'),
prompt: 'A dreamy pop ballad in the style of the 80s',
threadId: 'thread-1',
modelOptions: {
lyrics_prompt: '[instrumental]',
},
})Result shape#
import type { TokenUsage } from '@tanstack/ai'
interface AudioGenerationResult {
id: string
model: string
audio: {
url?: string
b64Json?: string
contentType?: string
duration?: number
}
// Same TokenUsage shape as chat. Present when the provider reports it
// (for example Gemini Lyria). fal sets `usage.billed` instead:
// { quantity, unit: 'units' }, read from the `x-fal-billable-units`
// result header. Multiply the quantity by the endpoint unit price
// (from the fal pricing API) to get the exact cost.
usage?: TokenUsage
}An audio object holds either url or b64Json, never both. Gemini returns b64Json. fal returns url. To get raw bytes from a fal result, fetch the URL:
import { generateAudio } from '@tanstack/ai'
import { falAudio } from '@tanstack/ai-fal'
const result = await generateAudio({
adapter: falAudio('fal-ai/diffrhythm'),
prompt: 'An upbeat electronic track',
threadId: 'thread-1',
})
if (result.audio.url) {
const response = await fetch(result.audio.url)
const bytes = new Uint8Array(await response.arrayBuffer())
}Audio generation or text-to-speech#
| generateAudio() | generateSpeech() | |
|---|---|---|
| Purpose | Music, soundscapes, sound effects | Spoken words |
| Result | result.audio.url or result.audio.b64Json | Base64 in result.audio |
| Main input | prompt | text |
| Voice and speed controls | No | Yes (voice, speed) |
Debug a request#
If the audio is not what you expected, pass debug: true. Examples: the model cuts the track short, the provider rejects the prompt, or the response shape looks wrong. The adapter then logs every chunk that the provider SDK sends. See Debug Logging.
API keys#
Each provider reads its key from the environment by default:
GOOGLE_API_KEY=your-google-api-key
FAL_KEY=your-fal-api-keyTo pass the key in code:
import { createGeminiAudio } from '@tanstack/ai-gemini'
import { falAudio } from '@tanstack/ai-fal'
createGeminiAudio('lyria-3-pro-preview', 'your-key')
falAudio('fal-ai/diffrhythm', { apiKey: 'your-key' })To let each user bring their own key, see the useGenerateAudio example in Generation Hooks.