roombaの日記

読書・非線形科学・プログラミング・アート・etc...

名曲の楽譜データをMusicXML形式で入手し、プログラムで可視化する方法

はじめに

様々な曲の楽譜をデータとして扱えたら楽しそうだと思いませんか?
かといって手動で入力するのも大変です。どこかでダウンロードできると嬉しい…

調べてみたところ、クラシック音楽の楽譜データの多くがCC Zeroライセンスで公開されていることが分かりました。この記事では、

  • 好きな曲の楽譜データを検索・ダウンロードする方法
  • 楽譜データのファイル形式・解析方法
  • Pythonを使って楽譜データをプログラム中の配列として取り込む方法
  • 取り込んだ楽譜データをグラフで可視化する方法

を紹介しています。

目次

f:id:roomba:20160203150110p:plain

MusicXMLファイルのダウンロード

楽譜データはMusicXMLという形式で扱うことが可能です。
様々な楽曲のMusicXMLファイルを入手するには、MuseScoreというWebサイトで曲の名前を検索*1すれば見つかります*2
Sheet Music | MuseScore

今回は、短くてCC Zeroライセンスの以下の曲をダウンロードしてみましょう。
Bach Menuet G-Dur | MuseScore

上記URLの画面右にある"Download"をクリックするとファイル形式を選択できるので、"MusicXML"を選びます。アカウント作成が必要になると思うので登録しましょう。

Bach_Menuet_G-Dur.mxl というファイルがダウンロードされるはずです。

MusicXMLファイルの展開

この"Bach_Menuet_G-Dur.mxl"、圧縮されているのでそのまま見ることはできません。
以下の手順で展開します。

拡張子を.mxlから.zipに変更

$ mv Bach_Menuet_G-Dur.mxl Bach_Menuet_G-Dur.zip

普通にGUIでファイル名を変更してもOKです。

解凍

$ unzip Bach_Menuet_G-Dur.zip

zip解凍ソフト等を用いてもOKです。

確認

unzipすると、

  • META-INF/container.xml
  • lg-203466147999847691.xml

が現れると思います。
一つ目は基本どうでもよくて、lg-(数字).xmlの方に楽譜の情報が含まれています。次章で詳しく見てみましょう。

MusicXMLの内容

好きなテキストエディタでlg-203466147999847691.xmlを開くと、以下のようなXMLファイルになっています。
f:id:roomba:20160203104403p:plain

この中には楽譜自体の情報と印刷のための情報が混在しているので、前者に絞って説明します。

どこが重要か

3行目〜最終行の<score-partwise>タグ内に全ての情報が含まれています。
その中の、

  • <work>
  • <identification>
  • <defaults>
  • <credit>

は飛ばしてしまい、残りの

  • <part-list>
  • <part id="P1">

が重要です。

パートリスト

<part-list>タグの中身です。
名前通りパートのリストが挙げられています。

<part-list>
  <part-group type="start" number="1">
    <group-symbol>brace</group-symbol>
  </part-group>
  <score-part id="P1">
    <part-name></part-name>
    <score-instrument id="P1-I3">
	<instrument-name></instrument-name>
    </score-instrument>
    <midi-instrument id="P1-I3">
	<midi-channel>1</midi-channel>
	<midi-program>2</midi-program>
	<volume>98.4252</volume>
	<pan>0</pan>
    </midi-instrument>
  </score-part>

とりあえず、"P1"というパートが1つだけあることが分かれば十分です。

パート

<part id="P1">タグの中身で、パートリストに挙げられていた"P1"というパートの内容が書かれています。
楽譜の主要なデータを含んでいるため最も重要な部分です。

1. <measure number="1" width="***">:小節

<part id="P1">タグ内にはこのようなmeasureタグが並んでいます。measureとは「小節」のことで、numberが「何小節目か」を表します。

2. <attributes>:調・拍子・音符の最小長さを規定

最初のmeasureタグ内にはattributesタグが存在します。中身を見てみましょう。

