# Audio creatives (/docs/gateway/audio-creatives)

Songs and podcasts as editable codai Studio creatives — a multitrack timeline with sections, tracks, clips, MIDI, effects, a master bus and timed lyrics, edited with small ops.

An **audio creative** (`kind: "audio"`) is a [creative](./creatives.mdx) whose document is a multitrack audio timeline: a song or a podcast episode. It uses the same API as every other creative — create, read, edit with ops, versions, revert, fork, lint, share — and the same document, with the timeline in `doc.audio`. Creating, reading and editing it is **free**; the files you put on it come from [media operations](./media-ops.mdx), music and speech generation or your own uploads.

```bash
curl https://ai.codai.ro/v1/studio/creatives \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "kind": "audio", "title": "Episode 12" }'
```

## The document model [#the-document-model]

An audio document has one format, `stereo` (48 kHz mixdown; its 1920×1080 canvas is only used by an optional cover / visualiser scene), at most **one** scene (captions only) and an optional `fps`. Everything that matters lives in `doc.audio` (schema `CreativeAudio`):

| Field | What it holds |
| --- | --- |
| `meta` | `bpm` (20–300), `key` (`{ tonic, mode }`), `timeSignature` (`[4, 4]`), `language`, `loudnessTarget`, `sampleRate` (44100 \| 48000) |
| `sections` | Song or episode parts: `{ id, kind, startMs, durationMs, label?, lyrics?, styleHint?, locked? }`; `kind` is `intro`, `verse`, `pre_chorus`, `chorus`, `bridge`, `outro`, `instrumental` or `custom` |
| `tracks` | Up to 16: `{ id, role, kind?, name?, gainDb?, pan?, mute?, solo?, locked?, sends?, fx?, automation?, clips[], midiClips? }` |
| `master` | The master bus: `fx`, `targetLufs`, `truePeakDb`, `limiter` |
| `lyrics` | Timed lines `{ startMs, endMs, text, sectionId? }` (LRC-ready) |
| `humanLog` | Who did what: `{ op, actor: human \| ai, ts, detail? }`, appended by the server on every edit |

Milliseconds are the source of truth; bars and beats are derived from `meta.bpm` and `meta.timeSignature`.

**Tracks.** `role` is one of `voice`, `music`, `sfx`, `drums`, `bass`, `keys`, `guitar`, `vocal`, `lead`, `pad`, `fx`, `bus`, `podcast_host`, `podcast_guest`. `kind` is `audio` (default), `midi` (holds `midiClips` only) or `bus` (no clips; other tracks reach it through `sends`). `fx` is a chain of up to 16 effects (`eq`, `compressor`, `reverb`, `delay`, `limiter`, `gate`, `deesser`, `pitch`, `chorus`, `saturation`) with numeric `params` and `bypass`; `automation` envelopes target `gainDb`, `pan` or `fx.<fxId>.<param>`.

**Clips.** `{ id, assetId, startMs, inMs?, outMs?, gainDb?, pan?, fadeInMs?, fadeOutMs?, fadeCurve?, stretch?, pitchSemitones?, loop?, duck?, color?, generation?, locked? }`. `inMs` / `outMs` are offsets into the **source** asset: a clip ends on the timeline at `startMs + (outMs − inMs) × stretch`. `fadeCurve` is `linear`, `exp` or `s`; `stretch` (0.25–4) keeps the pitch; `generation` records how a generated clip was made (public codai provider and model, prompt, seed, parent clip, op).

**Ducking.** Music tracks of an audio creative duck automatically under `voice`, `vocal`, `podcast_host` and `podcast_guest` clips (150 ms attack, 400 ms release, 12 dB); any clip can opt in or out with `duck`.

## Editing with ops [#editing-with-ops]

Audio creatives take every common op (`setMeta`, `setAsset`, `setAudioClip`, `removeAudioClip`, `setLocale`…) plus 16 audio ops, sent to `POST /v1/studio/creatives/{id}/ops` like any other edit:

| Op | Shape |
| --- | --- |
| Sections | `addSection { section }`, `updateSection { target, patch }`, `removeSection { target }`, `moveSection { target, startMs }` |
| Tracks | `addTrack { track, index? }`, `updateTrack { target, patch }`, `removeTrack { target }` |
| Clips | `splitClip { clipId, atMs, newId }`, `trimClip { clipId, startMs?, endMs? }`, `moveClip { clipId, startMs?, trackId? }`, `setFades { clipId, fadeInMs?, fadeOutMs?, fadeCurve? }` |
| MIDI | `addMidiClip { trackId, clip }`, `removeMidiClip { clipId }`, `updateMidiNotes { clipId, update?, remove?, add?, replace? }` |
| Master and lyrics | `setMaster { master }`, `setLyrics { lines }` |

`setMeta` also accepts `audio` (a patch of `meta`) and `setAudioClip` a `trackId`. In every patch `null` removes an optional field, so each edit has an exact inverse and can be reverted.

