# Versuri și jam live (/ro/docs/gateway/lyrics-jam)

Uneltele de compoziție din codai Studio — scrii versuri cu verificarea silabelor și a rimelor, le aliniezi la un clip vocal pentru sincronizare karaoke și LRC și cânți live cu muzică generată printr-un WebSocket.

Lângă [creațiile audio](./audio-creatives.mdx) stau trei unelte: un **scriitor de versuri** care verifică metrica și rima, **alinierea versurilor** care sincronizează rândurile cu un clip vocal și un **jam live** care transmite muzică generată pe care o dirijezi în timp real.

| Metodă | Cale | Rezultat | Facturare |
| --- | --- | --- | --- |
| `POST` | `/v1/studio/lyrics` | Versuri cu raport de silabe și rime | Tokeni (`codai::lyrics`) |
| `POST` | `/v1/studio/lyrics/align` | Rânduri sincronizate + LRC, aplicate ca `setLyrics` | Gratuit cu cuvinte din cache, altfel speech-to-text (`codai::lyrics-align`) |
| `WS` | `/v1/studio/jam` | Audio PCM live dirijat prin prompturi | Pe minut început (`codai::jam`) |

## Scrierea versurilor [#scrierea-versurilor]

```bash
curl https://ai.codai.ro/v1/studio/lyrics \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: song-7" \
  -d '{
    "brief": "o noapte de vară la mare, doi prieteni care se regăsesc",
    "language": "ro",
    "genre": "pop",
    "structure": ["verse", "chorus", "verse", "chorus"],
    "syllables_per_line": 8,
    "rhyme_scheme": "AABB",
    "lines_per_section": 4
  }'
```

| Câmp | Implicit | Observații |
| --- | --- | --- |
| `brief` | obligatoriu | 3–4000 de caractere; nu se jurnalizează niciodată |
| `language` | obligatoriu | `ro` sau `en`; româna folosește diacriticele corecte (ș ț ă î â) |
| `genre`, `mood`, `title` | — | Text liber (100 / 100 / 200 de caractere) |
| `structure` | verse, chorus, verse, chorus, bridge, chorus | 1–12 dintre `intro`, `verse`, `pre_chorus`, `chorus`, `bridge`, `outro`, `instrumental`, `custom` |
| `syllables_per_line` | — | 3–20; verificat cu o toleranță de ±1 |
| `rhyme_scheme` | `free` | `AABB`, `ABAB` sau `free` |
| `lines_per_section` | `4` | 2–8 |

Scriitorul rulează **o singură** tură `codai` limitată, cu un tool forțat, apoi verifică fiecare vers cu numărătorile proprii codai: silabe (regulile pentru română tratează diftongii, triftongii și hiatul; în engleză se numără grupurile de vocale după eliminarea unui -e / -es / -ed mut) și terminații de rimă (ultimul cuvânt de la vocala accentuată în română, de la ultimul grup de vocale în engleză, fără diacritice). Când peste **30 %** din versuri nu se potrivesc — un număr de silabe în afara țintei ±1 sau un vers dintr-o pereche nerimată — **o singură tură de reparare** numește problemele și se întoarce cel mai bun dintre cele două răspunsuri (`repaired: true` când a câștigat reparația).

```json
{
  "object": "studio.lyrics",
  "title": "Noapte de vară",
  "language": "ro",
  "rhyme_scheme": "AABB",
  "syllables_per_line": 8,
  "sections": [
    {
      "kind": "verse",
      "lines": [
        { "text": "Marea cântă lin sub lună", "syllables": 8, "rhyme": "una", "letter": "A", "off": false },
        { "text": "Ne-am regăsit iar împreună", "syllables": 8, "rhyme": "una", "letter": "A", "off": false }
      ]
    }
  ],
  "text": "[verse]\nMarea cântă lin sub lună\nNe-am regăsit iar împreună",
  "report": { "lines": 16, "off_lines": 1, "off_ratio": 0.063, "pairs": 8, "rhymed": 7, "repair_ratio": 0.125 },
  "repaired": false,
  "cost_micro_usd": "4210"
}
```

`text` este tot cântecul, cu un antet `[kind]` pe secțiune — îl trimiți ca `lyrics_text` la aliniere sau ca parametrul `lyrics` al [`music.generate_ace`](./media-ops.mdx).

**Facturare.** Turele se taxează după tokeni sub modelul public `codai` (model de utilizare `codai::lyrics`, prompt caching activ). Mai întâi se rezervă în portofel o sumă mică, 20 000 µUSD (`402 insufficient_funds` dacă nu încape), decontată apoi cu costul real în tokeni. Când modelul nu reușește deloc să producă versuri răspunsul este `422 no_lyrics` și se taxează doar turele care au rulat.

