# Write the music

Read the episode's timing, write a score as a song file, check it, and play it into a music track on your computer.

Your agent writes the music as a short Python script. The script saves a song file, which is data: instrument names, notes and times in seconds. MiniCut checks the song file and plays it with the instruments it brings. MiniCut never runs your script.

Before you start, sign in and make a project with a shot sheet for the episode, `episodes/ep01/shots.md`. See [Shot sheet](/docs/cli/shot-sheet). The script needs Python 3.9 or newer. To play a song, run `minicut setup` once. It installs the music tools and the instrument samples. The samples are about 396 MB. Run the commands in the project folder, `~/.minicut/projects/rain-shop` here.

## Read the timing

1. List the shots, the cuts and the spoken lines of the episode, in seconds:

```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)
```

The spoken lines come from the `Line (speaker)` column of `shots.md`, rough to the whole shot. When the episode has a `transcript.json`, they come from its word times. Add `--json` to get the same facts as JSON.

2. List the instruments:

```bash Terminal
minicut music instruments
```

It prints one line for each instrument with its note range, then the knobs and the reverb presets. A drum instrument lists its pieces instead of notes.

## Write the score

1. Print the import lines for your script:

```bash Terminal
minicut music api
```

2. Make the file `episodes/ep01/music/score.py`. Paste the two lines the command printed at the top. Then write the score. Every time is in seconds.

```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` turns the score down under each spoken line. `save` checks the song and writes it. On a problem it writes nothing, prints each problem and exits with code 5.

3. Run the script with your own 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
```

A song is up to 15 minutes long and has up to 64 tracks.

## Check and play the song

1. Check that the song file can be played:

```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
```

If it fails, it prints each problem and `FAIL`, and exits with code 5. Fix the script, run it again and check again.

2. Play the song into audio files:

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

It writes `score.wav`, `score.mp3`, `score.beats.json`, `score-waveform.png` and `score.report.json` beside the song file.

3. Optional: to play one stretch while you work on it, add `--from 9 --to 12`. To also write each instrument as its own file, add `--stems`. To skip the `.mp3`, add `--no-mp3`.
4. Measure the result against the episode:

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

It reports loudness, peaks, hits on the cuts and the music level under the spoken lines. Change the script and play again until the numbers fit the shot sheet.

## Optional: start from a MIDI file

1. Write a map file that says which instrument plays each MIDI track. A key is `TRACK:CHANNEL` or the track's name. A `drums` key maps a MIDI note to `INSTRUMENT/PIECE`.

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

2. Turn the MIDI file into a song file:

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

It prints `Wrote` with the path of the new `.song.json` file, its length, tracks and notes, and the next command to run.

A track or drum note that is not in the map is left out, and the command prints `left out:` with its name. Then check and play the song file as above.

## Check it worked

`score.wav` and `score.mp3` are in `episodes/ep01/music`, and the `measure` numbers match what the shot sheet asks for. The waveform picture `score-waveform.png` shows where the music is loud and quiet.

## If a step fails

| Message | What to do |
|---|---|
| `shots.md is missing` | Write the shot sheet first. See [Shot sheet](/docs/cli/shot-sheet) |
| `no Python found. Run: minicut setup` | Run `minicut setup`, then run the command again |
| `the music tools are not set up (...). Run: minicut setup` | Run `minicut setup`, then run the command again |
| `no instrument samples in .... Run: minicut setup (about 396 MB, once). Nothing was rendered.` | Run `minicut setup`, then render again |
| `FAIL: N problem(s).` | Fix the problems printed above it in your script, then run it again |
| `the song file is over 32 MB` | Make the song smaller |
| `Stopped: it ran past the time limit.` | Render a shorter part with `--from` and `--to` |

## Next

[Scene and music](/docs/cli/scene-and-music) [Checks and loops](/docs/cli/checks-and-loops) [Setup](/docs/cli/setup)
