import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import os
import librosa
import numpy as np
import pretty_midi
from tensorflow import keras
import tensorflow as tf
# モデルの読み込み
try:
model = keras.models.load_model("sound_to_midi_model.h5")
except Exception as e:
print(f"モデルの読み込みに失敗しました: {e}")
model = None
def wav_to_midi(wav_path, output_midi_path):
try:
y, sr = librosa.load(wav_path, sr=None)
duration = librosa.get_duration(y=y, sr=sr)
# 短すぎる音声ファイルの処理
if duration < 1:
raise ValueError("音声ファイルが短すぎます。1秒以上のファイルを選択してください。")
# 特徴量抽出
hop_length = 512
n_fft = 2048
if sr < 22050:
print(f"警告: サンプリングレートが低いため、処理結果が劣化する可能性があります。{sr}")
stft = librosa.stft(y, hop_length=hop_length, n_fft=n_fft)
abs_stft = np.abs(stft)
mag_db = librosa.amplitude_to_db(abs_stft)
# モデル入力用に整形
input_data = np.expand_dims(mag_db.T, axis=0) # (1, time, frequency) にする
input_data = tf.convert_to_tensor(input_data, dtype=tf.float32)
# モデルによる予測
prediction = model.predict(input_data)
# MIDIデータに変換
midi_data = prediction_to_midi(prediction, sr, hop_length, duration)
# MIDIファイルの保存
midi_data.write(output_midi_path)
return True
except Exception as e:
print(f"エラーが発生しました: {e}")
messagebox.showerror("エラー", f"エラーが発生しました: {e}")
return False
def prediction_to_midi(prediction, sr, hop_length, duration):
midi_data = pretty_midi.PrettyMIDI()
piano_program = pretty_midi.instrument_name_to_program('Acoustic Grand Piano')
piano = pretty_midi.Instrument(program=piano_program)
notes = np.argmax(prediction[0], axis=-1)
threshold = 0.5
prev_note = -1
time_per_frame = hop_length / sr
for i, note_index in enumerate(notes):
if note_index > 0: # 0は休符
midi_note = note_index + 20 # 21から始まる
if midi_note > 127 :
midi_note = 127
if midi_note < 1 :
midi_note = 1
start_time = time_per_frame * i
end_time = start_time + time_per_frame
if prev_note == -1 :
prev_note = midi_note
start_note = start_time
elif prev_note != midi_note:
note = pretty_midi.Note(velocity=60, pitch=prev_note, start=start_note, end=start_time)
piano.notes.append(note)
prev_note = midi_note
start_note = start_time
if i == len(notes) -1 :
note = pretty_midi.Note(velocity=60, pitch=midi_note, start=start_note, end=end_time)
piano.notes.append(note)
midi_data.instruments.append(piano)
return midi_data
def select_wav_file():
filepath = filedialog.askopenfilename(
title="WAVファイルを選択してください",
filetypes=[("WAV files", "*.wav"), ("MP3 files", "*.mp3")])
if filepath:
input_path_entry.delete(0, tk.END)
input_path_entry.insert(0, filepath)
def select_output_directory():
dirpath = filedialog.askdirectory(title="MIDIファイルの保存先フォルダを選択してください")
if dirpath:
output_path_entry.delete(0, tk.END)
output_path_entry.insert(0, dirpath)
def convert():
input_path = input_path_entry.get()
output_dir = output_path_entry.get()
if not input_path or not output_dir:
messagebox.showerror("エラー", "入力ファイルと出力フォルダを選択してください。")
return
if not os.path.exists(input_path):
messagebox.showerror("エラー", "入力ファイルが存在しません。")
return
filename = os.path.basename(input_path)
name_without_extension = os.path.splitext(filename)[0]
output_path = os.path.join(output_dir, f"{name_without_extension}.mid")
progress_bar["value"] = 0 # プログレスバーを初期化
window.update_idletasks() # 画面更新
if wav_to_midi(input_path, output_path):
messagebox.showinfo("完了", f"MIDIファイルが{output_path}に保存されました。")
progress_bar["value"] = 100 # プログレスバーを100にする
else:
progress_bar["value"] = 0
window.update_idletasks() # 画面更新
window = tk.Tk()
window.title("Sound to MIDI Converter")
window.geometry("600x300")
# 入力ファイルパス
input_label = tk.Label(window, text="入力ファイル:")
input_label.grid(row=0, column=0, padx=10, pady=10, sticky="w")
input_path_entry = tk.Entry(window, width=50)
input_path_entry.grid(row=0, column=1, padx=10, pady=10)
input_button = tk.Button(window, text="選択", command=select_wav_file)
input_button.grid(row=0, column=2, padx=10, pady=10)
# 出力フォルダパス
output_label = tk.Label(window, text="出力フォルダ:")
output_label.grid(row=1, column=0, padx=10, pady=10, sticky="w")
output_path_entry = tk.Entry(window, width=50)
output_path_entry.grid(row=1, column=1, padx=10, pady=10)
output_button = tk.Button(window, text="選択", command=select_output_directory)
output_button.grid(row=1, column=2, padx=10, pady=10)
# 変換ボタン
convert_button = tk.Button(window, text="変換", command=convert)
convert_button.grid(row=2, column=0, columnspan=3, pady=20)
# プログレスバー
progress_bar = ttk.Progressbar(window, orient=tk.HORIZONTAL, length=300, mode='determinate')
progress_bar.grid(row=3, column=0, columnspan=3, pady=10)
window.mainloop()
高品質なMIDI生成のための考慮点:
- モデルの準備:
- 高品質な変換には、十分なデータで訓練された、音声からMIDIへの変換モデルが必要です。
- このコードでは
sound_to_midi_model.h5
という名前でモデルをロードするようになっていますが、実際には自分で学習させるか、公開されているモデルを使用する必要があります。 - 音符の長さ、ベロシティ、ピッチなどの細かな情報を考慮したモデルを準備してください。
- モデルの出力層は、MIDIノート(ピッチ)、ベロシティ、時間情報などを出力できる形にする必要があります。
- 特徴量抽出:
- 音声の周波数特性を捉えるために、STFT(短時間フーリエ変換)を使用。
- 場合によってはメル周波数ケプストラム係数 (MFCC)など、他の特徴量を使用することも検討してください。
- 必要に応じて、ノイズ除去や音量正規化などの前処理を行うと、精度が向上する可能性があります。
- MIDI生成処理:
- モデルの出力を、MIDIの音符情報に変換する処理が必要です。
- 音符のオンセット、オフセットを推定し、MIDIノートとして表現します。
- 単純に閾値処理で音符を判定するのではなく、モデルの出力確率を考慮した処理を取り入れることが理想的です。
- ベロシティや音符の長さを適切に設定することで、より音楽的なMIDIデータを作成できます。
- UIについて:
- UIはTkinterを用いて作られています。
- 入力WAVファイルの選択、出力MIDIファイルの保存先ディレクトリの選択ができるようになっています。
- 変換ボタンを押すことで、WAVからMIDIへの変換が実行されます。
- プログレスバーで処理の進捗状況を表示します。
モデルについて:
このコードでは、sound_to_midi_model.h5
という名前のモデルをロードしようとしていますが、実際には自分で学習させる必要があります。
- 学習データの準備:
- 大量の音声データ(WAVファイル)と、対応するMIDIファイルが必要です。
- さまざまな楽器、ジャンル、演奏スタイルを網羅したデータを用意することが望ましいです。
- データセットは、インターネット上に公開されているものや、自分で作成したものを使うことができます。
- モデルの構築:
- モデルは、音声特徴量を入力とし、MIDIノート(ピッチ、ベロシティ、時間情報など)を出力とする回帰モデルを構築する必要があります。
- Convolutional Neural Network (CNN), Recurrent Neural Network (RNN), Transformerなどのアーキテクチャが考えられます。
- TensorFlowやPyTorchなどのフレームワークを使って構築します。
- モデルの学習:
- 用意したデータセットを使って、モデルを学習させます。
- 学習率やバッチサイズなどのハイパーパラメータを調整しながら、モデルの精度を最適化します。
補足:
- このコードはあくまで基本的な枠組みであり、実際の高品質な変換には、モデルの改善やより高度な信号処理が必要になります。
- モデルの学習には、GPU環境とそれなりの計算資源が必要となります。
- 処理時間や精度は、モデルや入力ファイルの長さによって大きく変動します。
モデルの例
import tensorflow as tf
from tensorflow import keras
def create_model(input_shape):
model = keras.Sequential([
keras.layers.Conv2D(32, (3, 3), activation='relu', padding='same', input_shape=input_shape),
keras.layers.MaxPooling2D((2, 2)),
keras.layers.Conv2D(64, (3, 3), activation='relu', padding='same'),
keras.layers.MaxPooling2D((2, 2)),
keras.layers.Conv2D(128, (3, 3), activation='relu', padding='same'),
keras.layers.MaxPooling2D((2, 2)),
keras.layers.Flatten(),
keras.layers.Dense(256, activation='relu'),
keras.layers.Dense(128, activation='relu'),
keras.layers.Dense(128, activation='softmax') # MIDIノート(ピッチ)を予測する場合
])
return model
if __name__ == '__main__':
# ダミーの入力シェイプ
input_shape = (None, 1025, 1) # 例:(時間フレーム数, 周波数ビン数, チャネル数)
model = create_model(input_shape)
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
model.summary() # モデルの構造を出力
# ダミーの入力データを作成してモデルをテスト
dummy_input = tf.random.normal(shape=(1, 100, 1025, 1)) # 1サンプル、100フレーム、1025周波数ビン
dummy_output = model(dummy_input)
print("出力形状:", dummy_output.shape)
# モデルを保存
model.save("sound_to_midi_model.h5")