Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit95280a384c

README.md

audio_gen (service)

Text-to-audio generation via the ElevenLabs API — speech, sound effects, and music. A .service asset — a plain Luau module, resolvable as @builtin::services.audio_gen, exposing three generators that run through the credit-metered service path.

Operations

  • speech({ text }) — text-to-speech. Returns spoken audio, read in the offering's reading voice.
  • sfx({ prompt, duration? }) — text-to-sound-effect. duration is the target length in seconds (optional). Returns a short sound effect.
  • music({ prompt, duration?, instrumental? }) — text-to-music. duration is the target length in seconds (optional); instrumental forces a track with no vocals (optional). Returns a musical track.

Those field names are the whole surface: an operation refuses a field it does not declare, by name, before any credit is spent. :operations() is the machine-readable form of the list above.

Speech reads in one voice

speech renders every line in the same reading voice, and the offering takes no voice selection — so a cast of several characters is separated by what they say and by how their clips are played back, not by picking a speaker per line. Both of those are yours to set:

  • Writing and delivery — vocabulary, sentence length and punctuation move a read further than a voice swap does.
  • Playback treatment — the Audio component's pitch shifts a speaker up or down; audio.decode plus asset.create("soundClip", …, { pcm, sampleRate, channels }) re-renders a clip, so resampling, band-limiting and a reverb tail are all authorable per character.

Using it

A generation runs in the background: the call hands back a handle straight away, and the audio arrives on the run's status row a few seconds later.

local audio = asset.resolve("audio_gen", "service")
print(audio:cost("sfx"), audio:balance())          -- budget before spending

local g = audio:invoke({ operation = "sfx", prompt = "a heavy wooden door slamming", duration = 2 })

-- Watch the run until it settles. `status` moves through the run's own stages
-- (`submitted`, `writing`, `importing`) to `completed` or `failed`; a completed
-- row's `asset` is the finished, spawnable `.soundClip`.
local row = tools.use("services", "status", { id = g.id })
while row ~= nil and row.status ~= "completed" and row.status ~= "failed" do
    task.wait(1)
    row = tools.use("services", "status", { id = g.id })
end
print(row.status, row.asset)

audio_gen declares no primary operation (there are three siblings, not one default) — always pass operation.

The handle's asset_path is where the run directs the raw generated file, not the asset it becomes: the importer turns those bytes into a .soundClip container, and that container is what the completed status row names as asset. Read the produced asset from the row. The same holds for an asset_path you pass: asset_path = "/zero/source/game/audio/door.mp3" lands the clip /zero/source/game/audio/door.soundClip, and so does asset_path = "/zero/source/game/audio/door.soundClip", which names the clip itself.

Cost

Each operation costs credits (see :cost(operation)); the exact amount charged is returned on the generation handle. Check :balance() first. A call refused for an input the operation does not declare is refused before the charge, so a wrong field name costs a message rather than a generation.