## Alinierea versurilor la un clip (karaoke) [#alinierea-versurilor-la-un-clip-karaoke]

Alinierea sincronizează versurile scrise cu un clip dintr-o creație audio (sau video) și le scrie în `doc.audio.lyrics` printr-o singură editare `setLyrics`:

```bash
curl https://ai.codai.ro/v1/studio/lyrics/align \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "creative_id": "8a1c…",
    "clip_id": "vox1",
    "lyrics_text": "[verse]\nMarea cântă lin sub lună\nNe-am regăsit iar împreună",
    "language": "ro"
  }'
```

| Câmp | Implicit | Observații |
| --- | --- | --- |
| `creative_id`, `clip_id` | obligatorii | Asset-ul clipului trebuie să fie un asset `speech`, `music`, `video` sau `upload` al tău, gata |
| `lyrics_text` | obligatoriu | Cel mult 5000 de caractere; liniile antet `[verse]` / `[chorus]` încep secțiuni |
| `language` | `meta.language` al documentului | Indiciu pentru transcriere, `ro` sau `en` |
| `apply` | `true` | `false` întoarce rândurile sincronizate și LRC-ul fără să editeze creația |
| `base_seq` | versiunea curentă | Versiunea pe care se bazează editarea; o editare în conflict dă `409 stale` |
| `retranscribe` | `false` | Ignoră cuvintele din cache și transcrie din nou (taxat) |

Gateway-ul ia marcajele de timp ale cuvintelor din asset-ul clipului — din cache-ul pe care [subtitrările](./creatives.mdx) și alinierile anterioare îl lasă pe asset (**gratuit**, `cached: true`) sau dintr-o transcriere Whisper nouă a clipului (cel mult 25 MB) —, le mută pe cronologie ținând cont de tăierea, poziția și întinderea clipului și potrivește cuvintele versurilor cu cele transcrise printr-o aliniere tolerantă la diacritice și greșeli de tipar. Rândurile ale căror cuvinte nu s-au auzit se interpolează între vecini. Răspunsul:

```json
{
  "object": "studio.lyrics_alignment",
  "creative_id": "8a1c…",
  "clip_id": "vox1",
  "applied": true,
  "seq": 12,
  "doc_hash": "…",
  "lines": [{ "startMs": 12040, "endMs": 14880, "text": "Marea cântă lin sub lună", "sectionId": "verse1" }],
  "lrc": "[ti:Noapte de vară]\n[00:12.04]Marea cântă lin sub lună\n…",
  "match_ratio": 0.94,
  "cached": false,
  "cost_micro_usd": "18000"
}
```

