Python で Just the Two of Us 進行 【コード進行】

Python

Python を使って、Just the Two of Us などの任意のコード進行を演奏してみます。
簡単な音楽を作曲して再生することもできると思います。

以下の環境で動作確認をしています。
環境:
・ Windows パソコン (Anaconda 設定済み)
・ Python 3、pygame のインストール済み
★ 再生にあたり、Windows パソコン内の MIDI 音源を使用します。

背景 ~ コード進行を自力で生成&理解する!

YouTube でストリートピアノをよく聴きます。Just the Two of Us などの洋楽も昔から聴きます。
また以前、Python を使って、ドレミファソラシドを鳴らすプログラムについてまとめていました。

たまたまですが、最近、音律や音階、コード進行に関する本を読みました。実際に自分で、コードを鳴らしたくなってきます。
ということで、音階やコード進行(和音)を再生する Python のスクリプトをまとめておくことにします。

テキストファイルに音符の長さ、音階・コードを書いておくと、そのデータを読み込んで音楽を再生します。
楽器を持っていなくても、パソコン1台さえあれば、各音階、和音で演奏できます。
簡単な作曲もできるのではないかと思います。

なお、私が使っているパソコンがたまたま Windows であり、Windows パソコン内の MIDI の機能を使っています。プログラムは Windows などの環境依存になるかと思いますが、ご理解ください。

設定手順

① 以下を参考に、Windows パソコンにフォルダ(例:chord_player1)を作成してください。
例: c:\user\chord_player1
② ①のフォルダに、さらに、”chord1″ という名前でフォルダを作成してください。
例: c:\user\chord_player1\chord1
※ このフォルダの中に音階を記載したテキスト(以下の「再生用のサンプル」を参照)を入れます。
③ ①のフォルダにもどり、chord_player1.py 等の名前でテキストを作成し、以下のサンプルスクリプトをコピー&ペーストして保存してください。

使い方

④ 音階、コードを記載したテキストファイルを、上記の②の “chord1” フォルダに入れておきます。
例:01_just_the_two_of_us_chord1.txt
※ 最初は、下記の再生用のサンプル(5つ)をテキストファイルで作成し、再生してみてください。
※ 音階、コードの書き方は、同サンプルと以下の記載を参照してください。
⑤ コマンドプロンプト(または、Anaconda Prompt)を起動し、以下を参考に、③のスクリプトを実行します。
例: python C:\user\chord_player1\chord_player1.py
→ アプリが起動します。

※ 以後、大きな音が出る可能性があるため、事前に音量を(小さめに)調整しておきます。
⑥ アプリ上で、ファイル名の部分をクリックすると、そのファイル(コード、音楽)を再生します。

※ [play] ボタンをクリックしても、再生します。
※ もし、④のフォルダにテキストファイルを追加した場合は、アプリを再度、起動しなおしてください。
※ テキストを書き換えた場合は、アプリ上でファイル名の部分を再度クリックすることで、新たにファイルを読み込んで再生できます。容易に再読み込みができますので、ちょっとした和音の違い、変更の違いを聞き比べることができます。

音階、コードの書き方

基本ルール

・ ②の chord1 フォルダ内に、再生用のテキストファイルを追加し、音符の長さや音階などを半角カンマ区切りで記入していきます。
・ テキストファイルの1行が、1つの音(音階/コードの長さ)、または、コマンドに対応します。
たとえば、1拍(デフォルトで1秒間)ずつの時間間隔で、ピアノのドレミファソラシド(C4、D4、E4、F4、G4、A4、B4、C5)の音階を順に鳴らす場合は、以下を記載します。

1.0, C4
1.0, D4
1.0, E4
1.0, F4
1.0, G4
1.0, A4
1.0, B4
1.0, C5

※ C4 などの表記は、国際的表記に従います。A4 が、440 Hz のラに対応します。C4 を1オクターブ下げるには C3、1オクターブ上げるには C5 にします。
※ シャープ、フラットとする場合は、末尾に、”s”、”f” をつけます。
たとえば、”C4s” は、C4 ♯(ドのシャープ)に対応します。
※ 上の例で、1.0 の部分を 0.5 に変更すると、0.5拍の速度で各音階を鳴らします。
・ 休符を入れる場合は、時間部分のみを記載します。
例: 1.0
・ 複数の音名を1行に並べて書くことで、複数の音を同時に鳴らすことができます。

