python语言pygame播放mid文件播放器程序代码QZQ

import subprocess
import os
import sys
import tkinter as tk
from tkinter import messagebox, ttk, filedialog
import threading
import platform
import pygame  # 新增pygame库用于播放MIDI

class MidiPlayerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("MIDI播放器")
        self.root.geometry("600x440")
        self.root.resizable(False, False)

        # 设置中文字体支持
        self.style = ttk.Style()
        self.style.configure("TLabel", font=("SimHei", 10))
        self.style.configure("TButton", font=("SimHei", 10))
        self.style.configure("TEntry", font=("SimHei", 10))

        # 初始化文件路径
        self.fluid_path = ""  # 移除FluidSynth默认路径
        self.soundfont = r"a.sf2"
        self.midi_file = "送别1.mid"

        # 播放方式选项
        self.use_custom_soundfont = tk.BooleanVar(value=False)  # 默认不使用自定义音色库
        self.use_system_player = tk.BooleanVar(value=True)     # 默认使用系统播放(pygame)

        self.process = None  # 存储子进程对象
        self.is_playing = False  # 播放状态
        self.pygame_initialized = False  # pygame初始化标记

        self.create_widgets()

    def create_widgets(self):
        # 创建主框架
        main_frame = ttk.Frame(self.root, padding=20)
        main_frame.pack(fill=tk.BOTH, expand=True)

        # 标题
        ttk.Label(main_frame, text="MIDI播放器", font=("SimHei", 16, "bold")).pack(pady=10)

        # FluidSynth路径选择(仅在不使用系统播放时显示)
        fluid_frame = ttk.Frame(main_frame)
        fluid_frame.pack(fill=tk.X, pady=5)

        ttk.Label(fluid_frame, text="FluidSynth路径:").pack(side=tk.LEFT, padx=5)
        self.fluid_path_var = tk.StringVar(value=self.fluid_path)
        self.fluid_entry = ttk.Entry(fluid_frame, textvariable=self.fluid_path_var, width=40)
        self.fluid_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)

        browse_fluid_btn = ttk.Button(fluid_frame, text="浏览...", command=self.browse_fluid_path)
        browse_fluid_btn.pack(side=tk.LEFT, padx=5)

        # 使用系统播放器的复选框(改为默认选中,使用pygame播放)
        use_sys_frame = ttk.Frame(main_frame)
        use_sys_frame.pack(fill=tk.X, pady=5)

        use_sys_check = ttk.Checkbutton(
            use_sys_frame,
            text="使用系统默认MIDI播放 (pygame)",
            variable=self.use_system_player,
            command=self.toggle_system_player
        )
        use_sys_check.pack(side=tk.LEFT, padx=5)

        # 系统播放器说明
        self.sys_player_note = ttk.Label(
            use_sys_frame,
            text="(启用后将使用pygame播放MIDI,依赖系统音频驱动)",
            foreground="blue",
            font=("SimHei", 9)
        )
        self.sys_player_note.pack(side=tk.LEFT, padx=5)

        # SoundFont路径选择(仅在使用FluidSynth时显示)
        self.soundfont_frame = ttk.Frame(main_frame)
        self.soundfont_frame.pack(fill=tk.X, pady=5)

        ttk.Label(self.soundfont_frame, text="SoundFont文件:").pack(side=tk.LEFT, padx=5)
        self.soundfont_var = tk.StringVar(value=self.soundfont)
        soundfont_entry = ttk.Entry(self.soundfont_frame, textvariable=self.soundfont_var, width=40)
        soundfont_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)

        browse_sf_btn = ttk.Button(self.soundfont_frame, text="浏览...", command=self.browse_soundfont)
        browse_sf_btn.pack(side=tk.LEFT, padx=5)

        # MIDI文件路径选择
        midi_frame = ttk.Frame(main_frame)
        midi_frame.pack(fill=tk.X, pady=5)

        ttk.Label(midi_frame, text="MIDI文件:").pack(side=tk.LEFT, padx=5)
        self.midi_file_var = tk.StringVar(value=self.midi_file)
        midi_entry = ttk.Entry(midi_frame, textvariable=self.midi_file_var, width=40)
        midi_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)

        browse_midi_btn = ttk.Button(midi_frame, text="浏览...", command=self.browse_midi_file)
        browse_midi_btn.pack(side=tk.LEFT, padx=5)

        # 音量控制(pygame和FluidSynth均可使用)
        volume_frame = ttk.Frame(main_frame)
        volume_frame.pack(fill=tk.X, pady=10)

        ttk.Label(volume_frame, text="音量:").pack(side=tk.LEFT, padx=5)
        self.volume_var = tk.DoubleVar(value=0.8)
        volume_scale = ttk.Scale(volume_frame, variable=self.volume_var, from_=0.0, to=1.0, orient=tk.HORIZONTAL,
                                 length=200)
        volume_scale.pack(side=tk.LEFT, padx=5)
        self.volume_label = ttk.Label(volume_frame, text=f"{int(self.volume_var.get() * 100)}%")
        self.volume_label.pack(side=tk.LEFT, padx=5)
        volume_scale.bind("<Motion>", self.update_volume_label)

        # 状态显示
        self.status_var = tk.StringVar()
        self.status_var.set("就绪")
        status_label = ttk.Label(main_frame, textvariable=self.status_var, foreground="red", font=("SimHei", 12))
        status_label.pack(pady=10)

        # 按钮框架
        btn_frame = ttk.Frame(main_frame)
        btn_frame.pack(fill=tk.X, pady=10)

        # 播放按钮
        play_btn = ttk.Button(btn_frame, text="播放", width=15, command=self.play_midi)
        play_btn.pack(side=tk.LEFT, padx=5)

        # 停止按钮
        stop_btn = ttk.Button(btn_frame, text="停止", width=15, command=self.stop_midi)
        stop_btn.pack(side=tk.LEFT, padx=5)

        # 退出按钮
        exit_btn = ttk.Button(btn_frame, text="退出", width=15, command=self.root.quit)
        exit_btn.pack(side=tk.LEFT, padx=5)

        # 底部信息
        ttk.Label(main_frame, text="注意:确保MIDI文件路径正确且系统音量已打开", font=("SimHei", 9)).pack(side=tk.BOTTOM, pady=10)

        # 初始化控件状态
        self.toggle_system_player()

    def toggle_system_player(self):
        """根据复选框状态启用或禁用相关控件"""
        use_system = self.use_system_player.get()

        # 禁用FluidSynth和SoundFont相关控件
        self.fluid_entry.configure(state="disabled" if use_system else "normal")
        for child in self.soundfont_frame.winfo_children():
            child.configure(state="disabled" if use_system else "normal")

        # 音量控制始终启用(pygame和FluidSynth均支持)
        for child in self.volume_label.master.winfo_children():
            if isinstance(child, ttk.Scale):
                child.configure(state="normal")
            elif isinstance(child, ttk.Label) and child != self.volume_label:
                continue
            else:
                child.configure(state="normal")

    def update_volume_label(self, event):
        """更新音量标签显示"""
        self.volume_label.config(text=f"{int(self.volume_var.get() * 100)}%")
        # 如果使用pygame,更新音量
        if self.use_system_player.get() and self.pygame_initialized:
            pygame.mixer.music.set_volume(self.volume_var.get())

    def browse_fluid_path(self):
        """浏览并选择FluidSynth路径(仅在不使用系统播放时有效)"""
        if self.use_system_player.get():
            return

        filename = filedialog.askopenfilename(
            title="选择FluidSynth可执行文件",
            filetypes=[("可执行文件", "*.exe")]
        )
        if filename:
            self.fluid_path_var.set(filename)

    def browse_soundfont(self):
        """浏览并选择SoundFont文件(仅在使用FluidSynth时有效)"""
        if self.use_system_player.get():
            return

        filename = filedialog.askopenfilename(
            title="选择SoundFont文件",
            filetypes=[("SoundFont文件", "*.sf2")]
        )
        if filename:
            self.soundfont_var.set(filename)

    def browse_midi_file(self):
        """浏览并选择MIDI文件"""
        filename = filedialog.askopenfilename(
            title="选择MIDI文件",
            filetypes=[("MIDI文件", "*.mid;*.midi")]
        )
        if filename:
            self.midi_file_var.set(filename)

    def check_files(self):
        """检查必要文件是否存在"""
        self.midi_file = self.midi_file_var.get()

        if not os.path.exists(self.midi_file):
            messagebox.showerror("错误", f"MIDI文件 '{self.midi_file}' 不存在!")
            return False

        if not self.use_system_player.get():
            # 检查FluidSynth相关文件
            self.fluid_path = self.fluid_path_var.get()
            self.soundfont = self.soundfont_var.get()

            if not os.path.exists(self.fluid_path):
                messagebox.showerror("错误", f"FluidSynth程序 '{self.fluid_path}' 不存在!")
                return False

            if not os.path.exists(self.soundfont):
                messagebox.showerror("错误", f"SoundFont文件 '{self.soundfont}' 不存在!")
                return False

        return True

    def play_midi(self):
        """播放MIDI文件"""
        if self.is_playing:
            return

        if not self.check_files():
            return

        self.status_var.set("正在播放...")
        self.is_playing = True

        # 在新线程中执行播放,避免阻塞GUI
        threading.Thread(target=self._run_playback, daemon=True).start()

    def _run_playback(self):
        """根据选择的播放方式执行播放"""
        try:
            if self.use_system_player.get():
                # 使用pygame播放(系统默认音色库)
                self._play_with_pygame()
            else:
                # 使用FluidSynth播放(自定义音色库)
                self._play_with_fluidsynth()

        except Exception as e:
            error = str(e)

            def show_exception():
                messagebox.showerror("播放错误", error)

            self.root.after(0, show_exception)
        finally:
            self.root.after(0, self._play_finished)

    def _play_with_pygame(self):
        """使用pygame播放MIDI(系统默认音色库)"""
        try:
            # 初始化pygame mixer
            if not self.pygame_initialized:
                pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=512)
                self.pygame_initialized = True

            # 加载并播放MIDI
            pygame.mixer.music.load(self.midi_file)
            pygame.mixer.music.set_volume(self.volume_var.get())
            pygame.mixer.music.play()

            # 等待播放完成
            while pygame.mixer.music.get_busy() and self.is_playing:
                pygame.time.delay(100)

        except pygame.error as e:
            raise Exception(f"pygame播放错误: {str(e)}")
        except Exception as e:
            raise Exception(f"播放失败: {str(e)}")

    def _play_with_fluidsynth(self):
        """使用FluidSynth播放(自定义音色库)"""
        # 构建FluidSynth命令参数
        command = [self.fluid_path, '-a', 'dsound', '-g', str(self.volume_var.get())]
        command.extend([self.soundfont, self.midi_file])

        # 执行命令
        self.process = subprocess.Popen(
            command,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        )

        # 等待进程完成
        stdout, stderr = self.process.communicate()

        if self.process.returncode != 0:
            error_msg = stderr.decode('utf-8') if stderr else "FluidSynth执行错误"
            raise Exception(error_msg)

    def _play_finished(self):
        """播放完成或停止后的清理工作"""
        self.is_playing = False
        self.process = None
        self.status_var.set("就绪")

        # 停止pygame播放(如果正在使用)
        if self.use_system_player.get() and self.pygame_initialized:
            pygame.mixer.music.stop()

    def stop_midi(self):
        """停止播放"""
        if not self.is_playing:
            return

        self.is_playing = False

        if self.use_system_player.get() and self.pygame_initialized:
            # 停止pygame播放
            pygame.mixer.music.stop()
        elif self.process:
            # 停止FluidSynth进程
            try:
                self.process.terminate()
                try:
                    self.process.wait(timeout=2)
                except subprocess.TimeoutExpired:
                    self.process.kill()
            except Exception as e:
                messagebox.showerror("错误", f"停止播放时出错: {str(e)}")

        self._play_finished()


if __name__ == "__main__":
    root = tk.Tk()
    app = MidiPlayerApp(root)
    root.mainloop()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

EYYLTV

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

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

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

打赏作者

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

抵扣说明:

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

余额充值