<attributes>
  <divisions>2</divisions>
  <key>
    <fifths>1</fifths>
    <mode>major</mode>
  </key>
  <time>
    <beats>3</beats>
    <beat-type>4</beat-type>
  </time>
  <staves>2</staves>
  <clef number="1">
    <sign>G</sign>
    <line>2</line>
  </clef>
  <clef number="2">
    <sign>F</sign>
    <line>4</line>
  </clef>
</attributes>
  • <divisions>

音符・休符の最小長さを規定します。ここでは"2"となっていますが、どういう意味でしょうか?
…後述のようにこの曲は分の三拍子なので、divisionsが2ということは分音符の2分の1の長さ(=八分音符の長さ=1小節の6分の1の長さ)を音符・休符の最小長さとすることを意味します。それより細かい音符・休符が登場しない曲だからそれで良いわけですね。
このように最小長さを規定することにより、各音符・休符の長さを「最小長さ何個分か」で定義することが可能になります。

  • <key>

ハ長調ニ短調のような調を規定します。
まず、<mode>がmajorとなっていることから長調と分かります。何長調なのかを指定する方法は少しトリッキーですが、楽譜左端にあるシャープ・フラットの数から一意に指定できます(以下のWikipediaの画像を参照)。
調 - Wikipedia

MusicXMLでは#1つあたり+1, ♭1つあたり-1を加算した数字を<fifths>の中に記すきまりになっていて、この場合1になっているので#が1つのト長調という感じです。

  • <time>

拍子を指定します。beatsが3, beat-typeが4となっていますが、これは4分の3拍子という意味です。

  • <clef>

ト音記号𝄞やヘ音記号𝄢を示します。これは楽譜の表記の問題なのであまり気にしなくてOK。

3. <note>:音符や休符

各measure(小節)タグの中に存在し、音符や休符を表します。

  • 音符の場合
<note default-x="65.33" default-y="-90.00">
  <pitch>
    <step>D</step>
    <octave>4</octave>
  </pitch>
  <duration>4</duration>
  <voice>5</voice>
  <type>half</type>
  <stem>down</stem>
  <staff>2</staff>
</note>

<pitch>は音の高さを表します。上記ではD4の音、つまり中央ハのすぐ上のニの音(D, レともいう)を表します。音名について詳しくは↓
音名・階名表記 - Wikipedia

<duration>は音の長さを表します。attributesタグのdivisionsで定義した「音符・休符の最小長さ」が何個分か、という数値を記します。先述のように「四分音符の2分の1の長さ(=八分音符の長さ=1小節の6分の1の長さ)」を最小長さと指定していたので、durationが4というのは二分音符の長さです。

#や♭などの臨時記号がつく場合、<type><stem>の間に以下のような行が挿入されます。

<accidental>sharp</accidental>
  • 休符の場合
<note>
  <rest/>
  <duration>2</duration>
  <voice>5</voice>
  <type>quarter</type>
  <staff>2</staff>
</note>

休符の場合、<note>の中に<pitch>ではなく<rest>を記します。
↑ではいきなり<rest/>となっていますが、<rest> 内容 </rest>となることもあります。
音符同様、durationで長さを指定できます。

3. <backup>:時間の巻き戻し

measure(小節)タグの中に存在し、「時間の巻き戻し」を行います。
例えば以下ではduration(音符・休符と同じ)が6の巻き戻しを行います。

<backup>
  <duration>6</duration>
</backup>

で、巻き戻しとは何か? これは少しややこしいので注意が必要です。

基本的に、MusicXMLでは音符や休符が配置されるごとにその長さ分の時間(durationで指定)が進んだものと考えるようです。
和音の場合は各構成音をバラバラの<note>で表記するのですが、複数の音が同時に鳴って欲しいので、2つ目以降の構成音では<chord/>という行を<note>の行と<pitch>の行の間に挟むことで「時間を止める」ことを行っています。
では「時間の巻き戻し」はどう使うか? 例えば第n小節で右手で弾く音を順番に記述した後、第n小節の先頭に戻って左手の音を記述したいときがあると思います。普通に左手の音を続けて記述したのでは第n+1小節に進んでしまうので、このようなときに1小節分の長さを「巻き戻し」して第n小節の先頭に戻るというわけです。