1.0, C4, E4, G4
※ 同時に、ド、ミ、ソ(C4, E4, G4 の和音)を鳴らす例です。
この場合、以下の記載も可能です。Cメジャーの意味です。
1.0, C4_M

・ テキストファイル内で、# から始まる行や空行がある場合は、読み飛ばします。冒頭を # とすることで、コメントを入れることができます。
・ set_ … とした行はコマンド行です。たとえば、set_BPM1 で、曲のテンポを定義します。
“set_BPM1, 60″ とした行を追加すると、以降の音楽を、1分間に60拍のテンポで再生します。”set_BPM1, 120” とすると、120拍/分のテンポで再生します。(BPM = beats per minute)
曲の途中でも、テンポの変更が可能です。

コードの記載方法

・ コード(和音)を記載する場合は、C4_m (または、C_m)のように、必ず、アンダーバー “_” を途中に入れて記載します。
これは、単一の音(音名)と、コードを区別するためです。
たとえば、C の単一の音と、C メジャーは慣習上、”C” のみで書かれることが多いですが、プログラム上判別ができなくなるため、”_” の有無でコードか否かを判別します。
・ コードの記載で、アンダーバー “_” の左側が、基準となる音(ルート)、右側がコードの種類(3和音、4和音等)に対応します。
・ 3和音のメジャーの場合、C_M のように、”_M” を明記します。
“C”、”C_” などと書くことにすると、紛らわしいためです。

・ コードの種類は、現在のところ、以下(3和音および4和音)を定義しています。 各行の左側の記載をすることで、各コードを再生できます。

3和音
C4_M  Cメジャー(C)
C4_m  Cマイナー (Cm、C-)

4和音
C4_7  Cセブンス(C7)
C4_M7 Cメジャーセブンス (C△7、Cmaj7、CM7)
C4_m7 Cマイナーセブンス(Cm7)
C4_m7f5 Cマイナーセブンスフラットファイブ(Cm7(♭5))

※ ここで、左側の “C4” の部分は、基準となる音階(ルート)であって、一例です。
他の “D4″、”E4″、… などに書き換えて使用できます。
また、コードの表記には、音楽の各分野によって流儀があるようですので、参考のためによく使われる表記をカッコ内に示しておきます。カッコ内の表記はプログラム内では使えません。

よく使われるコード進行

代表的なコード進行の一例についてまとめておくことにします。
下記のようなコード進行が得られたとき、前述の書き方に従ってテキストファイルを作成すると、実際に、そのコード進行を Python のプログラムで演奏することができます。

Just the Two of Us 進行

F△7 | E7 | Am7 | Gm7 C7 |   ★1
IV△7 | III7 | VIm7 | Vm7  I7 | ★2
※ 下記の “01_just_the_two_of_us_chord1.txt” が記載例です。
※ このコード進行の派生バージョンもいくつかあります。
一例を “05_just_the_two_of_us_chord_simplified1.txt” に入れてあります。
※ 椎名林檎の「丸の内サディスティック」で有名です。おしゃれコードの定番といわれており、J-POP を席巻しています。

カノン進行

D A | Bm F#m | G D | G A |
I V | VI III | IV I | IV V |
※ ヨハン・パッヘルベルのカノン(ニ長調)
※ 下記の “02_canon_progression_chord1.txt” が記載例です。

Japanese Chord Progression (王道進行)

F | G | Em | Am |
IV | V | IIIm | VIm |
※ 下記の “03_japanese_chord_progression1.txt” が記載例です。

参考:I, II, III, IV, V, VI, VII の数値の当てはめ方

一例として、★2のコード進行で、I ~ VII の7つの数値を音階の C ~ B にそれぞれ対応させると、★1が得られます。これは、C がキー(I)の場合に対応しています。

D をキーとする場合は、I ~ VII の7つの数値を、音階の D ~ C に対応させます。
同様に、E、F、G、…、B をキーとする場合は、I ~ VII を E ~ D、F ~ E、G ~ F、… にそれぞれ対応させます。
これにより、★2のような汎用なコード進行が与えられたとき、任意のキー(音階)/すべてのパターンでコード進行を作ることが可能となります。

サンプルスクリプトの説明

