楽譜をCDする技術

注釈: ここでのCDはCompact DiscのほうではなくてContinuous Deploymentのほうを指します。

---

趣味で楽曲制作をしています。
普段は電子音メインなんですが、たまに生楽器の小編成な曲も書くことがあって、そのついでとして譜面化することもあります。
本職はエンジニアだし、せっかくならテキストフォーマットで書いてGitで管理したいなということで、そのあたりの話をします。

リポジトリ: https://github.com/Pctg-x8/farewell_for_reunion_score

## 楽譜をGitで管理する技術

楽曲のデータ形式としてよく知られているものにWAV, MP3, MIDIといったものがありますが、これらはいずれもバイナリフォーマットなのでGitで取り扱うには向きません。これは各種DAWのプロジェクトファイルも同様です[^prjxml]。
テキストデータで楽曲を表現できるものの一つにMMLといったものもありますが、こちらはテンポや発音や音色指定といった、どちらかというと演奏のために必要な情報しか記述できないため楽譜化するには情報がたりません。

というわけで、テキストで記述でき、かつ楽譜として必要な情報を記述できるようなフォーマットが新たに必要になります。その一つが[LilyPond](https://lilypond.org/index.ja.html)です。GNUのオープンソースプロジェクトの一つで、様々な種類の楽譜をテキストデータで記述し、PDFなどのファイルとして生成することができます。
LilyPondに読み込ませるフォーマットは基本的な音の配置を表すMML風の表記にいくつかの拡張情報を添付できるようにし、さらにそれらをいくつかのブロックにまとめて構造化したような形をしています。MML「風」としたのは、単一の音階以外にもコード名も記述できるためです。以下の`bes:m7`がそれで、この場合はBbm7を表します。

```lilypond:書きかけのscore.ly
\version "2.18"

\header {
    title = "aaa"
    composer = "SisterCat"
}

<<
\new ChordNames \chordmode {
    bes:m7
}
\new Staff \relative c' {
    \tempo 4 = 96
    \time 4/4
    c4
}
\new Staff {
    \clef bass
    \relative { c4 }
}
>>
```

これをプログラムに通して変換すると以下のような出力を得ることができます（変換方法は後述します）。

![スクリーンショット 2021-12-13 215654.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94115/6adde391-ac1b-13e1-0b70-54f4294162e6.png)

かなり幅広いタイプの楽譜の出力に対応しており、全部を紹介することは難しいので詳しい表記法については[公式ドキュメント](https://lilypond.org/doc/v2.22/Documentation/notation/index.ja.html)を参照してください。
とりあえず、これで楽譜をテキストデータとして表すことができるようになり、つまりGitで管理するのに適した形にすることができました。

## 楽譜をCDする技術

さて、楽譜ソースをGitで管理できるようになったということは他のプログラミング言語と同様にCI/CDを走らせたりすることができるようになったということでもあります。
今回はGitHubでホスティングしていますので、せっかくなのでGitHub Actionsを使って、mainに変更がpushされたら自動でアーティファクトとして楽譜のPDFファイルを生成してくれるようにしてみます。

GitHub Actionsについて詳しくは記載しませんが、特定のディレクトリに置いたyamlファイルでいい感じにジョブ/ステップを構成することでGitHub上のイベントに連動して、または手動で一連のジョブ（プログラム）を実行してくれるものです。
GitHub Actions最大の特徴はここでの「ステップ」にあたるプログラムを自由に共有して使い回すことができる点にあります。けっこういろんなものがすでに作られていて、実際今回登場したLilyPondのプログラムを実行するだけのAction[^lpa]もあります。が、自分が試した当時だと本体のバージョンが低くエラーが出てしまったので今回はActionを自作します。

今回作るワークフローは以下の単純な構成になっています。このうち2番目のステップを自作します。

1. mainをcheckout
1. lilypondでPDFを作る
1. アーティファクトとしてupload

### LilyPondで変換を実行するだけのActionを作る

今回は用途が限定的なので、ソースファイル名と出力ファイル名だけを受け取る単純なDockerアクションにします。

```dockerfile:.github/actions/lilypond_generator/Dockerfile
FROM ubuntu:bionic

ADD entrypoint.sh /entrypoint.sh
RUN apt-get update && apt-get install -y lilypond && chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]
```

LilyPond自体は普通にパッケージがありますので、インストール作業はapt-getでOKです。
`entrypoint.sh`は以下のように単にlilypondを呼び出すだけとなっています。

```sh:.github/actions/lilypond_generator/entrypoint.sh
#!/bin/bash

set -xe

lilypond -o $OUTPUT_FILE $SCORE_FILE
```

lilypondはデフォルトではPDFファイルを生成するようなのでこの引数だけでOKです。これ以外にSVG, PNG, PostScriptといった出力フォーマットに対応しており、`-f`でそれぞれ指定することが可能です[^epsformat]。
また、拡張子は省略しなければなりません。引数側でつけると二重に拡張子が付きます。

このActionを使って、環境変数で`OUTPUT_FILE`と`SCORE_FILE`を指定することでLilyPondを使って変換を行うことができるようになります。

### Workflow全体を作る

変換Actionが作れましたので、これでワークフロー自体を構成できるようになりました。以下が全体です。

```yaml:.github/workflows/generate_master.yml
name: Score PDF Autobuild
on:
  push:
    branches:
      - main
jobs:
  build:
    name: Build PDF
    runs-on: ubuntu-latest
    steps:
      - name: checkout
        uses: actions/checkout@v2
      - name: build
        uses: ./.github/actions/lilypond_generator
        env:
          SCORE_FILE: score.ly
          OUTPUT_FILE: score
      - name: publish
        uses: actions/upload-artifact@v2
        with:
          name: Score PDF
          path: score.pdf
          if-no-files-found: error
```

mainへのpushを起点にcheckout, build, publishを行う至ってシンプルなワークフローです。これは特に説明するところはないかなと思います。

## おわり

以上が楽譜を自動デプロイするためにやったこととなります。
本来はウェブサイトまで用意してS3にアップロードしてPDF本体をDLできるようにする、とかもやりたかった[^fed]んですが時間が......

[^prjxml]: 知らないだけで実はXMLで保存してます！みたいなDAWはあるかも
[^lpa]: https://github.com/marketplace/actions/lilypond-generator
[^epsformat]: Encapsulated PostScript(EPS)にも対応していますが、こちらはオプションが分かれており`-E`もしくは`--eps`で有効化することになります
[^fed]: OpenID Connect（AWS Federation）で認証してサービス使えるようにする、とか今一番アツい話題ですし、めっちゃやりたかった............