Pythonでリアルタイム演奏〜music21を使った実装〜

# はじめに
この記事は、[音楽情報処理ライブラリmusic21の公式ドキュメントで紹介されているコード](https://web.mit.edu/music21/doc/moduleReference/moduleMidiRealtime.html?highlight=realtime#module-music21.midi.realtime)を実際に動かしてみて、日本語で詳しい解説をつけたり感想を書いたりしたものです。書いてて楽しくなってきちゃったのでソースコードの「全行解説」にも挑戦しました。


# 動機
皆さん、今やpythonでなんでもできると思ってますよね。僕もそう思っています。
人それぞれに作りたいものがあると思いますが、僕はずっとpythonで音楽を聞きたかった。
できたらいいなぁ程の気持ちで関連技術を勉強していたのですが、簡単に実装できるアイデアが公開されているのを先ほど見つけたので紹介します。
正直コピペなんですが、解説記事を書くことで自分の理解を深めたいなぁと思ってキーボードに向かっています。
# 環境
macOS(10.15.7)
python3.8.5
jupyter-notebook

music21とpygameがインストールしてあるpython環境

# Music21について
Music21は、MITから公開されている音楽情報処理に長けたpythonライブラリです。[公式ドキュメントはこちら](https://web.mit.edu/music21/doc/index.html#)
音楽に関すること、特に楽譜で表現できることを扱うパワフルな機能が多数搭載されています。先日、[概要を紹介した記事](https://qiita.com/iNomaG/items/16081daf103a042ea0fe)を書いたので気になる方はこちらもご覧になってください。

# Pygameについて
Music21はリアルタイム処理を専門としていません。そのため、ここで主に扱うオブジェクトであるmusic21.midi.realtimeはPygameに依存しています。環境にpygameを用意しておいてください。
# 早速動かしてみよう
何も考えないで公式ドキュメントからコピペして実行します。music21のimport文だけは追加で書いときます。
何が起こるのかは起こってから考えます。

```python
from music21 import*
import random
keyDetune = []
for i in range(127):
   keyDetune.append(random.randint(-30, 30))
b = corpus.parse('bwv66.6')
for n in b.flat.notes:
   n.pitch.microtone = keyDetune[n.pitch.midi]
sp = midi.realtime.StreamPlayer(b)
sp.play()
```
## 出力結果
音声が重要なので動画でご覧ください。
https://youtu.be/M7SP6j82pmI
バッハだ！やったー！
(僕の環境で実行した時はライブラリの依存先であるpygameが起動したときに色々主張してきたんですが、無視)


## コード全行解説
### コード解説の前に
早速コードを一行ずつ見ていきたいところですが、その前に。
出力された音楽を聴いて、違和感を覚えた人がいるかと思います。
機械が出力する音楽にしては、ピッチが正確でないですね。
安心してください。これは技術の限界などではなく、コード側でわざわざ音程を狂わせてるんです。なんでそんなことを、と思いますが、次の段落で解説します。

全行解説は冗長かもしれませんが、結構コードを読むのに背景知識を使うので。
##### ピッチズレ配列の生成
```python
keyDetune = []
for i in range(127):
   keyDetune.append(random.randint(-30, 30))
```

for文を使ってランダムに127個ズレの大きさ(最大±30cent)を設定しています。

__定数127はMIDIのノートナンバーの総数__ですね。数値ベタ書きはちょっと親切じゃないなと思いました。MIDIでは鍵盤の音に0~127の数を割り振っています。１０オクターブ半+半音相当です。

つまるところこれは、__ここで再現するピアノは各鍵盤がそれぞれちょっとズレてるよ__、という配列です。バロック音楽を演奏する古楽器の雰囲気が出てるのかもしれません。知りません。30centもズレてることあるんですか？

（なお、DTMにはdetuneというテクニックがあるようです[（参考）](https://sleepfreaks-dtm.com/dtm_word/2-9te/detune/)）

##### courpus
```python
b = corpus.parse('bwv66.6')
```
music21の誇る強力な曲データベース、corpusからバッハの曲を取得します。
主にクラシックの楽譜が多数収録されており、中身の音符のデータとかもいじれます。

parseメソッドが返す曲オブジェクトはmusic21.stream.Scoreオブジェクトと言います。

bwvというのは[バッハ作品主題目録番号](https://ja.wikipedia.org/wiki/%E3%83%90%E3%83%83%E3%83%8F%E4%BD%9C%E5%93%81%E4%B8%BB%E9%A1%8C%E7%9B%AE%E9%8C%B2%E7%95%AA%E5%8F%B7#:~:text=%E6%AD%A3%E5%BC%8F%E5%90%8D%E7%A7%B0%E3%81%AF%20Thematisch%2Dsystematisches,%E3%81%BE%E3%81%9F%E3%81%AF%E8%8B%B1%E8%AA%9E%E5%BC%8F%E3%81%AB%E3%83%93%E3%83%BC)のことです。バッハの曲が一意に特定できます。

```python
for n in b.flat.notes:
   n.pitch.microtone = keyDetune[n.pitch.midi]
```
曲中の全ての音符（note） オブジェクトにアクセスして、microtoneプロパティに先ほど作ったピッチズレを代入。KeyDetune配列のキーがMIDI番号になってるのがミソですね

##### flatプロパティ
さて、b.flat.notesという部分が気になる方もいるかもしれません。flatというのは、入れ子構造になっているstreamを全て展開して読み取りやすくした新たなstreamを返してくれる読み込み専用プロパティです。

例を挙げるために、簡単な２声の楽譜を用意します。（公式ドキュメントからコピペ）
パートが二つあっても、一つのscoreオブジェクトに格納できます。

```python
sc = stream.Score()
p1 = stream.Part()
p1.id = 'part1'
n1 = note.Note('C4')
n2 = note.Note('D4')
p1.append(n1)
p1.append(n2)

p2 = stream.Part()
p2.id = 'part2'
n3 = note.Note('E4')
n4 = note.Note('F4')
p2.append(n3)
p2.append(n4)

sc.insert(0, p1)
sc.insert(0, p2)
```
作ったscoreオブジェクトの要素を見てみましょう

```python
sc.elements
```
`(<music21.stream.Part part1>, <music21.stream.Part part2>)`

sc直下にあるのはstream.Partオブジェクトだけです。'E4'などのNoteオブジェクトはさらに下層にあるので、elementプロパティからは直接みえないんですね〜。

だから、このままflatプロパティを使わずに下層オブジェクトに触れようと思ったらこう書く必要があります。

```python
sc[0].elements
```
`(<music21.note.Note C>, <music21.note.Note D>)`


もしくは、


```python
sc.getElementById('part2').elements
```
`(<music21.note.Note E>, <music21.note.Note F>)`

こういうのが面倒くさいので、flatプロパティが存在しています。

```python
sc.flat.elements
```
`(<music21.note.Note C>, <music21.note.Note E>,
 <music21.note.Note D>, <music21.note.Note F>)`

入れ子構造がすっきりしましたね。
こいつのおかげで、前述のfor文の中にNoteオブジェクトを簡単に渡せるってワケです。

##### StreamPlayer
こいつが今回の「演奏」部分の核ですね。

```
sp = midi.realtime.StreamPlayer(b)
sp.play()
```
そんなに言うことはないです。
corpasから持ってきたオブジェクトを渡して、pc上で演奏してくれてます。カンタン！
渡すオブジェクトはstreamである必要があります。
## 終わりに
MITのドキュメントがめちゃくちゃ丁寧なおかげで、全行解説とかいう無茶も一応やり切ることができました。
music21で遊びたい時は、公式ドキュメントを読むことを強くお勧めします。

このmusic21ライブラリはあくまで「楽曲分析」のためのものであって、演奏機能はおまけに過ぎません。
それなのにここまでのことができるのはさすがMITと言わざるを得ませんが、演奏機能をメインに据える何かを開発したいなら、他のライブラリも調べた方が良さそうです。