・ サンプルスクリプトで、MIDI を使った音源の鳴らし方は、下記リンクの「ドレミファソラシドを鳴らすサンプル」とほぼ同じです。
・ GUI を使うため、tkinter を使っています。
・ 国際式の音階(例:C4, D4)を MIDI の note number (例:60, 62)に変換するため、関数 convert_CDEFGABC1 を定義しています。
・ MIDI の note number は、1つ値が増えると、半音上がること(周波数で\(2^{1/12}\)倍)に対応しています。
(半音)×12個分が1オクターブ(周波数で2倍)に対応します。式でいえば、以下となります。
$$ (半音) = 2^{1/12} $$
$$ (1オクターブ) = (半音)^{12} = 2 $$
・ デフォルトでのパラメータを def set_init1() としたところで定義しています。
たとえば、MIDI の楽器の種類は ins1 = 0 (ピアノ)としているところで定義しています。値を変えると、楽器を変更できます。楽器を変更する場合は、下記リンクの「キーボードピアノ【本格49鍵!】」を参照してみてください。
・ 音程を1オクターブ上下する場合は、set_init1() 関数内の oct1 = 0*12 としているところで、oct1 = 1*12、oct1 = -1*12 などに修正してください。(半音)*12 が1オクターブに対応しているため、このような記載としています。
・ 転調させる場合は、oct1 = 1、2、… などに書き換えることで、半音1、2、…個分、強制的に転調させることが可能です。
・ 和音については、基準となる音(ルート)に、半音のいくつ分の音を加えるか、という考え方で、3和音、4和音のデータを生成しています。
・ ルートとなる音(note number)を n1 としたとき、メジャー、マイナーなどのコードの定義とプログラム内での記載を書き出しておきます。この記載を拡張していくことで、自由にコードを追加していくことが可能です。

コードの定義
3和音のメジャーは、ルート(n1)に対し、4×(半音)と、7×(半音)を加えたものです。したがって、プログラム内での和音の表記は、n1, n1+4, n1+7 となります。
3和音のマイナーは、ルート(n1)に対し、3×(半音)と、7×(半音)を加えたものです。したがって、n1, n1+3, n1+7 となります。
「セブンス」(7の数値がついているもの)は、すべて4和音で定義されています。原則、3和音に1音加えたもの、となっています。
メジャーセブンスは、3和音のメジャーに、11×(半音)を加えたものです。したがって、n1, n1+4, n1+7, n1+11 となります。
セブンスは、3和音のメジャーに、10×(半音)を加えたものです。したがって、n1, n1+4, n1+7, n1+10 となります。
マイナーセブンスは、3和音のマイナーに、10×(半音)を加えたものです。したがって、n1, n1+3, n1+7, n1+10 となります。

まとめ

Python で、任意のコード進行を再生するスクリプトについてまとめました。
パソコン1台さえあれば、コード進行の再生、作曲、作曲した音楽の再生などが可能になります。

当初、興味が沸いたので、「Just the Two of Us 」進行(椎名林檎の「丸の内サディスティック」進行)のコードをプログラミングで再生して確認したいと思いました。
プログラムを作ってみると、コード進行のファイルの追加は容易です。数分もかかりません。
そこで、パッヘルベルのカノン、Japanese Chord Progression(王道進行)など、オーソドックスなコード進行も加えました。
自力でコードを書いて再生してみると、和音、不協和音、3和音、4和音を聴き比べることができ、和音やコード進行についてよく理解できます。
楽器が使えなくても、ソフトウェアを使うことで、コード進行が自由自在に扱えるようになりました。
もっと昔からやっておけばよかった。。パソコン1台さえあれば、特殊な機材やソフトウェア不要で、音階やコード進行が自由自在に扱えることになります。ネット検索をする限り、Python などのプログラミング言語を楽器替わりにして、名曲、ヒット曲などのコード進行を分析している人がほとんどいない?ような気がします。

なお、MIDI 音源を利用した Python のプログラムについては、以下の関連リンクなどでもまとめています。
興味のある方は参照してみてください。

関連リンク
・ ドレミファソラシドを鳴らすサンプル【Python】
・ キーボードピアノ 【本格49鍵!】
・ 指定範囲を繰り返し再生する音楽プレーヤー【Python】
・ フォルダビューアの作り方 【Python, tkinter】
・ Python tkinter サンプル 【最短3行&最速3ステップ】
・ Anaconda 環境で Pygame をインストールする手順【Python】
・ Pygame をインストールする手順

外部リンク
・ Grover Washington Jr. feat. Bill Withers – Just The Two of Us (YouTube)

再生用のサンプル(テキストで保存)

サンプル1: “01_just_the_two_of_us_chord1.txt” Just the Two of Us のコード進行

