以前、私がオリジナルで定義した音情報のフォーマットデータからMIDI音楽を再生するGUIプログラムを作成しました。また前回、MIDIファイルを保存するプログラムを紹介しました。今回はその2つを統合して、MIDIファイルを保存する機能をGUIプログラムに追加したので紹介しようと思います。
ソースコードはこちら
|
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 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 |
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 import mido import time 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 root = tk.Tk() midi = myMIDI(); midi.start() 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 ask_output_filename(msg = None, types = [('', '*.*')]): rt = tk.Tk() rt.withdraw() filename = fd.asksaveasfilename(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 button4['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 == '∞': repeat_n = 999 elif val == '1': repeat_n = 1 elif 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() 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() def save_midi(): midi.pause() filename = ask_output_filename("保存するファイル名を指定してください。", types=[('', '*.mid')]) if filename == "": return # スピード val = val_scale.get() NOTES = dict(ド=60, レ=62, ミ=64, ファ=65, ソ=67, ラ=69, シ=71, 間=-1) mid = mido.MidiFile() track = mido.MidiTrack() mid.tracks.append(track) track.append(mido.MetaMessage('set_tempo', tempo=mido.bpm2tempo(120))) for d, oct, s, t in zip(doremi, octave, sharp, tempo): if d == "間": track.append(mido.Message('note_off', note=0, time=int(t*1000/val))) else: track.append(mido.Message('note_on', note=NOTES[d]+oct*12+s, velocity=100, time=0)) track.append(mido.Message('note_off', note=NOTES[d]+oct*12+s, time=int(t*1000/val))) mid.save(filename) 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)) button4 = tk.Button(root, text="Save", width=10, command=save_midi) button4.grid(row=3, column=0, padx=5, pady=5, sticky=(N, E, S, W)) button4['state'] = tk.DISABLED # 倍速を指定するスケール 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() |
GUIプログラムの使い方

使い方はいたってシンプルです。まずFileボタンを押して音楽を定義しているエクセルファイルを読み込みます。そのあとSaveボタンを押すと保存ダイアログが開き、ファイル名を指定してMIDIファイルを保存できます。
speedを変えると、指定した倍速に合わせて保存されるMIDIの速度も変わります。
※ 再生回数に関しては保存するMIDIファイルに影響しません。つまり、repeatに2以上の値を指定しても、保存される音の再生回数は1回のみです。
ソースコードの説明
以前紹介したGUIプログラムから、新たに追加した部分のソースコードを簡単に説明します。
|
218 219 220 |
button4 = tk.Button(root, text="Save", width=10, command=save_midi) button4.grid(row=3, column=0, padx=5, pady=5, sticky=(N, E, S, W)) button4['state'] = tk.DISABLED |
GUIにSaveボタンを追加し、押されたときにsave_midi関数を実行するように指定しています。
|
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
def save_midi(): midi.pause() filename = ask_output_filename("保存するファイル名を指定してください。", types=[('', '*.mid')]) if filename == "": return # スピード val = val_scale.get() NOTES = dict(ド=60, レ=62, ミ=64, ファ=65, ソ=67, ラ=69, シ=71, 間=-1) mid = mido.MidiFile() track = mido.MidiTrack() mid.tracks.append(track) track.append(mido.MetaMessage('set_tempo', tempo=mido.bpm2tempo(120))) for d, oct, s, t in zip(doremi, octave, sharp, tempo): if d == "間": track.append(mido.Message('note_off', note=0, time=int(t*1000/val))) else: track.append(mido.Message('note_on', note=NOTES[d]+oct*12+s, velocity=100, time=0)) track.append(mido.Message('note_off', note=NOTES[d]+oct*12+s, time=int(t*1000/val))) mid.save(filename) |
save_midi関数では、保存するファイル名を尋ねるダイアログを表示し、ファイル名を取得します。その後は、前回紹介したプログラムとほぼ同じ内容の処理をしています。音の情報はグローバル変数doremi、octave、sharp、tempoに格納されているので、その内容に従って音情報をトラック変数trackに追加しています。
自分でいろいろな音楽を作って、気に入ったものができたらmidiファイルに保存してみてください。
- 投稿タグ
- プログラミング