`match_ratio` este proporția cuvintelor din versuri potrivite cu un cuvânt auzit; sub aproximativ 0,6 sincronizarea e mai mult interpolată — verifică clipul și limba. LRC-ul se exportă și la [randarea audio](./audio-creatives.mdx#randare-și-exporturi) (`lrc: true`).

**Facturare.** Cuvintele din cache sunt gratuite. O transcriere nouă se taxează `codai::lyrics-align` la tariful speech-to-text — 6 000 µUSD pe minut început din clip — sub o rezervare în portofel. Alinierea are nevoie de un serviciu de transcriere din afara UE și nu e disponibilă pentru conturile cu rezidența datelor în UE (`403 residency_unavailable`); când transcrierea nu e configurată, răspunsul este `503 transcription_unavailable`.

## Jam live (WebSocket) [#jam-live-websocket]

`wss://ai.codai.ro/v1/studio/jam` deschide o sesiune muzicală live pe Lyria RealTime: trimiți prompturi text ponderate și câteva controale muzicale, iar gateway-ul transmite înapoi audio generat continuu. OpenAPI nu poate descrie WebSockets, așa că protocolul e documentat aici.

### Conectarea [#conectarea]

| Client | Cum te autentifici |
| --- | --- |
| Runtime de server | `Authorization: Bearer <cheie API sau token efemer>` la upgrade |
| Browser | Subprotocoalele `codai-jam` și `bearer.<token efemer>`; serverul răspunde cu `codai-jam` și nu repetă niciodată credențialul |
| Browser (alternativ) | `?key=<token efemer>` — o cheie de durată brută în query string e refuzată cu închiderea `4401` |

Tokenii efemeri se emit cu [`POST /v1/tokens`](./embeddings-audio.mdx) (scope `realtime` sau `live`). `?session_id=` se înregistrează pe rândul de utilizare.

### Client → gateway (cadre text JSON) [#client--gateway-cadre-text-json]

| Mesaj | Câmpuri |
| --- | --- |
| `{ "type": "prompts", "prompts": [{ "text", "weight" }] }` | 1–8 prompturi; `text` de cel mult 200 de caractere; `weight` între −10 și 10, diferit de 0 (negativ îndepărtează) |
| `{ "type": "config", "bpm"?, "density"?, "brightness"?, "scale"? }` | `bpm` 60–200, `density` și `brightness` 0–1, `scale` una dintre `C_MAJOR_A_MINOR` … `B_MAJOR_A_FLAT_MINOR` sau `SCALE_UNSPECIFIED`; actualizările parțiale se combină cu ultima configurație, iar o schimbare de `bpm` sau `scale` resetează contextul muzical |
| `{ "type": "play" }`, `pause`, `stop`, `reset` | Controlul redării |

Mesajele trimise înainte ca upstream-ul să fie gata se pun în coadă. Orice altceva — JSON invalid, un tip necunoscut, un cadru binar — închide sesiunea cu `4400`.

### Gateway → client [#gateway--client]

- **Cadre binare**: PCM16 little-endian brut, stereo intercalat, 48 000 Hz. Audio-ul se aruncă (nu se pune în buffer) cât timp socket-ul tău rămâne în urmă cu peste 4 MB.
- `{ "type": "status", "state": "connecting" | "ready" | "playing" | "paused" | "stopped", "remaining_ms", "sample_rate"?, "channels"? }` — `ready` vine cu `sample_rate: 48000` și `channels: 2`.
- `{ "type": "filtered", "text", "reason" }` — filtrul de siguranță al modelului a ignorat un prompt.
- `{ "type": "error", "code", "message" }` — trimis înaintea unui refuz sau a unei închideri din upstream.

### Coduri de închidere [#coduri-de-închidere]

| Cod | Înseamnă |
| --- | --- |
| `4400` | Mesaj invalid de la client |
| `4401` | Credențiale lipsă sau invalide, ori o cheie brută în query string |
| `4402` | Rezervarea pentru 10 minute nu încape în portofel (`insufficient_funds`) |
| `4403` | Cont cu rezidența datelor în UE, model nepermis pentru cheie sau plafon de cheltuieli atins |
| `4408` | S-a atins limita de 10 minute a sesiunii (`max_duration`) |
| `4409` | Ai deja un jam live deschis (`jam_busy`) — unul pe cont |
| `4502` | Modelul muzical din upstream a eșuat sau s-a închis |
| `4503` | Jam-ul live nu e configurat pe acest gateway (`jam_unavailable`) |

### Prețuri [#prețuri]

0,03 USD pe **minut început** din sesiunea upstream deschisă (model de utilizare `codai::jam`, upstream `lyria-realtime-exp`). Prețul pentru 10 minute (0,30 USD) se rezervă la deschiderea sesiunii și se decontează la închidere cu minutele folosite efectiv; o sesiune care nu ajunge la upstream nu se taxează. Lyria RealTime e un model experimental, așa că prețul se poate schimba. Prompturile și audio-ul nu se jurnalizează niciodată.

## SDK-uri [#sdk-uri]

```ts
const song = await codai.lyrics.write({ brief: 'o noapte de vară', language: 'ro', rhyme_scheme: 'AABB' });
const aligned = await codai.lyrics.align({ creative_id: id, clip_id: 'vox1', lyrics_text: song.text });

const jam = codai.jam.connect({ token }); // browser: token efemer
jam.on('audio', (ev) => player.push(new Int16Array(ev.data)));
jam.on('close', (ev) => console.log('închis', ev.code));
jam.prompts([{ text: 'pian lo-fi cald', weight: 1 }]);
jam.config({ bpm: 90, density: 0.4 });
jam.play();
```

```python
song = client.lyrics.write("o noapte de vară", "ro", rhyme_scheme="AABB", syllables_per_line=8)
aligned = client.lyrics.align(creative_id, "vox1", song["text"])
```

TypeScript: `lyrics.write / align`, `jam.connect` (`JamSession`: `prompts`, `config`, `play`, `pause`, `stop`, `reset`, `close`, `on`; `JAM_CLOSE_CODES`). Python: `lyrics.write / align`; SDK-ul Python nu are un helper WebSocket — folosește orice client WebSocket cu protocolul de mai sus.
