GatsbyJS ブログ記事に楽譜を埋め込む

LilyPond を使用して、 GatsbyJS のブログ記事に自動で楽譜を埋め込むプラグインを作ってみました。 Markdown ファイルに LilyPond ソースコードを直接書くと、以下のように SVG 形式で楽譜を埋め込むことができます。楽譜作成ソフトから手動で SVG を出力して貼り付ける作業が不要になります。

```lilypond:embed
\version "2.22.0"
\paper { page-breaking = #ly:one-line-auto-height-breaking }

chordmusic = \chordmode {
  \clef treble
  \omit Score.TimeSignature
  \set majorSevenSymbol = \markup { M7 }
  c1:maj7 d:m7 e:m7 f:maj7 g:7 a:m7 b:m7.5-
}

<<
  \new ChordNames \chordmusic
  \new Staff \chordmusic
>>
```

音楽理論を解説するブログなんかに使えそうです。

LilyPond とは

LilyPond は楽譜を作成するソフトウェアです。市販の楽譜作成ソフトは GUI で楽譜を作成しますが、 LilyPond はテキストファイルをコンパイルすることで楽譜を作成します。まさに楽譜界の TeX です。

記述の自由度は極めて高く、五度圏などの図も LilyPond 単体で描くことができます。また、 LISP の方言である Scheme が組み込まれているため、動作を詳細にカスタマイズすることができます。

出力は、 PDF はもちろん SVG 形式などでも出力することができます。この記事では SVG 出力を利用して、楽譜を記事に埋め込んでみようと思います。

ちなみに Lily はユリ、 Pond は池という意味です。某都知事の名前を彷彿とさせますね。

前提条件

GatsbyJS と gatsby-transformer-remark で構築されたブログが必要です。

また、ブログの開発やビルドをするシステムに LilyPond がインストールされている必要があります。 Arch Linux なら以下のコマンドでインストールできます。

# pacman -S lilypond

macOS だとインストールが少々面倒みたいです。 Windows は試していません。 WSL (Windows Subsystem for Linux) なら何とかなるかも知れません。

ここまで書いておいてアレですが、正直 Docker 使えばどうとでもなります。

プラグインを書く

GatsbyJS において、 Markdown ファイルを HTML に変換するプラグインは gatsby-transformer-remark です。 gatsby-transformer-remark 用のプラグインを書くことで、 Markdown の変換の挙動をカスタマイズすることができます。プラグイン作成のドキュメントは Creating a Remark Transformer Plugin | Gatsby にあります。

それでは gatsby-remark-lilypond という名前でプラグインを作りましょう。まず GatsbyJS プロジェクトのルートディレクトリに plugins/gatsby-remark-lilypond ディレクトリを作ります。このディレクトリの中にプラグインを書くことになります。

$ mkdir -p plugins/gatsby-remark-lilypond

プラグインには package.json が必要なので、最小限の package.json を書きます。

plugins/gatsby-remark-lilypond/package.json
{
  "name": "gatsby-remark-lilypond"
}

後は index.js にプラグイン本体のコードを書きます。

plugins/gatsby-remark-lilypond/index.js
const child_process = require('child_process');
const crypto = require('crypto');
const fs = require('fs');
const visit = require("unist-util-visit");

module.exports = ({ markdownAST }, { lilypondPath = 'lilypond' }) => {
  visit(markdownAST, 'code', node => {
    const { lang, value: source } = node;

    // コードブロックの言語判定
    if (lang !== 'lilypond:embed') return;

    // LilyPond コードのハッシュから、一時生成ファイルのパスを決定
    const hash = crypto.createHash('sha256').update(source).digest('hex');
    const filename = `/tmp/lilypond-generated-${hash}`;
    const filenameWithExt = `${filename}.svg`;

    // LilyPond を実行し、 SVG ファイルを生成 (標準出力への出力は未サポート?)
    child_process.execFileSync(lilypondPath, [
      '-d', 'no-point-and-click',
      '-f', 'svg',
      '-o', filename,
      '-'
    ], { input: source });

    // 生成した SVG ファイルを読み取り、 SVG ファイルを削除
    const svg = fs.readFileSync(filenameWithExt, 'utf-8');
    fs.unlinkSync(filenameWithExt);

    // コードブロックノードを HTML ノードで上書き
    node.type = 'html';
    node.children = void 0;
    node.value = `<div class="lilypond-generated">${svg}</div>`;
  });
  return markdownAST;
};