Pythonプログラムで扱う方法

おおまかな流れ

最初に<attributes>タグから調・拍子・音符の最小長さを取り出しておき、<note>タグを順番に辿ることによって音符や休符の配列を取得します。
開始からの時間を表す変数を用意し、<backup>タグの項で述べたように、

  • 音符や休符ごとに時間を進め、
  • <chord/>とあるところでは時間を止め、
  • <backup>では時間を巻き戻す

といったところがポイントです。

ここでは、各音符について音の高さ・開始時間をセットにした配列を取得し、時系列グラフで可視化してみます。

使う道具

PythonXMLファイルを手軽に解析するために、BeautifulSoupを用います。

$ sudo pip install beautifulsoup4

使い方は以下を参照してください。
PythonとBeautiful Soupでスクレイピング - Qiita

グラフの描画にはseabornを使います。その過程でデータ整形用にpandasを用いました。

$ sudo pip install seaborn
$ sudo pip install matplotlib
$ sudo pip install pandas

pythonで美しいグラフ描画 -seabornを使えばデータ分析と可視化が捗る その1 - Qiita

ソースコード

要点

まずは必要なものをインポートします。

from bs4 import BeautifulSoup
import matplotlib.pyplot as plt
import seaborn
import pandas as pd

MusicXMLファイルを読み込み、BeautifulSoupで扱えるようにします。

xml_name = "lg-203466147999847691.xml"# Your MusicXML file
soup = BeautifulSoup(open(xml_name,'r').read(), "lxml")

読み込んだデータから楽譜データを取り出す関数です。cur_time変数で時間を管理していることに注意!

def extract_music(soup):
    """ Extract music data from xml file. """
    pitch_list = []
    cur_time = 0 # Current time
    tmp_duration = 0
    # parse
    for m in soup.find_all("measure"):
        for nb in m.find_all({"note", "backup"}):
            if nb.name == "backup": # 巻き戻し
                cur_time -= int(nb.duration.string)
            if nb.name == "note":
                if not nb.chord: # 和音でなければ
                    cur_time += tmp_duration
                if nb.pitch: # 音符
                    pitch_list.append([cur_time,
                                       nb.pitch.step.string, 
                                       nb.pitch.octave.string])
                if nb.rest: # 休符
                    pass
                if nb.duration: # 装飾音はdurationないので飛ばす
                    tmp_duration= int(nb.duration.string)
    return pitch_list

得られた配列には各音符の高さが[u'G', u'5']のような形で収められているので、数字に直してグラフに表示しやすくします。

def height(pitch):
    """ Calculate absolute height of given pitch. """
    # pitch example: [u'G', u'5']
    cde_list = ["c","d","e","f","g","a","b"]
    h =  int(pitch[1])*7
    h += cde_list.index(pitch[0].lower())
    return h

グラフを描画する関数。

def show_graph(data):
    """ Show time series graph of given data. """
    height_list = sorted([[p[0], height(p[1:])] for p in data],
                         key=lambda x: x[0])
    df = pd.DataFrame(height_list)
    df.columns = ["time","height"]
    seaborn.jointplot('time', 'height', data=df)
    plt.show()

あとは実行!

music_data = extract_music(soup)
show_graph(music_data)

グラフが表示されます。横軸が時間、縦軸が音の高さです。音の長さは無視して点としてプロットしています。
f:id:roomba:20160203145707p:plain
後述する完全版では以下のように拍子・division・調がprintされます。

4分の3拍子
division: 2
G major

完全版

以下に公開しています。


今後の活用

有名な音楽を様々な方法で可視化したり、楽曲の構造や特徴を解析したり、それをもとに自動の音楽生成を行うために使っていきたいと思っています。
活用したらこのブログで紹介しようと考えているので、購読していない方はぜひ購読を。

*1:英語で検索するのがおすすめ

*2:ライセンスに注意