import tkinter as tk
from tkinter import messagebox,Menu
from midiutil import MIDIFile
import re
note_map = {
'1...': 24, '2...': 26, '3...': 28, '4...': 29, '5...': 31, '6...': 33, '7...': 35,
'1..': 36, '2..': 38, '3..': 40, '4..': 41, '5..': 43, '6..': 45, '7..': 47,
'1.': 48, '2.': 50, '3.': 52, '4.': 53, '5.': 55, '6.': 57, '7.': 59,
'1': 60, '2': 62, '3': 64, '4': 65, '5': 67, '6': 69, '7': 71,
'1*': 72, '2*': 74, '3*': 76, '4*': 77, '5*': 79, '6*': 81, '7*': 83,
'1**': 84, '2**': 86, '3**': 88, '4**': 89, '5**': 91, '6**': 93, '7**': 95,
'1***': 96, '2***': 98, '3***': 100, '4***': 101, '5***': 103, '6***': 105, '7***': 107,
'0': None, '-': None
}
def parse_score(s):
score = []
i = 0
while i < len(s):
if s[i] == '-':
j = i
while j < len(s) and s[j] == '-':
j += 1
duration = j - i
score.append(('-', duration))
i = j
else:
j = i + 1
while j < len(s) and s[j] in ['.', '*']:
j += 1
note = s[i:j]
k = j
while k < len(s) and s[k] == '-':
k += 1
duration = 1 + (k - j)
score.append((note, duration))
i = k
return score
def generate_midi():
"""生成MIDI文件"""
try:
简谱 = 简谱文本框.get("1.0", tk.END).strip()
速度 = int(速度文本框.get())
乐器编号 = int(乐器编号文本框.get())
文件名 = 文件名文本框.get().strip()
if not 简谱:
messagebox.showwarning("警告", "请输入简谱")
return
if not 文件名:
messagebox.showwarning("警告", "请输入文件名")
return
parsed_score = parse_score(简谱)
midi = MIDIFile(numTracks=1)
midi.addTempo(0, 0, 速度)
midi.addProgramChange(0, 0, 0, 乐器编号)
time = 0
for note_str, duration in parsed_score:
if note_str in ['0', '-']:
time += duration
continue
midi_pitch = note_map.get(note_str)
if not midi_pitch:
messagebox.showwarning("警告", f"未知音符:{note_str}")
continue
midi.addNote(
track=0, channel=0, pitch=midi_pitch,
time=time, duration=duration, volume=80
)
time += duration
with open(f"{文件名}.mid", "wb") as f:
midi.writeFile(f)
messagebox.showinfo("成功", f"MIDI文件生成成功:{文件名}.mid")
except Exception as e:
messagebox.showerror("错误", f"生成失败:{str(e)}")
def create_textbox_menu(textbox):
"""创建文本框右键菜单"""
menu = Menu(textbox, tearoff=0)
def select_all(event):
textbox.tag_add(tk.SEL, "1.0", tk.END)
textbox.icursor(tk.END)
return "break"
def copy_text(event):
textbox.event_generate("<<Copy>>")
return "break"
def paste_text(event):
textbox.event_generate("<<Paste>>")
return "break"
def show_menu(event):
try:
menu.tk_popup(event.x_root, event.y_root)
finally:
menu.grab_release()
menu.add_command(label="全选", command=lambda: select_all(None))
menu.add_separator()
menu.add_command(label="复制", command=lambda: copy_text(None))
menu.add_command(label="粘贴", command=lambda: paste_text(None))
textbox.bind("<Button-3>", show_menu)
textbox.bind("<Control-a>", select_all)
textbox.bind("<Control-c>", copy_text)
textbox.bind("<Control-v>", paste_text)
窗口 = tk.Tk()
窗口.title("简谱转MIDI工具")
窗口.geometry("500x400")
tk.Label(窗口, text="请输入简谱(使用.-*表示音高):").pack(pady=5)
简谱文本框 = tk.Text(窗口, height=8, width=50)
简谱文本框.pack(pady=5, padx=10)
简谱文本框.insert(tk.END, "5-351*--76-1*-5---5-123-212----5-351*--76-1*-5---5-234--7.1----6-1*-1*---7-671*---671*665312----5-351*--76-1*-5---5-234--7.1------")
create_textbox_menu(简谱文本框)
tk.Label(窗口, text="速度(BPM,默认120):").pack(pady=3)
速度文本框 = tk.Entry(窗口)
速度文本框.pack(pady=3, padx=10)
速度文本框.insert(tk.END, "120")
tk.Label(窗口, text="乐器编号(GM标准,如钢琴=0):").pack(pady=3)
乐器编号文本框 = tk.Entry(窗口)
乐器编号文本框.pack(pady=3, padx=10)
乐器编号文本框.insert(tk.END, "0")
tk.Label(窗口, text="请输入文件名:").pack(pady=3)
文件名文本框 = tk.Entry(窗口)
文件名文本框.pack(pady=3, padx=10)
生成按钮 = tk.Button(窗口, text="生成MIDI文件", command=generate_midi, bg="#4CAF50", fg="white")
生成按钮.pack(pady=15)
窗口.mainloop()