プラグインには、 Markdown の AST (Abstract Syntax Tree) が渡されます (AST の詳細は syntax-tree/mdast に書いてあります) 。 Markdown のコードブロック (``` で囲まれた部分) の言語を判定し、 lilypond:embed だった場合は変換処理が走るようにしています。 LilyPond は楽譜の出力を標準出力へ流せないみたい (流せるかもしれない?) なので、 /tmp 以下に楽譜を生成し、それを読み取っています。

これでプラグインはひとまず完成です。

プラグインを使う

プラグインを書いたので、これを使うように設定しましょう。 gatsby-config.js を編集します。

gatsby-config.js
 module.exports = {
   // ...
   plugins: [
     // ...
     {
       resolve: 'gatsby-transformer-remark',
       options: {
         plugins: [
+          'gatsby-remark-lilypond',
           // ...
         ]
       }
     }
     // ...
   ],
   // ...
 };

gatsby-transformer-remark の設定で、先程作った gatsby-remark-lilypondplugins に指定します。 gatsby-remark-prismjs などと干渉することを防ぐため、先頭に書きます。

これで設定は完了です。

記事を書く

あとは Markdown で記事を書くだけです。

articles/lilypond.md
---
title: "LilyPond のテスト"
slug: /lilypond-test
date: 2021-05-04T00:00:00
---

```lilypond:embed
\version "2.22.0"
\paper { page-breaking = #ly:one-line-auto-height-breaking }

{
  \time 2/4
  \clef treble
  \relative {
    c'8 d e f
    g4 a8 f
    e4 d
    c2
  }
  \bar "|."
}
```

ファイルを保存すると自動的に LilyPond が走り、以下のようにレンダーされると思います。

生成された楽譜は CSS の color プロパティの色で描画されます。

記事に SVG が直接埋め込まれるので、一度ビルドしてしまえばブログは爆速で動作します。

GitHub Actions でビルドする

前提条件の節でも説明した通り、ビルドをするシステムにも LilyPond がインストールされている必要があります。

GitHub Actinos の場合、ブログをビルドする前に LilyPond をインストールするようステップを追加しておく必要があります。

.github/workflows/ci.yml
 # ...

 jobs:
   build:
     name: Build
     runs-on: ubuntu-latest
+    env:
+      LILYPOND_INSTALLER_VERSION: 2.22.1-1
     steps:
       # ...

+      - name: Prepare LilyPond installer cache
+        id: lilypond-installer-cache
+        uses: actions/cache@v2
+        with:
+          path: install-lilypond.sh
+          key: ${{ runner.os }}-lilypond-installer-${{ env.LILYPOND_INSTALLER_VERSION }}
+      - name: Download LilyPond installer
+        if: steps.lilypond-installer-cache.outputs.cache-hit != 'true'
+        run: curl -o install-lilypond.sh "https://lilypond.org/download/binaries/linux-64/lilypond-${LILYPOND_INSTALLER_VERSION}.linux-64.sh"
+      - name: Install LilyPond
+        run: sh install-lilypond.sh --batch
+      - name: Add Path
+        run: echo "$HOME/bin" >> $GITHUB_PATH

       # ...

LilyPond の ダウンロードページ からインストーラをダウンロードして、インストールするよう設定しています。

環境変数 LILYPOND_INSTALLER_VERSION に LilyPond インストーラのバージョンを指定しています。

バージョン 2.22.1-1 現在ではインストーラのサイズは約 41 MB あり、ビルドの度にダウンロードしに行くとサーバーに余計な負荷を掛けてしまうことになります。リポジトリで Dependabot などを有効にしていると、同時にビルドがたくさん走ることになり危険です。複数回のダウンロードを避けるため、ここでは actions/cache@v2 を使用してインストーラをキャッシュしています。

キャッシュがヒットした場合はインストーラがリストアされます。キャッシュがヒットしなかった場合、 curl を使用して LilyPond インストーラをダウンロードします。

あとはインストーラを実行して、 LilyPond をインストールします。 LilyPond のバイナリは $HOME/bin/lilypond に出力されるので、 PATH を通す必要があります。

LilyPond のインストールステップ以降で GatsbyJS プロジェクトのビルドを行うよう設定します。

これで、 GitHub Actions でもビルドができるようになるはずです。

ギャラリー

適当なレンダリング例をいくつか並べておきます。

お辞儀の和音

簡単なピアノスコアのレンダリング例から紹介します。

幼稚園児の時によく聞いたであろう和音。トニック → ドミナント → トニックの進行で、音楽理論の超基本が詰まっている。

ドミナントセブンスの解決

色を付けることもできます。

G7 の B と F が減五度の音程つまりトライトーンとなり非常に不安定な響きとなる。これが C と E に移ることで解決する。

ピアノ譜

もちろん本格的な楽譜をレンダリングすることもできます。

ショパンの幻想即興曲の冒頭部分です。

オーケストラのフルスコアのような大きい楽譜も記事に埋め込むことができますが、普通はそんなことしないと思います。

歌詞とコード

歌詞やコードを出すことができます。ギターのフレットボードダイアグラムも自動的に生成できます。デフォルトで指番号もレンダーされる親切設計。

あの曲のサビのボーカル譜です。一部コードは簡略化しています。王道進行 (IV → V → IIIm7 → VIm7) と Just the Two of Us 進行 (IV → III7 → VIm7 → Vm7 → I7 (→ IV)) でできています。

Just the Two of Us 進行のセカンダリードミナント III7 が鳴った瞬間に「あの進行か?」と身構えるようになった人は多いはず。最後のセカンダリードミナントのツーファイブ (Vm7 → I7) を省略して I もしくは I7 単体にすると丸ノ内サディスティック進行になる (個人的見解。諸説あり) 。

複数の声部と符頭

声部を複数のせたり、リズミックスラッシュ表記で即興や掛け声などを表すことができます。矢印で主旋律側の声部を表すこともできます。

ベースラインが II → III → IV → #IV → V と遷移する MONACA 定番の進行。

サビ前のノンダイアトニックコード 2 連発が印象的。セカンダリードミナント (V7 of IV) の代理でイキスギコード (Db+/G) が使われている。

現代音楽

もちろん休符だけの曲も描画できます。

言わずと知れたジョン・ケージの 4 分 33 秒。

この他に、途中から五線を曲げたり音部記号や調号を回転させたりなど LilyPond は自由奔放に楽譜を作ることができるらしいです。

おわり

GatsbyJS 上での remark トランスフォーマープラグインを書くことで、記事の要素を編集する方法を紹介しました。これを応用すれば LilyPond 以外でも色々な要素をブログに追加することができます。

ちなみに筆者は何も楽器できません。