python语言pygame16音轨简谱播放器QZQ-2025-4-24

#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 = []

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")

# 创建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
    # 处理八度
    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 输出设备
midi_out.close()
pygame.midi.quit()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EasySoft易软

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值