```json
{
  "base_seq": 4,
  "ops": [
    { "op": "addSection", "section": { "id": "chorus1", "kind": "chorus", "startMs": 32000, "durationMs": 16000 } },
    { "op": "setFades", "clipId": "c7", "fadeInMs": 500, "fadeCurve": "s" },
    { "op": "setMaster", "master": { "targetLufs": -14, "truePeakDb": -1, "limiter": true } }
  ]
}
```

## Mastering targets [#mastering-targets]

`meta.loudnessTarget` takes a preset or a number of integrated LUFS (−40…−5):

| Preset | LUFS | Use |
| --- | --- | --- |
| `streaming` | −14 | Spotify, Apple Music, YouTube (the default when nothing is set) |
| `podcast` | −16 | Podcast platforms |
| `broadcast` | −23 | EBU R128 radio and TV |

`master.targetLufs` overrides the preset; the true peak defaults to −1 dBTP. To master a mixdown file, run [`audio.master`](./media-ops.mdx) with `target_lufs` −14, −16 or −23.

## Lint [#lint]

[Lint](./creatives.mdx#lint-and-fixes) adds five audio rules: `clipping_risk` (summed clip gain above 0 dB), `missing_lyrics` (a verse / pre-chorus / chorus / bridge without lyrics when the song has a vocal track), `missing_loudness_target`, `audio_overlap` (overlapping clips on one track) and `audio_out_of_range` (a clip or note outside its source or the timeline).

## Media operations for audio [#media-operations-for-audio]

The building blocks of a song or an episode come from [media operations](./media-ops.mdx): `music.generate_ace`, `music.repaint`, `music.extend` and `music.cover` for songs, `sfx.generate` for effects, `audio.separate_stems` to split a mix into vocal / drum / bass / other tracks, `audio.denoise` to clean a voice, `audio.to_midi` to turn a melody into a MIDI clip, `audio.analyze` to read BPM, key and loudness into `meta`, and `audio.master` for the final loudness. Each output is a new asset you place on a track with `setAudioClip`; its lineage (`parent_asset_id`, `op`) stays on the asset.

## Rendering and exports [#rendering-and-exports]

`POST /v1/studio/creatives/{id}/render` on an audio creative renders the **mixdown**: every track, clip, MIDI note, effect, send and automation is mixed with ffmpeg, mastered to the loudness target (two-pass loudnorm + true-peak limiter) and exported. The answer is `202` with the render id; poll `GET /v1/studio/renders/{id}` like any render.

| Field | Default | What it does |
| --- | --- | --- |
| `formats` | `["wav", "mp3"]` | 1–3 of `wav` (24-bit), `mp3` (320 kb/s), `flac`, each a separate file |
| `stems` | `false` | Also one WAV per audible track (audio tracks with clips, MIDI tracks with notes) |
| `lrc` | on when the document has timed lyrics | An LRC file of `doc.audio.lyrics`; `true` without lyrics is `400 no_lyrics` |
| `loudness` | the document's `master.targetLufs` / `meta.loudnessTarget`, else −14 | `streaming` (−14), `podcast` (−16), `broadcast` (−23) or LUFS (−40…−5) |
| `title` | the creative title | Stored with the render |

```bash
curl https://ai.codai.ro/v1/studio/creatives/$ID/render \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: mix-12" \
  -d '{ "formats": ["wav", "mp3"], "stems": true, "loudness": "podcast" }'
```

The 202 lists the planned `outputs` (`kind`: `mix` / `stem` / `lrc`, `format`, `track_id`), the resolved `target_lufs` and `true_peak_db`, `duration_seconds` and `quote_micro_usd`. When the render has `succeeded`, its `data[]` holds **one asset per output** (1-hour signed `url`, `output`, `format`, `track_id`) and `qa` the measured `lufs`, `true_peak_db` and `clipping`. Every file carries the AI mark inside it (WAV RIFF ICMT, MP3 ID3v2.4, FLAC Vorbis comment) and its lineage points at the render.

**Price**: $0.001 per output second ($0.06 per minute), minimum $0.01; `stems: true` adds 50 %. The quote is held on your wallet before dispatch (`402 insufficient_funds` when short) and the measured length is billed once. At most 20 minutes per render (`400 too_long`); a document with nothing audible is `400 empty_audio`; every referenced asset must be yours and ready (`400 asset_not_found`).

Lyrics for the song come from the [lyrics writer](./lyrics-jam.mdx), and karaoke timing from lyrics alignment.

## SDKs [#sdks]

TypeScript: `creatives.create({ kind: 'audio' })`, `creatives.edit(id, { baseSeq, ops })` (types `CreativeAudio`, `CreativeOp`), `creatives.renderAudio(id, { formats, stems, lrc, loudness })` (types `CreativeAudioRenderRequest`, `CreativeAudioRenderAccepted`, `StudioAudioRender`), plus `mediaOps` and `lyrics`. Python: `client.creatives.create(...)`, `client.creatives.edit(...)`, `client.creatives.render_audio(id, formats=, stems=, lrc=, loudness=)`, `client.media_ops` and `client.lyrics`.
