Help us understand the problem. What is going on with this article?

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

はじめに

みなさん、ハノンピアノ教本をご存知ですか?
ピアノ経験者の方は嫌な思い出がある方も多いかもしれません。
ハノンピアノ教本は、ピアノを弾く上でとても基礎的なことが学べる教則本です。
百聞は一見に如かずなので、ぜひ演奏されている動画楽譜を見てみてください。
今回は、ハノンピアノ教本のなかでも、1〜20番のような曲を自動でつくることを目指しました。

使った言語やツール

使った言語は、Pythonです。
また、MITで開発されたPythonの音楽情報処理ライブラリであるmusic21を使用しました。
実は、music21自体が楽譜を作成するわけではなく、MuseScoreやLilypondで楽譜は作成されます。
(ただし、実際に自分がMuseScoreやLilypondを触るわけではないので、その辺の知識はなくて大丈夫です。)
music21については、このブログに大変お世話になりました。

ハノンっぽさって?

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

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

これらの条件を守る楽譜をランダムで作成するように実装しました。
結果として、このツイートのような楽譜ができました!

ソースコード

ソースコード
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')

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

ライブラリたち

# -*- coding: utf-8 -*-
from music21 import *
import numpy
import matplotlib
import scipy
import random

random以外は、music21を使うために必要らしいので、とりあえずインポートしときました。
乱数を作りたいので、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)

1小節目の2〜8音目のみをランダムにすれば、あとはそれを転調していくだけなので、乱数は7つ用意しました。
レ〜シと数字を対応させたかったので、乱数は1〜8の範囲で作りました。
同じ音が連続しないようにするために、while文を回しています。

楽譜を書くための下準備

##なんか最初のおまじない
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つ用意しておきます。
そして、それぞれのパートにト音記号とヘ音記号をつけます。
最後に、これから使う音を配列で用意しておいてあげます。

楽譜を書く!メインパート!!

##右手
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で表示させる

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

最後に、今まで書いたものをMuseScoreで表示させます。
最後の行を書き換えることで、Lilypondで表示させることもできます。

最後に

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

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした