import tkinter as tk
from tkinter import filedialog
import pygame.midi
import time
import threading
import mido
pygame.midi.init()
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
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_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("Digital Music Player")
root.geometry("800x800")
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
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():
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 = 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=125, time=0))
track.append(mido.Message('note_off', note=note_number, velocity=125, 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
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=150, height=50)
display_textbox(0)
root.mainloop()
midi_out.close()
pygame.midi.quit()