# BGM: composition (creative file 3/3)

**This is one of the three AI/human-authored creative files.** The other two are `script.md` and `edit.json`.

You write root `bgm.mml`; the tool exports the bed to **`clips/bgm.mp3`**
(matches episode layout and `<SKILL_DIR>/references/program.md` `bgm` param). Do not
leave the only export at episode-root `bgm.mp3`.

```jsonc
// uvid_generate_bgm
{ "input": "<EPISODE>/bgm.mml", "output": "<EPISODE>/clips/bgm.mp3", "duration": 62 }
```

`duration` is computed by the caller when the BGM bed must cover a known window.
If omitted, the tool exports the MML loop's own length.

**Done means:** `bgm.mml` has a valid shape, and `clips/bgm.mp3` duration is at least the required BGM window.

Tool `input`/`output` use **absolute** `<EPISODE>/…` (examples below).

---

## Composition steps

### 1) Estimate `bgmSec`

BGM covers **intro end → outro start** only (body + TOC). Intro/outro keep their own SFX beds — do not underlay BGM there.

```text
voiceSec = total kept aroll duration / 1000
tocSec   ≈ sum of TOC clip durations (or ~3s × N from the TOC template)
bgmSec   ≈ voiceSec + tocSec          # NOT full program; exclude intro/outro
bars     ≈ bgmSec × tempo / 60 / 4
```

`uvid_generate_timeline` with `bgm` writes `timeline.bgm.startMs/endMs` from packaging geometry. `uvid_generate_video` / `uvid_generate_otio` honor that window.

### 2) Energy map → loop length → notes

| bgmSec | Suggested loop length | Form |
|---|---|---|
| < 45s | 8 bars | A A' |
| 45–90s | 16 bars | A A B A |
| 90–180s | 16–32 bars | A A B A / A B A C |

Use `progression` as the loop structure and export with `duration: <bgmSec>` to cover the BGM window (intro end → outro start). Prefer too long over too short.

Practical guidance:

- Dense speech: keep S1 sparse, with more rests; let bass/drums carry motion.
- Sparse speech or TOC: allow a short 2-bar hook.
- Maintain one main 2-bar motif and vary it later.
- Takes not kept in `edit.json` do not exist for BGM timing.

### 3) Write order

```text
(optional) kept audio duration + script chapter order
  → bgmSec / bars / energy map / loop length
  → write bgm.mml
  → uvid_generate_bgm { input: <EPISODE>/bgm.mml, output: <EPISODE>/clips/bgm.mp3, duration: <bgmSec> }
```

---

## Contract: `bgm.mml`

### Recommended: chord progression + handwritten melody

```text
title: <episode title>
tempo: 128
progression: [Cmaj7 Am7 Dm7 G7]x2 Fmaj7 Em7 Dm7 G7 Cmaj7 Am7 Dm7 G7
style: drive
s2mode: thirds
bass: walk
swing: 0.45
S1: o5 l8 e r e g a4 r4 g e c r d4 r4 ...
```

`progression` only expands `[…]xN` / `[…]N`. A bare `[Fmaj7 Em7 …]` group is **not**
stripped — write plain chord tokens, or always use an `xN` loop.

Division of labor: you write the creative layer — progression, melody, and structure. The engine writes the craft layer — accompaniment. Missing S2/TR/NO channels are generated from `progression` and style options. Handwriting any channel overrides generation for that channel. S1 is never generated.

| Key | Meaning |
|---|---|
| `title` | Song title |
| `tempo` | BPM, integer |
| `progression` | Chord progression; each chord token is one bar; `[…]xN` / `[…]N` loops only (no bare `[…]`); loops if shorter than melody |
| `style` | `drive` / `chill` / `tense` |
| `s2mode` | `arp` / `echo` / `thirds` |
| `bass` | `root` / `walk` |
| `swing` | `0..1`, or `true` ≈ 0.5 |
| `S1` | Main melody, handwritten |
| `S2` / `TR` / `NO` | Generated by default; handwritten values override generation |

Chords: root `A–G`, optional `#`/`b`, plus suffix:

```text
m 7 maj7 m7 dim dim7 m7b5 aug sus2 sus4 6 m6 5
```

### Choosing a style