set_BPM1, 80 

1.0, F4_M7
1.0, F4_M7
1.0, E4_7
1.0, E4_7
1.0, A4_m7
1.0, A4_m7
1.0, G4_m7
1.0, C4_7

1.0, F4_M7
1.0, F4_M7
1.0, E4_7
1.0, E4_7
1.0, A4_m7
1.0, A4_m7
1.0, G4_m7
1.0, C4_7

サンプル2: “02_canon_progression_chord1.txt” パッヘルベルのカノンのコード進行


set_BPM1, 80 
1, D4_M
1, D4_M
1, A4_M
1, A4_M
1, B4_m
1, B4_m
1, F4s_m
1, F4s_m
1, G4_M
1, G4_M
1, D4_M
1, D4_M
1, G4_M
1, G4_M
1, A4_M
1, A4_M

1, D4_M
1, D4_M
1, A4_M
1, A4_M
1, B4_m
1, B4_m
1, F4s_m
1, F4s_m
1, G4_M
1, G4_M
1, D4_M
1, D4_M
1, G4_M
1, G4_M
1, A4_M
1, A4_M

サンプル3: “03_japanese_chord_progression1.txt” 王道進行 Japanese Chord Progression のコード進行


set_BPM1, 80 

1, F4_M 
1, F4_M 
1, G4_M 
1, G4_M 
1, E4_m 
1, E4_m 
1, A4_m 
1, A4_m 

1, F4_M 
1, F4_M 
1, G4_M 
1, G4_M 
1, E4_m 
1, E4_m 
1, A4_m 
1, A4_m 


サンプル4: “04_sample_CDEFGABC1.txt” ドレミファソラシド等を再生する例


# start 

set_duration1, 0.5 
1.0, C4 
1.0, D4 
1.0, E4
1.0, F4 
1.0, G4
1.0, A4 
1.0, B4
1.0, C5 
1.0

set_duration1, 1.5 
1.0, C4, E4, G4 
1.0, D4, G4, B4 
2.0, C4, E4, G4 

set_duration1, 0.5 
1.0, 60 
1.0, 62 
1.0, 64 
1.0, 65 
1.0, 67 
1.0, 69 
1.0, 71 
1.0, 72 
1.0

set_duration1, 1.5 
1.0, 60, 64, 67 
1.0, 62, 67, 71 
2.0, 60, 64, 67 

サンプル5: “05_just_the_two_of_us_chord_simplified1.txt” Just the Two of Us の簡略化バージョンの例


set_BPM1, 80 

# simplified 1 
1.0, F4_M7
1.0, F4_M7
1.0, E4_7
1.0, E4_7
1.0, A4_m7
1.0, A4_m7
1.0, C4_7
1.0, C4_7
2.0 

# simplified 2 
1.0, F4_M
1.0, F4_M
1.0, E4_M
1.0, E4_M
1.0, A4_m
1.0, A4_m
1.0, C4_M
1.0, C4_M


サンプルスクリプト chord_player1.py



import os 
import glob 
import pygame.midi as pm1 
import time as tm1 
import tkinter as tk1 

def read1( file1 ): 
#   with open( file1, 'r', encoding='utf-8' ) as f1: 
    with open( file1, 'r', encoding='shift_jis' ) as f1: 
        str1 = f1.read()
    return str1 

def get_a1( file1 ): 
    a1 = read1( file1 ).strip().split( "\n" ) 
    a2 = [] 
    for i1 in range( len( a1 ) ): 
        str1 = a1[i1].strip() 
        if len(str1) == 0 or str1[0] == "#": 
            continue 
        else: 
            a2.append( str1.split( "," ) ) 
    return a2 

def convert_CDEFGABC1( str1 ): 
    str2 = str1.strip() 
    if str2[0] == "C": 
        n1 = 60 
    elif str2[0] == "D": 
        n1 = 62 
    elif str2[0] == "E": 
        n1 = 64 
    elif str2[0] == "F": 
        n1 = 65 
    elif str2[0] == "G": 
        n1 = 67 
    elif str2[0] == "A": 
        n1 = 69 
    elif str2[0] == "B": 
        n1 = 71 
    if len(str2) > 1: 
        n1 = n1 + (int( str2[1] )-4)*12 
        if str2[-1] == "s": 
            n1 = n1 + 1 
        elif str2[-1] == "f": 
            n1 = n1 - 1 
    return n1 

