#pip install pygame
#pip install mido
#pip install pyinstaller
#pyinstaller --onefile main.py
import tkinter as tk
from tkinter import filedialog
import pygame.midi
import time
import threading
import mido # 导入mido库用于创建和保存MIDI文件
# 初始化 pygame.midi
pygame.midi.init()
# 获取 MIDI 输出设备
midi_out = pygame.midi.Output(0)
# 乐器名称映射
instrument_names = {
0: "大钢琴(声学钢琴)",
1: "明亮的钢琴",
2: "电钢琴",
}
# 播放控制标志
is_playing = False
is_paused = False
pause_start_time = 0
current_note_index = 0
all_notes = []
def play_note(note_number, duration, instrument):
global is_playing, is_paused, pause_start_time
midi_out.set_instrument(instrument)
midi_out.note_on(note_number, 125)
start_time = time.time()
while time.time() - start_time < duration / 1000:
if is_paused:
pause_start_time = time.time()
while is_paused:
root.update()
start_time += time.time() - pause_start_time
if not is_playing:
midi_out.note_off(note_number, 125)
return
midi_out.note_off(note_number, 125)
def play_next_note(notes, instrument, tune, speed):
global is_playing
index = 0
while is_playing and index < len(notes):
note = notes[index]
note_number, duration = process_note(note, speed)
note_number = convert_tune(note_number, tune)
if note_number is not None:
play_note(note_number, duration, instrument)
index += 1
def play_music_together():
global is_playing
is_playing = True
is_paused = False
main_music_notes = entry_main.get("1.0", "end-1c")
accompaniment_music_notes = entry_accompaniment.get("1.0", "end-1c")
main_notes, main_instrument, main_speed, main_tune = parse_notes_and_settings(main_music_notes)
accompaniment_notes, accompaniment_instrument, accompaniment_speed, accompaniment_tune = parse_notes_and_settings(
accompaniment_music_notes)
pause_button.config(text="暂停")
main_thread = threading.Thread(target=play_next_note, args=(main_notes, main_instrument, main_tune, main_speed))
accompaniment_thread = threading.Thread(target=play_next_note,
args=(accompaniment_notes, accompaniment_instrument, accompaniment_tune,
accompaniment_speed))
main_thread.start()
accompaniment_thread.start()
root = tk.Tk()
root.title("Digital Music Player")
# 设置窗口大小
root.geometry("800x600")
# 创建主旋律文本框
entry_main = tk.Text(root, wrap="word", height=5)
entry_main.pack(pady=10)
entry_main.place(x=10, y=10, width=300, height=100)
entry_main.insert(tk.END,
"G 大调,0,300\n-5/-,1/-,1/,1/-,3/,2/-,1/,2/-,3/,1/,1/-,1/,3/,5/,6/--,\n6/,5/,3/,3/-,1/,2/,1/,2/--,3/,1/,-6/,-6/,-5/,1/--,\n6/,5/,3/,3/-,1/,2/,1/,2/--,\n6/,5/-,3/,3/-,5/-,6/--,\n+1/,5/,3/,3/-,1/,2/,1/,2/-,\n3/,1/-,-6/,-6/-,-5/,1/--,")
# 创建伴奏文本框
entry_accompaniment = tk.Text(root, wrap="word", height=5)
entry_accompaniment.pack(pady=10)
entry_accompaniment.place(x=10, y=200, width=300, height=100)
entry_accompaniment.insert(tk.END,
"C 大调,0,300\n-5/-,1/-,1/,1/-,3/,2/-,1/,2/-,3/,1/,1/-,1/,3/,5/,6/--,\n6/,5/,3/,3/-,1/,2/,1/,2/--,3/,1/,-6/,-6/,-5/,1/--,\n6/,5/,3/,3/-,1/,2/,1/,2/--,\n6/,5/-,3/,3/-,5/-,6/--,\n+1/,5/,3/,3/-,1/,2/,1/,2/-,\n3/,1/-,-6/,-6/-,-5/,1/--,")
# 创建文本框的右键菜单
def show_textbox_menu(event):
menu = tk.Menu(root, tearoff=0)
menu.add_command(label="剪切", command=lambda: event.widget.event_generate("<<Cut>>"))
menu.add_command(label="复制", command=lambda: event.widget.event_generate("<<Copy>>"))
menu.add_command(label="粘贴", command=lambda: event.widget.event_generate("<<Paste>>"))
menu.add_separator()
menu.add_command(label="全选", command=lambda: event.widget.event_generate("<<SelectAll>>"))
menu.post(event.x_root, event.y_root)
entry_main.bind("<Button-3>", show_textbox_menu)
entry_accompaniment.bind("<Button-3>", show_textbox_menu)
def play_next_note_old():
global is_playing, current_note_index
if not is_playing or current_note_index >= len(all_notes):
is_playing = False
current_note_index = 0
pause_button.config(text="暂停")
return
note, instrument, tune, speed = all_notes[current_note_index]
note_number, duration = process_note(note, speed)
note_number = convert_tune(note_number, tune)
if note_number is not None:
play_note(note_number, duration, instrument)
current_note_index += 1
root.after(1, play_next_note_old)
def play_music():
global is_playing, current_note_index, all_notes
is_playing = True
is_paused = False
current_note_index = 0
main_music_notes = entry_main.get("1.0", "end-1c")
accompaniment_music_notes = entry_accompaniment.get("1.0", "end-1c")
main_notes, main_instrument, main_speed, main_tune = parse_notes_and_settings(main_music_notes)
accompaniment_notes, accompaniment_instrument, accompaniment_speed, accompaniment_tune = parse_notes_and_settings(
accompaniment_music_notes)
all_notes = []
for note in main_notes:
all_notes.append((note, main_instrument, main_tune, main_speed))
for note in accompaniment_notes:
all_notes.append((note, accompaniment_instrument, accompaniment_tune, accompaniment_speed))
pause_button.config(text="暂停")
play_next_note_old()
def pause_music():
global is_paused
is_paused = not is_paused
if is_paused:
pause_button.config(text="继续")
else:
pause_button.config(text="暂停")
def stop_music():
global is_playing, is_paused, current_note_index
is_playing = False
is_paused = False
current_note_index = 0
pause_button.config(text="暂停")
def parse_notes_and_settings(notes_str):
lines = notes_str.splitlines()
if len(lines) > 0:
settings = lines[0].split(',')
tune = settings[0].strip()
instrument = int(settings[1])
speed = int(settings[2])
notes_str = '\n'.join(lines[1:])
else:
tune = "C 大调"
instrument = 0
speed = 300
notes = []
current_note = ""
for char in notes_str:
if char == ',' or char == '\n':
if current_note:
notes.append(current_note)
current_note = ""
else:
current_note += char
if current_note:
if current_note.strip():
notes.append(current_note)
return notes, instrument, speed, tune
def process_note(note_str, base_duration):
note_number = None
duration = base_duration
# 处理八度
octave_offset = 0
if note_str.startswith('+'):
octave_offset = 12
note_str = note_str[1:]
elif note_str.startswith('-'):
octave_offset = -12
note_str = note_str[1:]
# 处理基本音符
note_mapping = {'0': None, '1': 60, '2': 62, '3': 64, '4': 65, '5': 67, '6': 69, '7': 71}
if note_str and note_str[0].isdigit():
note_number = note_mapping.get(note_str[0])
if note_number is not None:
note_number += octave_offset
note_str = note_str[1:]
# 处理时值变化
slash_count = note_str.count('/')
if slash_count > 0:
duration /= 2 ** slash_count
dash_count = note_str.count('-')
if dash_count > 0:
duration *= 2 ** dash_count
return note_number, duration
def convert_tune(note_number, tune):
if tune == "D 大调":
return note_number
elif tune == "E 大调":
return note_number + 2
elif tune == "F 大调":
return note_number + 3
elif tune == "G 大调":
return note_number + 5
elif tune == "A 大调":
return note_number + 7
elif tune == "B 大调":
return note_number + 9
elif tune == "C 大调":
return note_number - 2
def export_midi():
file_path = filedialog.asksaveasfilename(defaultextension=".mid", filetypes=[("MIDI Files", "*.mid")])
if file_path:
mid = mido.MidiFile()
main_music_notes = entry_main.get("1.0", "end-1c")
accompaniment_music_notes = entry_accompaniment.get("1.0", "end-1c")
main_notes, main_instrument, main_speed, main_tune = parse_notes_and_settings(main_music_notes)
accompaniment_notes, accompaniment_instrument, accompaniment_speed, accompaniment_tune = parse_notes_and_settings(
accompaniment_music_notes)
# 添加主旋律音轨
main_track = mido.MidiTrack()
mid.tracks.append(main_track)
main_track.append(mido.MetaMessage('track_name', name='Main Track'))
main_track.append(mido.Message('program_change', program=main_instrument, time=0))
for note in main_notes:
note_number, duration = process_note(note, main_speed)
note_number = convert_tune(note_number, main_tune)
if note_number is not None:
main_track.append(mido.Message('note_on', note=note_number, velocity=125, time=0))
main_track.append(mido.Message('note_off', note=note_number, velocity=125, time=int(duration)))
# 添加伴奏音轨
accompaniment_track = mido.MidiTrack()
mid.tracks.append(accompaniment_track)
accompaniment_track.append(mido.MetaMessage('track_name', name='Accompaniment Track'))
accompaniment_track.append(mido.Message('program_change', program=accompaniment_instrument, time=0))
for note in accompaniment_notes:
note_number, duration = process_note(note, accompaniment_speed)
note_number = convert_tune(note_number, accompaniment_tune)
if note_number is not None:
accompaniment_track.append(mido.Message('note_on', note=note_number, velocity=125, time=0))
accompaniment_track.append(mido.Message('note_off', note=note_number, velocity=125, time=int(duration)))
mid.save(file_path)
# 创建播放按钮
play_button = tk.Button(root, text="播放", command=play_music)
play_button.pack(pady=20)
play_button.place(x=330, y=100, width=100, height=50)
# 创建并行播放按钮
play_together_button = tk.Button(root, text="并行播放", command=play_music_together)
play_together_button.pack(pady=20)
play_together_button.place(x=330, y=150, width=100, height=50)
# 创建暂停按钮
pause_button = tk.Button(root, text="暂停", command=pause_music)
pause_button.pack(pady=20)
pause_button.place(x=440, y=130, width=100, height=50)
# 创建停止按钮
stop_button = tk.Button(root, text="停止", command=stop_music)
stop_button.pack(pady=20)
stop_button.place(x=550, y=130, width=100, height=50)
# 创建导出按钮
export_button = tk.Button(root, text="导出", command=export_midi)
export_button.pack(pady=20)
export_button.place(x=330, y=200, width=100, height=50)
root.mainloop()
# 关闭 MIDI 输出设备
midi_out.close()
pygame.midi.quit()
pygame简谱播放器双音轨支持导出mid音乐完整版代码QZQQM-2025-4-24
最新推荐文章于 2025-04-28 15:02:36 发布