#pip install pygame
#pip install mido
#pip install pyinstaller
#pyinstaller --onefile main.py
import random
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 = []
text_boxes = []
play_buttons = []
track_settings = [] # 存储每个音轨的设置(调式、乐器、速度)
threads = [] # 存储所有线程
# 记录当前显示的文本框索引
current_displayed_index = 0
# 音量设置,初始值为64
volume = 64
def play_note(note_number, duration, instrument, velocity):
global is_playing, is_paused, pause_start_time
midi_out.set_instrument(instrument)
midi_out.note_on(note_number, velocity)
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, velocity)
return
midi_out.note_off(note_number, velocity)
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, velocity = process_note(note, speed)
note_number = convert_tune(note_number, tune)
if note_number is not None:
play_note(note_number, duration, instrument, velocity)
index += 1
def play_all_tracks_together():
global is_playing, threads
is_playing = True
is_paused = False
pause_button.config(text="暂停")
threads = []
for i in range(16):
notes_str = text_boxes[i].get("1.0", "end-1c")
if notes_str.strip(): # 检查文本框是否有内容
notes, instrument, speed, tune = parse_notes_and_settings(notes_str)
thread = threading.Thread(target=play_next_note, args=(notes, instrument, tune, speed))
threads.append(thread)
thread.start()
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, threads
is_playing = False
is_paused = False
current_note_index = 0
pause_button.config(text="暂停")
# 停止所有线程
for thread in threads:
if thread.is_alive():
thread.join()
root = tk.Tk()
root.title("简谱播放器 V1.4")
root.geometry("900x800")
# 创建16个文本框和对应的按钮
for i in range(16):
text_box = tk.Text(root, wrap="word", height=5)
text_box.pack(pady=5)
text_box.place(x=10, y=10, width=900, height=50)
text_boxes.append(text_box)
play_button = tk.Button(root, text=f"显示音轨{i + 1}", command=lambda i=i: display_textbox(i))
play_button.pack(pady=5)
play_button.place(x=430, y=5 + i * 35, width=100, height=40)
play_buttons.append(play_button)
# 设置默认显示第一个音轨文本框的内容
if i == 0:
text_box.insert(tk.END,
"G 大调,0,500\n-5/,-6/,1/-,2/,3/,-7/,-6/,-7/,-5/,-6/,-6/--,1/,2/,3/-,5/,6/,1/,2/,3/,4/,3/,3/--,3/,5/,6/-,5/,6/--,-6/,3/,2/-,1/,2/--,3/,-5/-,-6/,-7/,3/,-6/,-6/,1/,1/--,5/,5/,6/,+1/,7/,6/,5/,6/,6/--,5/,5/,6/-,+1/,7/,6/,5/,3/,3/--,-5/,-6/,1/-,2/,3/,-7/,-6/,-7/,-5/,-6/,-6/--,1/,2/,3/-,5/,6/,1/,2/,3/,4/,3/,3/--,3/,5/,6/-,5/,6/--,-6/,3/,2/-,1/,2/--,3/,-5/-,-6/,-7/,3/,-6/,-6/,1/,1/--,1/,3/,-6/,-6/,1/,1/--,-7/,3/,-6/,-6/,1/,1/--,")
if i == 1:
text_box.insert(tk.END,
"C 大调,0,500\n-5/,-6/,1/-,2/,3/,-7/,-6/,-7/,-5/,-6/,-6/--,1/,2/,3/-,5/,6/,1/,2/,3/,4/,3/,3/--,3/,5/,6/-,5/,6/--,-6/,3/,2/-,1/,2/--,3/,-5/-,-6/,-7/,3/,-6/,-6/,1/,1/--,5/,5/,6/,+1/,7/,6/,5/,6/,6/--,5/,5/,6/-,+1/,7/,6/,5/,3/,3/--,-5/,-6/,1/-,2/,3/,-7/,-6/,-7/,-5/,-6/,-6/--,1/,2/,3/-,5/,6/,1/,2/,3/,4/,3/,3/--,3/,5/,6/-,5/,6/--,-6/,3/,2/-,1/,2/--,3/,-5/-,-6/,-7/,3/,-6/,-6/,1/,1/--,1/,3/,-6/,-6/,1/,1/--,-7/,3/,-6/,-6/,1/,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)
for text_box in text_boxes:
text_box.bind("<Button-3>", show_textbox_menu)
def play_music_together(track_index):
global is_playing
is_playing = True
is_paused = False
pause_button.config(text="暂停")
notes_str = text_boxes[track_index].get("1.0", "end-1c")
if notes_str.strip(): # 检查文本框是否有内容
notes, instrument, speed, tune = parse_notes_and_settings(notes_str)
thread = threading.Thread(target=play_next_note, args=(notes, instrument, tune, speed))
thread.start()
thread.join()
is_playing = False
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
velocity = 127 # 默认力度为127
# 处理八度
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
# 处理力度
if ',' in note_str:
parts = note_str.split(',')
if len(parts) > 1:
try:
velocity = int(parts[1])
except ValueError:
pass
return note_number, duration, velocity
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():
try:
file_path = filedialog.asksaveasfilename(defaultextension=".mid", filetypes=[("MIDI Files", "*.mid")])
if file_path:
mid = mido.MidiFile()
for i in range(16):
notes_str = text_boxes[i].get("1.0", "end-1c")
if notes_str.strip(): # 检查文本框是否有内容
notes, instrument, speed, tune = parse_notes_and_settings(notes_str)
# 添加音轨
track = mido.MidiTrack()
mid.tracks.append(track)
track.append(mido.MetaMessage('track_name', name=f'Track {i + 1}'))
track.append(mido.Message('program_change', program=instrument, time=0))
for note in notes:
note_number, duration, velocity = process_note(note, speed)
note_number = convert_tune(note_number, tune)
if note_number is not None:
track.append(mido.Message('note_on', note=note_number, velocity=velocity, time=0))
track.append(mido.Message('note_off', note=note_number, velocity=velocity, time=int(duration)))
mid.save(file_path)
print(f"文件已成功导出到 {file_path}")
except Exception as e:
print(f"导出过程中出现错误: {e}")
def display_textbox(index):
global current_displayed_index
for i, text_box in enumerate(text_boxes):
if i == index:
text_box.place(x=10, y=10, width=400, height=400) # 调整显示位置和大小
else:
text_box.place_forget() # 隐藏其他文本框
current_displayed_index = index
# 定义音符列表
z = ["-1/", "-2/", "-3/", "-4/", "-5/", "-6/", "-7/", "1/", "2/", "3/", "4/", "5/", "6/", "7/", "+1/", "+2/", "+3/", "+4/", "+5/",
"+6/", "+7/"]
# 定义音符列表
a = [",", "-,", "--,"]
# 定义生成随机音符的函数
def generate_random_notes():
random_notes = []
for _ in range(110):
random_note = random.choice(z)+random.choice(a)
random_notes.append(random_note)
# 清空文本框3并插入随机生成的音符
text_boxes[2].delete("1.0", tk.END) # 清空文本框3
text_boxes[2].insert(tk.END, "G 大调,0,500\n" + "".join(random_notes)) # 将随机音符插入文本框3
# 创建生成随机音符的按钮
generate_button = tk.Button(root, text="随机谱曲音轨3", command=generate_random_notes)
generate_button.pack(pady=20)
generate_button.place(x=700, y=80, width=100, height=30)
# 定义音符列表
z1 = ["-1/", "-2/", "-3/", "-4/", "-5/", "-6/", "-7/", "1/", "2/", "3/", "4/", "5/", "6/", "7/", "+1/", "+2/", "+3/", "+4/", "+5/",
"+6/", "+7/"]
# 定义音符列表
a1 = [",", "-,", "--,"]
# 定义生成随机音符的函数
def generate_random_notes1():
random_notes = []
for _ in range(110):
random_note = random.choice(z1)+random.choice(a1)
random_notes.append(random_note)
# 清空文本框4并插入随机生成的音符
text_boxes[3].delete("1.0", tk.END) # 清空文本框4
text_boxes[3].insert(tk.END, "G 大调,0,500\n" + "".join(random_notes)) # 将随机音符插入文本框3
# 创建生成随机音符的按钮
generate_button = tk.Button(root, text="随机谱曲音轨4", command=generate_random_notes1)
generate_button.pack(pady=20)
generate_button.place(x=700, y=110, width=100, height=30)
# 定义音符列表
z2 = ["-1/", "-2/", "-3/", "-4/", "-5/", "-6/", "-7/", "1/", "2/", "3/", "4/", "5/", "6/", "7/", "+1/", "+2/", "+3/", "+4/", "+5/",
"+6/", "+7/"]
# 定义音符列表
a2 = [",", "-,", "--,"]
# 定义生成随机音符的函数
def generate_random_notes2():
random_notes = []
for _ in range(110):
random_note = random.choice(z2)+random.choice(a2)
random_notes.append(random_note)
# 清空文本框5并插入随机生成的音符
text_boxes[4].delete("1.0", tk.END) # 清空文本框5
text_boxes[4].insert(tk.END, "G 大调,0,500\n" + "".join(random_notes)) # 将随机音符插入文本框3
# 创建生成随机音符的按钮
generate_button = tk.Button(root, text="随机谱曲音轨5", command=generate_random_notes2)
generate_button.pack(pady=20)
generate_button.place(x=700, y=140, width=100, height=30)
# 定义清空所有文本框的函数
def clear_all_textboxes():
for text_box in text_boxes:
text_box.delete("1.0", tk.END) # 清空文本框内容
# 创建清空所有文本框的按钮
clear_button = tk.Button(root, text="清空所有", command=clear_all_textboxes)
clear_button.pack(pady=20)
clear_button.place(x=550, y=300, width=100, height=50)
# 创建暂停按钮
pause_button = tk.Button(root, text="暂停", command=pause_music)
pause_button.pack(pady=20)
pause_button.place(x=550, y=200, width=100, height=50)
# 创建停止按钮
stop_button = tk.Button(root, text="停止", command=stop_music)
stop_button.pack(pady=20)
stop_button.place(x=550, y=250, width=100, height=50)
# 创建导出按钮
export_button = tk.Button(root, text="导出", command=export_midi)
export_button.pack(pady=20)
export_button.place(x=550, y=150, width=100, height=50)
# 创建同时播放所有有内容音轨的按钮
play_all_button = tk.Button(root, text="播放全部音轨", command=play_all_tracks_together)
play_all_button.pack(pady=20)
play_all_button.place(x=550, y=100, width=100, height=50)
# 创建音量滑块
volume_scale = tk.Scale(root, from_=0, to=127, orient=tk.HORIZONTAL, label="音量", command=lambda val: set_volume(int(val)))
volume_scale.set(volume)
volume_scale.pack(pady=20)
volume_scale.place(x=550, y=5, width=150, height=80)
def set_volume(new_volume):
global volume
volume = new_volume
message = [(0, [0xB0, 0x07, int(volume)])] # 包装成包含时间戳和 MIDI 消息的元组列表
# try:
# midi_out.write(message)
# except TypeError as e:
# print(f"写入 MIDI 消息时出错: {e}")
# 初始显示第一个文本框
display_textbox(0)
root.mainloop()
# 关闭 MIDI 输出设备
midi_out.close()
pygame.midi.quit()
python语言pygame16音轨简谱播放器ZXQZQ-2025-5-14
最新推荐文章于 2025-05-14 21:45:37 发布