前回、MIDI音楽を作成、再生するクラスを作りました。前回まではソースコードのなかに音楽データ(音符の順番と音の時間)を書き込んでいましたが、ファイルで定義した音楽を再生できるように、ファイルを読み込んで音楽を再生するGUIプログラムを作成したので紹介しようと思います。
実際に動かしたところはこちら
ビデオ内容の説明
- Fileボタンを押して音楽を定義しているエクセルファイルを読み込みます。
- Playボタンを押すと演奏が始まります。
- speedを2.0にすると倍速で演奏します。
- さらにspeedを3.0にして、repeatを2にすると3倍速で2回再生されます。
- ※ Stopボタンを押せば、演奏の途中でも再生が止まります。
読み込んでいるファイルはこちら

読み込むエクセルファイルは4列で音の情報を指定します。音符を指定するnote列、オクターブを指定するoctave列、シャープの有無を指定するsharp列、音の持続時間を指定するtime列の4列です。note列は前回同様、「ドレミファソラシ」および「間」のいずれかを指定します。octave列では通常(?)のドレミファソラシが0、そこから1オクターブ高い場合は1、1オクターブ低い場合は-1というように、±整数値でオクターブを指定します。sharp列は#ありが1、ない場合は0というように0/1で指定します。timeは音を鳴らす(もしくは間を取る)時間で、単位は秒です。
「カエルのうた」のファイルと「ドレミのうた」のファイルを作成しました。以下からダウンロードできます。
ソースコードはこちら
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
import tkinter as tk import tkinter.filedialog as fd from tkinter import * from tkinter import ttk import time import pygame.midi import threading import pandas as pd class myMIDI(threading.Thread): def __init__(self): super(myMIDI, self).__init__() pygame.midi.init() self.outp = pygame.midi.Output(0) self.outp.set_instrument(0) self.NOTES = dict(ド=60, レ=62, ミ=64, ファ=65, ソ=67, ラ=69, シ=71, 間=-1) self.vol = 100 # velocity(強さ) self.pace = 1 self.sound_type = 1 self.daemon = True # Allow main to exit even if still running. self.paused = True # Start out paused. self.state = threading.Condition() def __del__(self): pygame.midi.quit() def tone(self, note, oct, sharp, tmpo): self.outp.note_on(note+oct*12+sharp*1, self.vol) time.sleep(tmpo / self.pace) self.outp.note_off(note+oct*12+sharp*1, self.vol) def pause(self): with self.state: self.paused = True # Block self. def resume(self): with self.state: self.paused = False self.state.notify() # Unblock self if waiting. def run(self): self.resume() while True: with self.state: if self.paused: self.state.wait() # Block execution until notified. if self.paused == True: continue def play_sound(self, doremi, octave, sharp, tempo): for d, oct, s, t in zip(doremi, octave, sharp, tempo): if d == "間": time.sleep(t / self.pace) else: self.tone(self.NOTES[d], oct, s, t) if self.paused == True: return def set_pace(self, p): try: if 0.1 <= p and p <= 5: self.pace = p except: pass midi = myMIDI(); midi.start() root = tk.Tk() doremi = [] octave = [] sharp = [] tempo = [] global b_thread_stop b_thread_stop = False def ask_input_filename(msg = None, types = [('', '*.*')]): rt = tk.Tk() rt.withdraw() filename = fd.askopenfilename(title = msg, filetypes = types) rt.destroy() return filename def read_file(): midi.pause() filename = ask_input_filename("ファイルを選んでください。", types=[('', '*.xlsx')]) if filename == "": return doremi.clear() octave.clear() sharp.clear() tempo.clear() try: wb = pd.read_excel(filename) for i in range(len(wb)): try: d = wb.iloc[i][0] o = wb.iloc[i][1] s = wb.iloc[i][2] t = wb.iloc[i][3] if not d in ['ド', 'レ', 'ミ', 'ファ', 'ソ', 'ラ', 'シ', '間']: continue if int(o) != 0 and int(o) != 1 and int(o) != -1: continue if int(s) != 0 and int(s) != 1: continue if float(t) < 0: continue doremi.append(d) octave.append(o) sharp.append(s) tempo.append(t) except: pass button2['state'] = tk.NORMAL except: pass def play_midi_thread(repeat_n): global b_thread_stop midi.resume() for i in range(repeat_n): midi.play_sound(doremi, octave, sharp, tempo) if b_thread_stop == True: break midi.pause() def play_midi(): global b_thread_stop midi.pause() # 繰り返し回数 val = val_combo.get() repeat_n = 1 if val == '2': repeat_n = 2 elif val == '3': repeat_n = 3 elif val == '4': repeat_n = 4 elif val == '5': repeat_n = 5 # スピード val = val_scale.get() #print(val) midi.set_pace(val) b_thread_stop = False th = threading.Thread(target=play_midi_thread, args=(repeat_n, )) th.start() def stop_midi(): global b_thread_stop b_thread_stop = True midi.pause() button1 = tk.Button(root, text="File...", width=10, command=read_file) button1.grid(row=0, column=0, padx=5, pady=5, sticky=(N, E, S, W)) button2 = tk.Button(root, text="Play", width=10, command=play_midi) button2.grid(row=1, column=0, padx=5, pady=5, sticky=(N, E, S, W)) button2['state'] = tk.DISABLED button3 = tk.Button(root, text="Stop", width=10, command=stop_midi) button3.grid(row=2, column=0, padx=5, pady=5, sticky=(N, E, S, W)) # 倍速を指定するスケール label1 = ttk.Label(root, text="speed") label1.grid(row=0, column=1, padx=5, pady=5, sticky=(N, E, S, W)) val_scale = tk.DoubleVar() val_scale.set(1) sc = tk.Scale(root, variable=val_scale, orient=HORIZONTAL, from_=0.5, to=3.0, resolution=0.5, tickinterval=0.5) # 0.5(1/2倍速)~3倍速 sc.grid(row=0, column=2, padx=5, pady=5, sticky=(N, E, S, W)) # 繰り返し回数を指定するコンボボックス label2 = ttk.Label(root, text="repeat") label2.grid(row=1, column=1, padx=5, pady=5, sticky=(N, E, S, W)) module = ('1', '2', '3', '4', '5') val_combo = tk.StringVar() val_combo.set(1) combobox = ttk.Combobox(root, textvariable=val_combo, values=module, style="office.TCombobox", height=5) combobox.grid(row=1, column=2, padx=5, pady=5, sticky=(N, E, S, W)) root.mainloop() |
ソースコードの説明
前回のmyMIDIクラスを修正、拡張しています。前回のmyMIDIクラスではクラス内でMIDI音楽の情報を生成・保持していましたが、今回はplay_soundメソッドに音階(doremi)、オクターブ(octave)、シャープ(sharp)、時間(tempo)の情報を渡すことで、MIDI音楽を演奏させています。
Fileボタンを押すとread_file関数が呼び出され、エクセルファイルを読み込んでいます。
Playボタンを押すと、play_midi関数が呼び出され、その中で繰り返し回数と演奏速度を指定して、play_midi_thread関数でMIDIの演奏を開始させています。
いかがでしょうか。これなら誰でも作曲できるのではないでしょうか。エクセルファイルに曲を作ったら、共有、コメントいただけると幸いです。
- 投稿タグ
- プログラミング