Pythonでハノンっぽい楽譜を作ったお話

#はじめに
みなさん、ハノンピアノ教本をご存知ですか？
ピアノ経験者の方は嫌な思い出がある方も多いかもしれません。
ハノンピアノ教本は、ピアノを弾く上でとても基礎的なことが学べる教則本です。
百聞は一見に如かずなので、ぜひ[演奏されている動画](https://www.youtube.com/watch?v=051Zm3SH2dI)や[楽譜](https://ameblo.jp/manana888/entry-11753162091.html)を見てみてください。
今回は、ハノンピアノ教本のなかでも、1〜20番のような曲を自動でつくることを目指しました。

#使った言語やツール
使った言語は、**Python**です。
また、MITで開発されたPythonの音楽情報処理ライブラリである**music21**を使用しました。
実は、music21自体が楽譜を作成するわけではなく、MuseScoreやLilypondで楽譜は作成されます。
（ただし、実際に自分がMuseScoreやLilypondを触るわけではないので、その辺の知識はなくて大丈夫です。）
music21については、[このブログ](https://ameblo.jp/delphinus1024/entry-12365205195.html)に大変お世話になりました。

#ハノンっぽさって？
ハノンっぽい楽譜を作るためには、ハノンっぽさについて考えなければなりません。
以下の条件をハノンっぽさと決めました。
音楽をやってない人にはわかりにくい内容かもしれないので、適当に自分で楽譜を見ながらルールを決めたんだな〜くらいに思ってもらえればいい感じです。

1. 最初の音はド。
2. 1小節目でやったことを2〜14小節目では繰り返す(上行)。繰り返す際に、前の小節より１音あげる。
3. 右手と左手はオクターブ違いのユニゾン。
4. 同じ音が連続しない。
5. 1つの小節の音は、８度以内におさめる。
6. 15〜28小節目は下行。
7. 15小節目の最初の音はソから始まる。上行のときと動きが対照的になるようにする。
8. 29小節目はドを伸ばして終わる。

これらの条件を守る楽譜をランダムで作成するように実装しました。
結果として、[このツイート](https://twitter.com/bamboooo_music/status/1295705347076681728?s=19)のような楽譜ができました！

#ソースコード
<details><summary>ソースコード</summary><div>

```python:Hanon.py
# -*- coding: utf-8 -*-
from music21 import *
import numpy
import matplotlib
import scipy
import random



#乱数生成
random_num = [-1 for _ in range(8)]
random_num[0] = 0
for i in range(1,8):
    random_num[i] = random.randint(1, 7)
    if i >= 1: #前の音符と同じ音符がかぶらないようにする
        while True:
            if random_num[i] != random_num[i-1]:
                break
            else:
                random_num[i] = random.randint(1, 7)



#楽譜をかくよ

##なんか最初のおまじない
stream_right = stream.Part()
stream_left = stream.Part()

inst1 = instrument.Instrument()
inst2 = instrument.Instrument()

stream_right.append(inst1)
stream_left.append(inst2)

tc = clef.TrebleClef() #ト音記号
bc = clef.BassClef() #ヘ音記号

stream_right.append(tc)
stream_left.append(bc)

sounds = ["C1", "D1", "E1", "F1", "G1", "A1", "B1", "C2", "D2", "E2", "F2", "G2", "A2", "B2", "C3", "D3", "E3", "F3", "G3", "A3", "B3", "C4", "D4", "E4", "F4", "G4", "A4", "B4", "C5", "D5", "E5", "F5", "G5", "A5", "B5", "C6"]



##右手
for i in range(14): #のぼり
    ###1小節目
    meas = stream.Measure()
    for j in range(8):
        n_right_up = note.Note(sounds[random_num[j] + 14 + i], quarterLength = 0.25)
        meas.append(n_right_up)
    stream_right.append(meas)

for i in range(14): #くだり
    ###1小節目
    meas = stream.Measure()
    x = 18
    for j in range(8):
        if i == 0:
            n_right_down = note.Note(sounds[x + 14 - i], quarterLength = 0.25)
        else:
            n_right_down = note.Note(sounds[x - random_num[j] + 14 - i], quarterLength = 0.25)
        meas.append(n_right_down)
    stream_right.append(meas)

###最後の小節
meas = stream.Measure()
n = note.Note("C3", quarterLength = 2)
meas.append(n)
stream_right.append(meas)


##左手
for i in range(14): #のぼり
    ###1小節目
    meas = stream.Measure()
    for j in range(8):
        n_left_up = note.Note(sounds[random_num[j] + 7 + i], quarterLength = 0.25)
        meas.append(n_left_up)
    stream_left.append(meas)

for i in range(14): #くだり
    ###1小節目
    meas = stream.Measure()
    x = 18
    for j in range(8):
        if i == 0:
            n_left_down = note.Note(sounds[x + 7 - i], quarterLength = 0.25)
        else:
            n_left_down = note.Note(sounds[x - random_num[j] + 7 - i], quarterLength = 0.25)
        meas.append(n_left_down)
    stream_left.append(meas)

###最後の小節
meas = stream.Measure()
n = note.Note("C2", quarterLength = 2)
meas.append(n)
stream_left.append(meas)


##最後のおまじない
s = stream.Score()
s.append(stream_right)
s.append(stream_left)
s.show('musicxml')

```
</div></details>

[GitHub](https://github.com/wanwan0622/Hanon)でも見れます。
ここからは、ソースコードを区切って説明していきます。
ただ、わたしはmusic21を使い始めたばかりで、よくわからず他の方の真似をして書いている部分も多いので、間違っていたらご指摘お願いします。

##ライブラリたち
```python:
# -*- coding: utf-8 -*-
from music21 import *
import numpy
import matplotlib
import scipy
import random
```
random以外は、music21を使うために必要らしいので、とりあえずインポートしときました。
乱数を作りたいので、randomをインポートしておきます。

##乱数を作る
```python:
#乱数生成
random_num = [-1 for _ in range(8)]
random_num[0] = 0
for i in range(1,8):
    random_num[i] = random.randint(1, 7)
    if i >= 1: #前の音符と同じ音符がかぶらないようにする
        while True:
            if random_num[i] != random_num[i-1]:
                break
            else:
                random_num[i] = random.randint(1, 7)
```
1小節目の2〜8音目のみをランダムにすれば、あとはそれを転調していくだけなので、乱数は7つ用意しました。
レ〜シと数字を対応させたかったので、乱数は1〜8の範囲で作りました。
同じ音が連続しないようにするために、while文を回しています。

##楽譜を書くための下準備
```python:
##なんか最初のおまじない
stream_right = stream.Part()
stream_left = stream.Part()

inst1 = instrument.Instrument()
inst2 = instrument.Instrument()

stream_right.append(inst1)
stream_left.append(inst2)

tc = clef.TrebleClef() #ト音記号
bc = clef.BassClef() #ヘ音記号

stream_right.append(tc)
stream_left.append(bc)

otos = ["C1", "D1", "E1", "F1", "G1", "A1", "B1", "C2", "D2", "E2", "F2", "G2", "A2", "B2", "C3", "D3", "E3", "F3", "G3", "A3", "B3", "C4", "D4", "E4", "F4", "G4", "A4", "B4", "C5", "D5", "E5", "F5", "G5", "A5", "B5", "C6"]
```
まず、右手と左手の2つのパートがあるので、パートを2つ用意しておきます。
そして、それぞれのパートにト音記号とヘ音記号をつけます。
最後に、これから使う音を配列で用意しておいてあげます。

##楽譜を書く！メインパート！！
```python:
##右手
for i in range(14): #のぼり
    ###1小節目
    meas = stream.Measure()
    for j in range(8):
        n_right_up = note.Note(sounds[random_num[j] + 14 + i], quarterLength = 0.25)
        meas.append(n_right_up)
    stream_right.append(meas)

for i in range(14): #くだり
    ###1小節目
    meas = stream.Measure()
    x = 18
    for j in range(8):
        if i == 0:
            n_right_down = note.Note(sounds[x + 14 - i], quarterLength = 0.25)
        else:
            n_right_down = note.Note(sounds[x - random_num[j] + 14 - i], quarterLength = 0.25)
        meas.append(n_right_down)
    stream_right.append(meas)

###最後の小節
meas = stream.Measure()
n = note.Note("C3", quarterLength = 2)
meas.append(n)
stream_right.append(meas)
```
まず、1小節目を作成します。
このとき、for文を使うと、なぜか1小節に音が1つしか入らなかったので、仕方なくべた書きしました。
note.Note(音の高さ、音の長さ)というふうに書きます。
音の長さは四分音符を1として、分数や小数で表します。
1小節目が完成したら、それを転調しながら14回繰り返すと上行の完成です！
下行も同じように書きます。
そして、最後の小節のドの伸ばしを忘れずに追加したら完成です！！
左手はオクターブ低いだけなので省略しました。

##MuseScoreで表示させる
```python:
##最後のおまじない
s = stream.Score()
s.append(stream_right)
s.append(stream_left)
s.show('musicxml')
```
最後に、今まで書いたものをMuseScoreで表示させます。
最後の行を書き換えることで、Lilypondで表示させることもできます。

#最後に
もっとハノンっぽさを追求するために、機械学習とかをやってみるといいかも〜って言われたので、やってみたい気持ちがあります(なにも知らないので、0からお勉強しないと…)。
難易度を5段階くらいで調整できるようにもしてみたいです。
また、music21は楽曲分析とかにも使えるらしくて、何調かを調べてくれるなどできるらしい(すごい)ので、そういうのでも遊んでみたいな〜と思いました。



(2020/08/20編集)
ソースコードをきれいに書けたので、書き換えました。