| Episode content | Recommendation |
|---|---|
| Tutorial or light tool introduction | `drive` + `swing: 0.4~0.55` + `bass: walk` + `s2mode: thirds` |
| Calm explanation or thanks/credits | `chill` + `swing: 0~0.3` + `bass: root` + `s2mode: echo` or `arp` |
| Debugging, conflict, or tension | `tense` + `swing: 0~0.3` + `bass: root` + `s2mode: arp` |

### Fully handwritten fallback

If no `progression` is provided, all channels must be handwritten and harmonically self-contained:

```text
title: <episode title>
tempo: 140
swing: 0.5
S1: o4 l8 [ c e g > c < g e c ] x8
S2: o3 l8 [ c c g g e e c c ] x8
TR: o2 l8 [ c c g g c c g g ] x8
```

### MML notation

| Token | Meaning |
|---|---|
| `o4` | Octave |
| `l8` | Default note length |
| `c d e f g a b` | Note names; `#`, `+`, `-` are accidentals |
| `r` | Rest |
| `>` `<` | Octave up/down |
| `[ … ]xN` | Repeat N times |
| `@lead` `@pad` `@echo` `@bass` … | Switch instrument in fully handwritten mode |

### Writing discipline

1. Structure before notes.
2. Write the complete MML in one pass; do not probe syntax by repeatedly running the tool.
3. Prefer `progression` + style options + handwritten S1.
4. If a channel needs to break the generated pattern, handwrite that channel.
5. Export only after the MML is complete.

---

## Export

```jsonc
// uvid_generate_bgm
{
  "input": "<EPISODE>/bgm.mml",
  "output": "<EPISODE>/clips/bgm.mp3",
  "duration": 62,          // optional; full song if omitted
  "format": "mp3",         // mp3 | wav | aac; also from output extension
  "sampleRate": 48000,     // 44100 or 48000
  "bitrate": 192,
  "lufs": -36,
  "tp": -9,
  "lra": 11
}
```

| Param | Default | Meaning |
|---|---|---|
| `duration` | Full song | Export duration in seconds when the bed must cover a known BGM window |
| `format` / output extension | mp3 | `mp3` / `wav` / `aac` |
| `sampleRate` | 48000 | 44100 or 48000 |
| `bitrate` | 192 | FamiStudio/final bitrate in kbps |
| `lufs` / `tp` / `lra` | **-36** / -9 / 11 | Bed loudness (under −16 LUFS voice) |

### Loudness stack (do not stack both knobs aggressively)

Voice stays **−16 LUFS / −1.5 dBTP / 11 LRA**. BGM is quieter in two places:

| Layer | Default | Notes |
|---|---|---|
| `uvid_generate_bgm` loudnorm | **−36 LUFS / −9 dBTP / 11 LRA** | Export bed level |
| `timeline.bgm.volume` (program compile) | **0.3** | Linear mix under dialog (~−10.5 dB) |

Target feel: bed present under speech, about **18–24 dB** quieter than voice in the mix — not a whisper.

Tuning (prefer one knob first):

| Intent | `lufs` | `timeline.bgm.volume` |
|---|---|---|
| Default (recommended) | −36 | 0.30 |
| Still soft | −36 | 0.35 |
| Stronger bed / sparse VO | −34 | 0.32–0.35 |
| Music-forward | −32 | 0.35–0.40 |

Avoid `lufs` warmer than ~−32 **and** `volume` ≥ 0.5 together — bed will ride up in VO gaps. Re-export `clips/bgm.mp3` after changing `lufs`; recompile `timeline.program.json` (or edit `bgm.volume`) then re-render video after changing mix volume.

Pipeline:

```text
MML → FamiStudio text → FamiStudio mp3 export → loudnorm → output
```

Intermediate files stay in a temporary directory. Do not write `.famistudio.txt` or `.raw.mp3` next to the episode.

When `progression` is used, logs should include:

```text
accompaniment: style=… s2mode=… bass=…
```

---

## Completion checklist

- [ ] If `edit.json` exists, estimate `bgmSec` from kept aroll + TOC duration (intro end → outro start), not raw media / full program.
- [ ] Work structure-first: bgmSec, energy map, loop length/form, then MML.
- [ ] `bgm.mml` contains `title`, `tempo`, and either `progression`+S1 or fully handwritten S1+TR.
- [ ] `uvid_generate_bgm` writes `clips/bgm.mp3` with duration ≥ the required BGM window (intro end → outro start).
