pygame简谱播放器双音轨支持导出mid音乐完整版代码QZQQM-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 = []


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()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

EasySoft易软

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

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

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

打赏作者

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

抵扣说明:

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

余额充值