2022年11月24日木曜日

MIDI を楽譜に変換する midi2abc を作った

MIDI を使ったアプリを作りたかったので、その前準備として、MIDI を楽譜に変換する midi2abc を作りました。



楽譜を自動生成する利点

楽譜は MIDI から変換せず、手作業で作成したもののほうが確実ではありますが、自動生成にも利点があります。 まず、(1) 楽譜のフォーマットは非常に種類が多く、とてもすべてサポートできるものではありません。 また既にほとんど使われなくなったアプリの独自フォーマットも多く、サポートしても今後使い物になるのかという問題が起きているケースもあります。 有名な楽譜フォーマットでも相互変換もできないことがほとんどで、現実的には MIDI に変換してそこから他の楽譜フォーマットに変換することが多いです。 この点に関しても特定のアプリに依存しすぎている問題があります。 それなら MIDI から楽譜を自動生成する基盤を整備したほうが良いように思います。

(2) フルスコアと楽器ごとのパートスコアの動的生成に対応できます。 MIDI の 127 の楽器をすべて使ったフルスコアは、楽譜がカオスになって使いものにならない問題があります。 そのようなとき、現実的には DTM のような表示にするか、楽器を限定したパートスコアへの変換が必須と思うのですが、 生成済みの楽譜をベースにしたサービスでは取り扱いが難しいことに気付きます。 自動生成すれば対応しやすいです。 そしてこれは変換ツールというよりフロントエンドの役割です。

(2) 楽譜は曖昧性が高いフォーマットなので、MIDI から変換しようとするとどうやっても不完全になります。 ただ逆に言えば、重要性の高い記法だけを利用した楽譜に変換することで、シンプルになります。 古い楽譜を見ていると今は使われなくなった記法が多々存在しますが、そういったものをシンプル化することができます。 読みやすくなったり学習しやすくなる効果が生まれそうです。 既に最もシンプルな表現になるような実装ではあるのですが、 将来的には楽譜の難易度を変える機能なども作れるかも知れません。 楽譜の生成が完璧でなくてもこのような新たな価値を提供していくのはアリかもと思っています。

楽譜表示ライブラリは abc.js を採用

楽譜を表示するライブラリはいくつかありますが、 abc.jsOpenSheetMusicDisplay (OSMD)、Verovio のどれかが良さそうです。 本当は Magenta.js の楽譜機能が MIDI から楽譜を生成できて便利なのですが、 残念ながら現時点では実用に耐えません。 そして OSMD、Verovio には MusicXML で楽譜を表示する以上の説明がなくてつらいので、 発展的なアプリは作りにくいです。 そこで仕組みがわかりやすい abc.js を採用しました。

abc.js を採用した理由は API の充実度以外にもたくさんあります。まとめると以下のような理由があります。 (1) abc notation はファイルサイズが小さく、仕様がわかりやすく、パースしやすいです。 MusicXML は手作りしたくない一方で、abc notation なら手作りできそうでした。 (2) 表示される楽譜が綺麗です。OSMD / Verovio も同レベルに綺麗ですが、その他のライブラリと比べるとやはり大きな利点と言えます。 (3) MuseScore / MusicXML は外部アプリケーションの依存性が強すぎて自由度が低いです。 たとえば MusicXML を生成するためには今のところ MuseScore が必要と思うのですが、必要としてしまうとそこからの発展性がないので依存はあまり好ましくありません。 自由度が非常に高い abc notation をさらに使いやすくしていくことのほうが、OSS としての価値を感じました。 (4) abc notation で既に作られた資産は他と比較して非常に大きいです。 消滅すると明らかに困る資産です。

MIDI 解析は Mageta.js を利用

MIDI から楽譜への変換は、abcmidi に含まれる midi2abc (C言語) を利用すれば、 パート数の多い複雑な楽曲以外は割と綺麗に変換できました。 そのため wasm 化して利用させてもらえば、そのまま使えそうにも思いました。 ただコードを読んでいると、wasm に変換するとき file read がどうなるかよくわからないツラミがあり、 また MIDI 仕様への依存が大きく、JavaScript 脳の私には処理がわかりにくいと感じました。 楽譜生成した後にも楽譜を動的に調整できたほうが嬉しいと思ったのですが、 その時に wasm だと苦しそうな気がして、自前で実装することにしました。

具体的な方法としては、Magenta.js が MIDI をパースして Tone.js の NoteSequence に変換してくれるので、 NoteSequence を通じて MIDI と abc notation を相互変換することにしました。 この方法なら MIDI の解析部をパスして実装できてわかりやすいです。

楽譜への変換処理は、実装してみると細かな制約があることにも気付きましたが、許容範囲でした。 正確に確認した訳ではないのですが、公開時点ではクラシックならほとんどの MIDI は良い感じに再生できる印象です。 ゲーム音楽くらい複雑になるとまだまだな感じです。 あと聴くだけなら耐えられても、楽譜としては見るに耐えないものもあるので、まだまだ改善が必要です。

TODO

今のところの課題は正確な連符処理、装飾、転調などです。 他にも MIDI ファイルの楽器や楽譜の扱いが不十分だと楽譜がカオスになる問題はあります。 綺麗な楽譜を出力するにはそこが一番のハードルになるだろうと思っています。 そこまで作り込めば楽譜としてもほぼ完璧なものが出力できるようになると思いますが、 その後にも歌詞表示、ループ解析、小節解析、ドレミ表示など、やりたいことはたくさんあります。

100% の完成度を目指すといつ公開できるかわからないので、α版で公開しました。 コードは安定していないので利用しないほうが良く、ひとまず基本的なバグがなくなった状態です。 α版とは言っても簡単な曲なら綺麗に楽譜にできるはずです。