# 写音乐

读出这一集的时间安排，把配乐写成一个歌曲文件，检查它，再在你的电脑上播放成一条音乐轨。

你的 Agent 用一个很短的 Python 脚本来写音乐。脚本保存一个歌曲文件，它是数据：乐器名、音符和以秒为单位的时间。MiniCut 检查歌曲文件，并用自带的乐器播放它。MiniCut 从不运行你的脚本。

开始之前，先登录并建一个项目，项目里要有这一集的分镜表，`episodes/ep01/shots.md`，见[分镜表](/zh-cn/docs/cli/shot-sheet)。脚本需要 Python 3.9 或更新的版本。要播放歌曲，先运行一次 `minicut setup`。它会安装音乐工具和乐器采样，采样大约 396 MB。在项目文件夹里运行命令，这里是 `~/.minicut/projects/rain-shop`。

## 读时间安排

1. 以秒为单位，列出这一集的镜头、切换点和台词：

```bash Terminal
minicut music cuts episodes/ep01
```

```text Output
~/.minicut/projects/rain-shop/episodes/ep01: 15.0 s, 3 shot(s), 2 cut(s)
  C01      0.00 to    5.00 s  street         sound: rain
  C02      5.00 to   11.00 s  shop           sound: 
  C03     11.00 to   15.00 s  shop           SILENT sound: silence
  cut    5.00 s into C02  scene change
  cut   11.00 s into C03  into silence
  line 5.00 to 11.00 s  Lin: Are you open?
  lines from: shots.md Line (speaker) cells (whole row, rough)
```

台词来自 `shots.md` 的 `Line (speaker)` 列，只精确到整个镜头。如果这一集有 `transcript.json`，台词就来自它的词时间。加 `--json` 得到同样内容的 JSON。

2. 列出乐器：

```bash Terminal
minicut music instruments
```

它每个乐器打印一行，带音域，然后是旋钮和混响预设。鼓类乐器列出的是它的各个部件，而不是音符。

## 写配乐

1. 打印你的脚本要用的导入语句：

```bash Terminal
minicut music api
```

2. 建文件 `episodes/ep01/music/score.py`。把命令打印的那两行粘贴在最上面。然后写配乐。所有时间都以秒为单位。

```python score.py
# Paste the two lines that minicut music api printed here.

ep = read_episode("episodes/ep01")
song = Song(length_s=ep["length_s"], title="ep01 score", tempo=72)
strings = song.track("violins", volume_db=-8)
strings.chord(chord("D3", "minor"), at=0, length=4, velocity=60)
drum = song.track("timpani")
for cut in ep["cuts"]:
    if cut["scene_change"]:
        drum.hit("low", at=cut["t"], velocity=110)
song.duck_under(ep["lines"], db=-8)
song.save("episodes/ep01/music/score.song.json")
```

`duck_under` 会在每句台词下面把配乐调小。`save` 检查歌曲并写出文件。有问题时它什么都不写，打印每个问题，以退出码 5 结束。

3. 用你自己的 Python 运行脚本：

```bash Terminal
python3 episodes/ep01/music/score.py
```

```text Output
Wrote episodes/ep01/music/score.song.json: 15.0 s, 2 track(s), 4 note(s). Next: minicut music render episodes/ep01/music/score.song.json
```

一首歌最长 15 分钟，最多 64 条轨道。

## 检查并播放歌曲

1. 检查歌曲文件能不能播放：

```bash Terminal
minicut music check episodes/ep01/music/score.song.json
```

```text Output
PASS: 15.0 s, 2 track(s), 4 note(s). Next: minicut music render ~/.minicut/projects/rain-shop/episodes/ep01/music/score.song.json
```

如果没通过，它会打印每个问题和 `FAIL`，以退出码 5 结束。修好脚本，再运行，再检查。

2. 把歌曲播放成音频文件：

```bash Terminal
minicut music render episodes/ep01/music/score.song.json
```

它会在歌曲文件旁边写出 `score.wav`、`score.mp3`、`score.beats.json`、`score-waveform.png` 和 `score.report.json`。

3. 可选：想在改的时候只播放其中一段，加 `--from 9 --to 12`。想把每个乐器也写成单独的文件，加 `--stems`。想跳过 `.mp3`，加 `--no-mp3`。
4. 对照这一集量一下结果：

```bash Terminal
minicut music measure episodes/ep01/music/score.wav --episode episodes/ep01
```

它报告响度、峰值、切换点上的音符，以及台词下面的音乐音量。改脚本，再播放，直到数字和分镜表对得上。

## 可选：从 MIDI 文件开始

1. 写一个映射文件，说明每条 MIDI 轨道由哪个乐器演奏。键可以是 `TRACK:CHANNEL`，也可以是轨道的名字。`drums` 键把一个 MIDI 音符映射到 `INSTRUMENT/PIECE`。

```json map.json
{
  "tracks": { "0:0": { "instrument": "piano", "volume_db": -6 } }
}
```

2. 把 MIDI 文件变成歌曲文件：

```bash Terminal
minicut music from-midi episodes/ep01/music/theme.mid --map episodes/ep01/music/map.json
```

它会打印 `Wrote` 和新的 `.song.json` 文件的路径、长度、轨道数和音符数，以及下一条要运行的命令。

映射里没有的轨道或鼓音符会被略过，命令会打印 `left out:` 和它的名字。然后像上面那样检查并播放歌曲文件。

## 检查是否成功

`score.wav` 和 `score.mp3` 在 `episodes/ep01/music` 里，`measure` 的数字和分镜表要求的一致。波形图 `score-waveform.png` 显示音乐哪里响、哪里轻。

## 某一步失败时

| 信息 | 怎么做 |
|---|---|
| `shots.md is missing` | 先写分镜表。见[分镜表](/zh-cn/docs/cli/shot-sheet) |
| `no Python found. Run: minicut setup` | 运行 `minicut setup`，再运行命令 |
| `the music tools are not set up (...). Run: minicut setup` | 运行 `minicut setup`，再运行命令 |
| `no instrument samples in .... Run: minicut setup (about 396 MB, once). Nothing was rendered.` | 运行 `minicut setup`，再播放一次 |
| `FAIL: N problem(s).` | 在你的脚本里修好它上面打印的问题，再运行脚本 |
| `the song file is over 32 MB` | 把歌曲做小一点 |
| `Stopped: it ran past the time limit.` | 用 `--from` 和 `--to` 只播放较短的一段 |

## 下一步

[场景和音乐](/zh-cn/docs/cli/scene-and-music) [检查和环节](/zh-cn/docs/cli/checks-and-loops) [初始设置](/zh-cn/docs/cli/setup)
