はじめに
この記事は、音楽情報処理ライブラリmusic21の公式ドキュメントで紹介されているコードを実際に動かしてみて、日本語で詳しい解説をつけたり感想を書いたりしたものです。書いてて楽しくなってきちゃったのでソースコードの「全行解説」にも挑戦しました。
動機
皆さん、今やpythonでなんでもできると思ってますよね。僕もそう思っています。
人それぞれに作りたいものがあると思いますが、僕はずっとpythonで音楽を聞きたかった。
できたらいいなぁ程の気持ちで関連技術を勉強していたのですが、簡単に実装できるアイデアが公開されているのを先ほど見つけたので紹介します。
正直コピペなんですが、解説記事を書くことで自分の理解を深めたいなぁと思ってキーボードに向かっています。
環境
macOS(10.15.7)
python3.8.5
jupyter-notebook
music21とpygameがインストールしてあるpython環境
Music21について
Music21は、MITから公開されている音楽情報処理に長けたpythonライブラリです。公式ドキュメントはこちら
音楽に関すること、特に楽譜で表現できることを扱うパワフルな機能が多数搭載されています。先日、概要を紹介した記事を書いたので気になる方はこちらもご覧になってください。
Pygameについて
Music21はリアルタイム処理を専門としていません。そのため、ここで主に扱うオブジェクトであるmusic21.midi.realtimeはPygameに依存しています。環境にpygameを用意しておいてください。
早速動かしてみよう
何も考えないで公式ドキュメントからコピペして実行します。music21のimport文だけは追加で書いときます。
何が起こるのかは起こってから考えます。
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が起動したときに色々主張してきたんですが、無視)
コード全行解説
コード解説の前に
早速コードを一行ずつ見ていきたいところですが、その前に。
出力された音楽を聴いて、違和感を覚えた人がいるかと思います。
機械が出力する音楽にしては、ピッチが正確でないですね。
安心してください。これは技術の限界などではなく、コード側でわざわざ音程を狂わせてるんです。なんでそんなことを、と思いますが、次の段落で解説します。
全行解説は冗長かもしれませんが、結構コードを読むのに背景知識を使うので。
ピッチズレ配列の生成
keyDetune = []
for i in range(127):
keyDetune.append(random.randint(-30, 30))
for文を使ってランダムに127個ズレの大きさ(最大±30cent)を設定しています。
__定数127はMIDIのノートナンバーの総数__ですね。数値ベタ書きはちょっと親切じゃないなと思いました。MIDIでは鍵盤の音に0~127の数を割り振っています。10オクターブ半+半音相当です。
つまるところこれは、ここで再現するピアノは各鍵盤がそれぞれちょっとズレてるよ、という配列です。バロック音楽を演奏する古楽器の雰囲気が出てるのかもしれません。知りません。30centもズレてることあるんですか?
(なお、DTMにはdetuneというテクニックがあるようです(参考))
courpus
b = corpus.parse('bwv66.6')
music21の誇る強力な曲データベース、corpusからバッハの曲を取得します。
主にクラシックの楽譜が多数収録されており、中身の音符のデータとかもいじれます。
parseメソッドが返す曲オブジェクトはmusic21.stream.Scoreオブジェクトと言います。
bwvというのはバッハ作品主題目録番号のことです。バッハの曲が一意に特定できます。
for n in b.flat.notes:
n.pitch.microtone = keyDetune[n.pitch.midi]
曲中の全ての音符(note) オブジェクトにアクセスして、microtoneプロパティに先ほど作ったピッチズレを代入。KeyDetune配列のキーがMIDI番号になってるのがミソですね
flatプロパティ
さて、b.flat.notesという部分が気になる方もいるかもしれません。flatというのは、入れ子構造になっているstreamを全て展開して読み取りやすくした新たなstreamを返してくれる読み込み専用プロパティです。
例を挙げるために、簡単な2声の楽譜を用意します。(公式ドキュメントからコピペ)
パートが二つあっても、一つのscoreオブジェクトに格納できます。
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オブジェクトの要素を見てみましょう
sc.elements
(<music21.stream.Part part1>, <music21.stream.Part part2>)
sc直下にあるのはstream.Partオブジェクトだけです。'E4'などのNoteオブジェクトはさらに下層にあるので、elementプロパティからは直接みえないんですね〜。
だから、このままflatプロパティを使わずに下層オブジェクトに触れようと思ったらこう書く必要があります。
sc[0].elements
(<music21.note.Note C>, <music21.note.Note D>)
もしくは、
sc.getElementById('part2').elements
(<music21.note.Note E>, <music21.note.Note F>)
こういうのが面倒くさいので、flatプロパティが存在しています。
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と言わざるを得ませんが、演奏機能をメインに据える何かを開発したいなら、他のライブラリも調べた方が良さそうです。