music21はMITのMichael Scott Cuthbertらが開発しているPython製の音楽解析ツールキットです。楽譜データをオブジェクトとして読み書き・操作できることから音楽学の研究でよく使われますが、その裏返しとして「音楽理論を理解したオブジェクトモデル」を持つアルゴリズム作曲のための土台としても優秀です。この記事ではmusic21のデータ構造を出発点に、アルゴリズム作曲の観点から何ができるかを見ていきます。
music21とは
music21は楽譜(スコア)をPythonのオブジェクトとして表現し、解析・変換・生成するためのツールキットです。MusicXML・MIDI・Humdrum(kern)・ABCなど複数の形式でのインポート/エクスポートに対応し、Bachのコラールをはじめとした数百曲規模のコーパス(corpusモジュール)を同梱しています。単体で音を鳴らすシンセサイザーは持たず、生成した譜面はMuseScoreやFinaleなど外部の記譜ソフトに渡して描画・再生する設計になっています。MusicXMLの仕様については楽譜のデジタル表現 - 1. MusicXMLを参照してください。
中心となるデータ構造: Stream
music21のオブジェクトモデルの中心はstream.Streamです。Score・Part・Measure・VoiceはすべてStreamのサブクラスで、「時間軸上のオフセット位置に音楽要素を格納したコンテナ」という点で共通しています。Scoreは複数のPartを持ち、Partは複数のMeasureを持つ、という入れ子構造で1曲の楽譜を表現します。
from music21 import stream, note
s = stream.Stream()
s.append(note.Note('C4', quarterLength=1.0))
s.append(note.Note('E4', quarterLength=1.0))
s.append(note.Note('G4', quarterLength=2.0))
s.show('musicxml') # MuseScoreなど登録済みアプリで表示
appendは末尾に、insert(offset, element)は任意のオフセットに要素を挿入します。アルゴリズム作曲では生成ロジックが計算した時刻に音符を差し込みたい場面が多く、insertによるオフセット直接指定が基本操作になります。
基本オブジェクト: Pitch・Duration・Note・Chord
note.Noteはpitch.Pitch(音高)とduration.Duration(長さ)を保持するラッパーで、chord.Chordは複数のPitchをまとめて和音として扱います。ピッチは英語音名('C4'のような文字列)のほか、MIDIノート番号や周波数からも生成できます。
from music21 import chord
c = chord.Chord(['C4', 'E4', 'G4'])
print(c.pitchedCommonName) # 'C-major triad'
print(c.root().name) # 'C'
Chordが根音・第3音・第5音や転回形を自動的に把握できる点は、和音を単なる音高の集合としてではなく機能を持ったオブジェクトとして扱いたいアルゴリズム作曲にとって重要です。
乱数だけに頼らない生成: scale・key・roman
サイコロ的な乱数選択だけで音を並べると調性感のない結果になりがちですが、music21はscale・key・romanモジュールで音楽理論的な制約を生成ロジックに組み込めます。
import random
from music21 import stream, scale, note
sc = scale.MajorScale('D')
pitches = sc.getPitches('D4', 'D5') # Dメジャースケールの1オクターブ分
melody = stream.Stream()
for _ in range(8):
p = random.choice(pitches)
melody.append(note.Note(p, quarterLength=0.5))
さらにroman.RomanNumeralを使えば「I→IV→V→I」のようなローマ数字進行から具体的な和音(調ごとのピッチ集合)を直接生成できるため、和声法のルールに沿ったコード進行ジェネレータを比較的少ないコードで組み立てられます。
from music21 import roman, key
k = key.Key('D')
for numeral in ['I', 'IV', 'V', 'I']:
rn = roman.RomanNumeral(numeral, k)
print(rn.pitchNames)
コーパスとマルコフ連鎖
corpusモジュールにはBachのコラール(371曲)やドイツ民謡データベースなど、統計的な生成のための学習データがあらかじめ収録されています。既存曲から音高の遷移確率を集計し、マルコフ連鎖で新しい旋律を生成するのはmusic21を使ったアルゴリズム作曲でよく紹介される例です。
from collections import defaultdict
import random
from music21 import corpus
score = corpus.parse('bach/bwv66.6')
notes = [n.pitch.nameWithOctave for n in score.parts[0].flatten().notes]
transitions = defaultdict(list)
for a, b in zip(notes, notes[1:]):
transitions[a].append(b)
current = notes[0]
generated = [current]
for _ in range(15):
current = random.choice(transitions[current])
generated.append(current)
1次のマルコフ連鎖(直前の1音だけを参照)は局所的なつながりは自然でも曲全体としては迷走しがちなので、参照する状態を2音・3音分に増やした高次マルコフ連鎖にすると、フレーズらしいまとまりが出やすくなります。学習元をBachのコラールから民謡データベースに差し替えるだけで、生成結果の様式感を変えられるのもcorpusを土台にする利点です。
生成結果を検証する: analysis
生成した譜面が「それらしいか」をコード側でチェックできるのもmusic21の強みです。analysis.discreteにはKrumhansl-Schmucklerアルゴリズムによる調判定などが実装されており、生成した旋律が意図した調に収まっているかを推定できます。またvoiceLeadingモジュールを使うと、並行5度・並行8度の検出など声部書法上のルール違反を機械的にチェックでき、ランダム生成やマルコフ連鎖で出力した多声部の結果を、対位法の観点でフィルタリングするような使い方も可能です。
出力とレンダリング
生成したStreamは.write('musicxml')や.write('midi')でファイルに書き出せるほか、environment.UserSettings()でMuseScoreなどのパスを登録しておけば.show()一発で外部ソフトの譜面プレビューが立ち上がります。ここで書き出したMusicXMLをそのまま楽譜作成ソフトに読み込んで清書する、というのが典型的なワークフローです。
from music21 import environment
us = environment.UserSettings()
us['musicxmlPath'] = '/usr/bin/musescore4'
向き・不向き
music21が扱うのはあくまで記号レベルの楽譜データ(音高・長さ・拍子・調など)であり、波形や音響合成そのものは対象外です。したがって音そのものを生成・変形するリアルタイム性の高い生成音楽には向かず、そうした用途にはSuperColliderのようなサーバー/クライアント型の音響合成環境(SuperColliderのアーキテクチャを参照)の方が適しています。反対に、和声・調性・対位法といった西洋音楽理論の語彙をコードの中でそのまま扱いたい場合、music21はその理論的知識をあらかじめオブジェクトとして持っている分、ゼロから音楽理論を実装するより早く「理論に基づいたアルゴリズム作曲」に到達できます。