def convert1( str1, oct1 ): 
    str2 = str1.strip() 
    n1 = -1 
    a1 = [] 
    if str2.isnumeric():               # MIDI numeric 
        a1 = [ int( str2 )+oct1 ]
    elif "_" in str2:                  # chord 
        a2 = str2.split("_") 
        n1 = convert_CDEFGABC1( a2[0] ) + oct1  # root 
        if a2[1] == "M": 
            a1 = [ n1, n1+4, n1+7 ] 
        elif a2[1] == "m": 
            a1 = [ n1, n1+3, n1+7 ] 
        elif a2[1] == "M7": 
            a1 = [ n1, n1+4, n1+7, n1+11 ] 
        elif a2[1] == "7": 
            a1 = [ n1, n1+4, n1+7, n1+10 ] 
        elif a2[1] == "m7": 
            a1 = [ n1, n1+3, n1+7, n1+10 ] 
        elif a2[1] == "m7f5": 
            a1 = [ n1, n1+3, n1+6, n1+10 ] 
    else: 
        n1 = convert_CDEFGABC1( str2 ) 
        if n1 > 0: 
            n1 = n1 + oct1 
        a1 = [ n1 ] 
    return a1 

def set_init1(): 
    global len1 
    global vol1 
    global oct1 
    global chn1 
    global ins1 
    global busy1 
    len1 = 1.0 
    vol1 = 127 
    oct1 = 0 * 12 
    chn1 = 0 
    ins1 = 0 
    busy1 = 0 

def set_param1( a1 ): 
    global len1 
    if a1[0] == "set_BPM1":            # beats per minute 
        len1 = 60.0 / float( a1[1] ) 
        print( "len1:" + str(len1) ) 
    elif a1[0] == "set_duration1":     # sec 
        len1 = float( a1[1] ) 
        print( "len1:" + str(len1) ) 

def tone1( a1, vol1 ): 
    global len1 
    if "set_" in a1[0]: 
        set_param1( a1 ) 
        print( a1 ) 
    else: 
        a2 = [] 
        for i1 in range( len(a1)-1 ): 
            a2 = a2 + convert1( a1[i1+1], oct1 ) 
        print( [ a1, a2 ] ) 
        for i1 in range( len(a2) ): 
            op1.note_on(  a2[i1], vol1 )
        tm1.sleep( float( a1[0])*len1 ) 
        for i1 in range( len(a2) ): 
            op1.note_off( a2[i1], vol1 ) 

def play0(): 
    global a0 
    global busy1 
    if busy1 < 1: 
        busy1 = 1 
        for a1 in a0: 
            tone1( a1, vol1 ) 
    busy1 = 0 

def play1(): 
    frame1.after( 1000, play0 ) 
    
def click_list1( self ): 
    global file1 
    global list1 
    global n1 
    global a0 
    set_init1() 
    n1 = listbox1.curselection()[0] 
    file1 = list1[n1] 
    a0 = get_a1( file1 ) 
    print( file1 ) 
    frame1.after( 1000, play0 ) 

path1 = os.path.dirname(__file__) + "/chord1/" 
list1 = glob.glob( path1 + "*.txt" ) 
n1 = 0 
file1 = list1[n1] 
a0 = get_a1( file1 ) 
print( a0 ) 

len1 = 1.0 
vol1 = 127 
oct1 = 0 * 12 
chn1 = 0 
ins1 = 0 
busy1 = 0 

set_init1()

pm1.init()
op1 = pm1.Output(0)
op1.set_instrument(ins1, chn1)

frame1 = tk1.Tk()
frame1.title( 'chord player v0.1' ) 
frame1.geometry( "350x270+100+100" ) 

btn1 = tk1.Button( frame1, text="play", command=play1 ) 
btn1.place( x=10, y=10, width=60 ) 

listbox1 = tk1.Listbox( frame1, selectmode='single' )
listbox1.place( x=10, y=50, width=305, height=200 ) 

scrollbar1 = tk1.Scrollbar(frame1, orient='v', command=listbox1.yview)
scrollbar1.place( x=305, y=50, height=200 ) 

listbox1.configure( yscrollcommand=scrollbar1.set ) 

for list2 in sorted( list1 ): 
    list3 = os.path.split( list2 )[1] 
    listbox1.insert('end', list3 ) 

listbox1.bind('<<ListboxSelect>>', click_list1 )

frame1.mainloop()

op1.close()
pm1.quit()


タイトルとURLをコピーしました