python语言mido简谱播放器3音轨程序代码3

import os
import mido
from mido import Message
import time
import tkinter as tk
import threading
from tkinter import filedialog


class SimpleScoreEditor:
    instrument_options = ["大钢琴(声学钢琴)", "明亮的钢琴", "电钢琴"]
    mode_options = ["C大调", "G大调", "D大调", "A大调", "E大调", "B大调", "F大调"]
    time_signature_options = ["2/4", "3/4", "4/4", "6/8"]

    def __init__(self, root):
        self.root = root
        self.root.title("简易简谱编辑器")
        self.root.geometry("900x800")

        # 标题标签
        self.title_label = tk.Label(root, text="简易简谱编辑器", height=2)
        self.title_label.pack()


        # 第一个简谱输入框
        self.score_text_input1 = tk.Text(root, height=10)
        self.score_text_input1.place(x=100, y=50, width=600, height=100)
        self.score_text_input1.insert(tk.END, """在此输入简谱内容,如:-5/-,1/-,1/,1/-,3/,2/-,1/,2/-,3/,1/,1/-,1/,3/,5/,6/--,
        6/,5/,3/,3/-,1/,2/,1/,2/--,3/,1/,-6/,-6/,-5/,1/--,
        6/,5/,3/,3/-,1/,2/,1/,2/--,
        6/,5/-,3/,3/-,5/-,6/--,
        +1/,5/,3/,3/-,1/,2/,1/,2/-,
        3/,1/-,-6/,-6/-,-5/,1/--,""")
        self.score_text_input1.config(fg="gray")
        self.score_text_input1.bind("<FocusIn>", self.clear_placeholder1)
        self.score_text_input1.bind("<Button-3>", self.show_textbox_menu1)
        self.score_text_input1.pack(pady=10)


        # 第一个文本框显示/隐藏按钮
        self.toggle_button1 = tk.Button(root, text="切换文本框1显示",
                                        command=lambda: self.toggle_textbox(self.score_text_input1,
                                                                            self.toggle_button1))
        self.toggle_button1.pack(pady=5)
        self.toggle_button1.place(x=700, y=50, width=100, height=30)

        # 第一个音色选择下拉框
        self.instrument_var1 = tk.StringVar(root)
        self.instrument_var1.set("钢琴")  # 默认音色
        self.instrument_menu1 = tk.OptionMenu(root, self.instrument_var1, *self.instrument_options)
        self.instrument_menu1.pack(pady=5)
        self.instrument_menu1.place(x=300, y=150, width=100, height=50)

        # 第一个调式选择下拉框
        self.mode_var1 = tk.StringVar(root)
        self.mode_var1.set("C大调")  # 默认调式
        self.mode_menu1 = tk.OptionMenu(root, self.mode_var1, *self.mode_options)
        self.mode_menu1.pack(pady=5)
        self.mode_menu1.place(x=450, y=150, width=100, height=50)



        # 第二个简谱输入框
        self.score_text_input2 = tk.Text(root, height=10)
        self.score_text_input2.place(x=100, y=200, width=600, height=100)
        self.score_text_input2.insert(tk.END, """在此输入简谱内容,如:-5/-,1/-,1/,1/-,3/,2/-,1/,2/-,3/,1/,1/-,1/,3/,5/,6/--,
        6/,5/,3/,3/-,1/,2/,1/,2/--,3/,1/,-6/,-6/,-5/,1/--,
        6/,5/,3/,3/-,1/,2/,1/,2/--,
        6/,5/-,3/,3/-,5/-,6/--,
        +1/,5/,3/,3/-,1/,2/,1/,2/-,
        3/,1/-,-6/,-6/-,-5/,1/--,""")
        self.score_text_input2.config(fg="gray")
        self.score_text_input2.bind("<FocusIn>", self.clear_placeholder2)
        self.score_text_input2.bind("<Button-3>", self.show_textbox_menu2)
        self.score_text_input2.pack(pady=10)


        # 第二个文本框显示/隐藏按钮
        self.toggle_button2 = tk.Button(root, text="切换文本框2显示",
                                        command=lambda: self.toggle_textbox(self.score_text_input2,
                                                                            self.toggle_button2))
        self.toggle_button2.pack(pady=5)
        self.toggle_button2.place(x=700, y=200, width=100, height=30)

        # 第二个音色选择下拉框
        self.instrument_var2 = tk.StringVar(root)
        self.instrument_var2.set("钢琴")  # 默认音色
        self.instrument_menu2 = tk.OptionMenu(root, self.instrument_var2, *self.instrument_options)
        self.instrument_menu2.pack(pady=5)
        self.instrument_menu2.place(x=300, y=300, width=100, height=50)

        # 第二个调式选择下拉框
        self.mode_var2 = tk.StringVar(root)
        self.mode_var2.set("G大调")  # 默认调式
        self.mode_menu2 = tk.OptionMenu(root, self.mode_var2, *self.mode_options)
        self.mode_menu2.pack(pady=5)
        self.mode_menu2.place(x=450, y=300, width=100, height=50)

        # 第三个简谱输入框
        self.score_text_input3 = tk.Text(root, height=10)
        self.score_text_input3.pack(pady=10)
        self.score_text_input3.place(x=100, y=350, width=600, height=200)
        self.score_text_input3.insert(tk.END, """在此输入简谱内容,如:-5/-,1/-,1/,1/-,3/,2/-,1/,2/-,3/,1/,1/-,1/,3/,5/,6/--,
6/,5/,3/,3/-,1/,2/,1/,2/--,3/,1/,-6/,-6/,-5/,1/--,
6/,5/,3/,3/-,1/,2/,1/,2/--,
6/,5/-,3/,3/-,5/-,6/--,
+1/,5/,3/,3/-,1/,2/,1/,2/-,
3/,1/-,-6/,-6/-,-5/,1/--,""")
        self.score_text_input3.config(fg="gray")
        self.score_text_input3.bind("<FocusIn>", self.clear_placeholder3)
        self.score_text_input3.bind("<Button-3>", self.show_textbox_menu3)
        self.score_text_input3.pack(pady=10)



        # 第三个文本框显示/隐藏按钮
        self.toggle_button3 = tk.Button(root, text="切换文本框3显示",
                                        command=lambda: self.toggle_textbox(self.score_text_input3,
                                                                            self.toggle_button3))
        self.toggle_button3.pack(pady=5)
        self.toggle_button3.place(x=700, y=350, width=100, height=30)

        # 第三个音色选择下拉框
        self.instrument_var3 = tk.StringVar(root)
        self.instrument_var3.set("钢琴")  # 默认音色
        self.instrument_menu3 = tk.OptionMenu(root, self.instrument_var3, *self.instrument_options)
        self.instrument_menu3.pack(pady=5)
        self.instrument_menu3.place(x=300, y=450, width=100, height=50)

        # 第三个调式选择下拉框
        self.mode_var3 = tk.StringVar(root)
        self.mode_var3.set("G大调")  # 默认调式
        self.mode_menu3 = tk.OptionMenu(root, self.mode_var3, *self.mode_options)
        self.mode_menu3.pack(pady=5)
        self.mode_menu3.place(x=450, y=450, width=100, height=50)

        # 速度输入框,现在是每分钟节拍数(BPM)
        self.speed_label = tk.Label(root, text="输入播放速度(每分钟节拍数,BPM),默认 120 BPM")
        self.speed_label.pack()
        self.speed_label.place(x=500, y=500, width=300, height=40)
        self.speed_input = tk.Entry(root)
        self.speed_input.pack(pady=5)
        self.speed_input.place(x=500, y=530, width=300, height=30)

        # 拍子选择下拉框
        self.time_signature_var = tk.StringVar(root)
        self.time_signature_var.set("4/4")  # 默认拍子
        self.time_signature_menu = tk.OptionMenu(root, self.time_signature_var, *self.time_signature_options)
        self.time_signature_menu.pack(pady=5)
        self.time_signature_menu.place(x=550, y=150, width=100, height=50)

        # 第二个拍子选择下拉框
        self.time_signature_var1 = tk.StringVar(root)
        self.time_signature_var1.set("4/4")  # 默认拍子
        self.time_signature_menu1 = tk.OptionMenu(root, self.time_signature_var1, *self.time_signature_options)
        self.time_signature_menu1.pack(pady=5)
        self.time_signature_menu1.place(x=550, y=300, width=100, height=50)

        # 第三个拍子选择下拉框
        self.time_signature_var2 = tk.StringVar(root)
        self.time_signature_var2.set("4/4")  # 默认拍子
        self.time_signature_menu2 = tk.OptionMenu(root, self.time_signature_var2, *self.time_signature_options)
        self.time_signature_menu2.pack(pady=5)
        self.time_signature_menu2.place(x=550, y=450, width=100, height=50)

        # 播放按钮
        self.play_button1 = tk.Button(root, text="播放1", command=self.play_score)
        self.play_button1.pack(pady=10)
        self.play_button1.place(x=700, y=80, width=50, height=30)

        # 播放按钮
        self.play_button2 = tk.Button(root, text="播放2", command=self.play_score2)
        self.play_button2.pack(pady=10)
        self.play_button2.place(x=700, y=250, width=50, height=30)

        # 播放按钮
        self.play_button3 = tk.Button(root, text="播放3", command=self.play_score3)
        self.play_button3.pack(pady=10)
        self.play_button3.place(x=700, y=420, width=50, height=30)

        # 同时播放三个编辑框简谱的按钮
        self.play_all_button = tk.Button(root, text="同时播放三个简谱", command=self.play_all_scores)
        self.play_all_button.pack(pady=10)
        self.play_all_button.place(x=700, y=160, width=100, height=30)

        # 暂停按钮
        self.pause_button = tk.Button(root, text="暂停", command=self.pause_playback, state=tk.DISABLED)
        self.pause_button.pack(pady=10)
        self.pause_button.place(x=800, y=130, width=50, height=30)

        # 停止按钮
        self.stop_button = tk.Button(root, text="停止", command=self.stop_playback, state=tk.DISABLED)
        self.stop_button.pack(pady=10)
        self.stop_button.place(x=800, y=180, width=50, height=30)

        # 导出按钮
        self.export_button = tk.Button(root, text="导出", command=self.export_midi)
        self.export_button.pack(pady=10)
        self.export_button.place(x=700, y=320, width=50, height=30)

        # 初始化MIDI输出端口
        self.output_port = None
        try:
            self.output_port = mido.open_output()
        except (mido.NoOutputError, OSError):
            print("无法打开MIDI输出端口")

        # 播放控制标志
        self.playing = False
        self.paused = False
        self.play_thread = None

    def play_score2(self):
        self._play_helper(self.score_text_input2, self.instrument_var2, self.mode_var2, self.time_signature_var1)

    def play_score(self):
        self._play_helper(self.score_text_input1, self.instrument_var1, self.mode_var1, self.time_signature_var)

    def play_score3(self):
        self._play_helper(self.score_text_input3, self.instrument_var3, self.mode_var3, self.time_signature_var2)

    def play_all_scores(self):
        thread1 = threading.Thread(target=self.play_score)
        thread2 = threading.Thread(target=self.play_score2)
        thread3 = threading.Thread(target=self.play_score3)

        thread1.start()
        thread2.start()
        thread3.start()

    def _play_helper(self, score_text_input, instrument_var, mode_var, time_signature_var):
        if self.output_port:
            self.playing = True
            self.paused = False
            self.pause_button.config(state=tk.NORMAL)
            self.stop_button.config(state=tk.NORMAL)
            score = score_text_input.get("1.0", tk.END).strip()
            try:
                bpm = float(self.speed_input.get()) if self.speed_input.get() else 120
                # 将 BPM 转换为四分音符时值(秒)
                quarter_note_duration = 60 / bpm
            except ValueError:
                print("输入的播放速度不是有效的数字,使用默认值 120 BPM")
                quarter_note_duration = 60 / 120

            # 获取拍子信息
            time_signature = time_signature_var.get()
            beats_per_measure, beat_type = map(int, time_signature.split('/'))

            # 设置音色
            instrument_name = instrument_var.get()
            instrument_number = self.get_instrument_number(instrument_name)
            self.set_instrument(instrument_number)

            # 设置调式
            mode = mode_var.get()
            transpose = self.get_transpose(mode)

            self.play_thread = threading.Thread(target=self._play_single_score, args=(
                score, quarter_note_duration, transpose, beats_per_measure, beat_type))
            self.play_thread.start()

    def _play_single_score(self, score, quarter_note_duration, transpose, beats_per_measure, beat_type):
        i = 0
        beat_count = 0
        while i < len(score) and self.playing:
            while self.paused:
                time.sleep(0.1)
            note, duration = self.parse_note(score, i, quarter_note_duration)
            if note is not None:
                midi_note = self.note_to_midi(note)
                if midi_note is not None:
                    # 根据调式进行转换
                    midi_note += transpose
                    # 发送音符开启消息
                    msg_on = Message('note_on', note=midi_note, velocity=64, time=0)
                    self.output_port.send(msg_on)
                    print(f"发送音符开启消息: {midi_note}")
                    start_time = time.time()
                    while time.time() - start_time < duration and self.playing:
                        if self.paused:
                            time.sleep(0.1)
                        else:
                            time.sleep(0.01)
                    # 发送音符关闭消息
                    if self.playing:
                        msg_off = Message('note_off', note=midi_note, velocity=64, time=0)
                        self.output_port.send(msg_off)
                        print(f"发送音符关闭消息: {midi_note},持续时长: {duration} 秒")
                beat_count += duration / quarter_note_duration
                if beat_count >= beats_per_measure:
                    # 新的一小节开始
                    beat_count = 0
            i += len(note) if note else 1
        if not self.playing:
            self._stop_all_notes()
        self.playing = False
        self.pause_button.config(state=tk.DISABLED)
        self.stop_button.config(state=tk.DISABLED)

    def pause_playback(self):
        if self.playing:
            self.paused = not self.paused
            if self.paused:
                self.pause_button.config(text="继续")
            else:
                self.pause_button.config(text="暂停")

    def stop_playback(self):
        self.playing = False
        self.paused = False
        self.pause_button.config(state=tk.DISABLED, text="暂停")
        self.stop_button.config(state=tk.DISABLED)
        self._stop_all_notes()

    def _stop_all_notes(self):
        if self.output_port:
            for note in range(128):
                msg_off = Message('note_off', note=note, velocity=64, time=0)
                self.output_port.send(msg_off)

    def parse_note(self, score, start_index, default_duration):
        note = ""
        duration = default_duration
        i = start_index
        # 解析音符部分(包含 + - #! 符号)
        while i < len(score) and (score[i].isdigit() or score[i] in ('+', '-', '#', '!')):
            note += score[i]
            i += 1

        # 处理斜杠,改变时值
        slash_count = 0
        while i < len(score) and score[i] == '/':
            slash_count += 1
            i += 1
        if slash_count > 0:
            duration /= 2 ** slash_count

        # 处理延音线
        dash_count = 0
        while i < len(score) and score[i] == '-':
            dash_count += 1
            i += 1
        if dash_count > 0:
            duration *= (dash_count + 1)

        # 处理附点音符
        dot_count = 0
        while i < len(score) and score[i] == '.':
            dot_count += 1
            i += 1
        for _ in range(dot_count):
            duration += duration / 2

        # 跳过逗号
        if i < len(score) and score[i] == ',':
            i += 1

        return note, duration

    def note_to_midi(self, note):
        base_notes = {
            "1": 60, "2": 62, "3": 64, "4": 65, "5": 67, "6": 69, "7": 71,
            "0": None  # 表示休止符
        }
        modifier = 0
        base_note_str = note
        if note.startswith(('+', '-', '#', '!')):
            if note.startswith('+'):
                modifier = 12
                base_note_str = note[1:]
            elif note.startswith('-'):
                modifier = -12
                base_note_str = note[1:]
            elif note.startswith('#'):
                modifier = 1
                base_note_str = note[1:]
            elif note.startswith('!'):
                modifier = -1
                base_note_str = note[1:]

        base_midi = base_notes.get(base_note_str)
        if base_midi is not None:
            return base_midi + modifier
        return None

    def show_textbox_menu1(self, event):
        menu = tk.Menu(self.root, tearoff=0)
        menu.add_command(label="剪切", command=lambda: self.score_text_input1.event_generate("<<Cut>>"))
        menu.add_command(label="复制", command=lambda: self.score_text_input1.event_generate("<<Copy>>"))
        menu.add_command(label="粘贴", command=lambda: self.score_text_input1.event_generate("<<Paste>>"))
        menu.add_separator()
        menu.add_command(label="全选", command=lambda: self.score_text_input1.event_generate("<<SelectAll>>"))
        menu.post(event.x_root, event.y_root)

    def show_textbox_menu2(self, event):
        menu = tk.Menu(self.root, tearoff=0)
        menu.add_command(label="剪切", command=lambda: self.score_text_input2.event_generate("<<Cut>>"))
        menu.add_command(label="复制", command=lambda: self.score_text_input2.event_generate("<<Copy>>"))
        menu.add_command(label="粘贴", command=lambda: self.score_text_input2.event_generate("<<Paste>>"))
        menu.add_separator()
        menu.add_command(label="全选", command=lambda: self.score_text_input2.event_generate("<<SelectAll>>"))
        menu.post(event.x_root, event.y_root)

    def show_textbox_menu3(self, event):
        menu = tk.Menu(self.root, tearoff=0)
        menu.add_command(label="剪切", command=lambda: self.score_text_input3.event_generate("<<Cut>>"))
        menu.add_command(label="复制", command=lambda: self.score_text_input3.event_generate("<<Copy>>"))
        menu.add_command(label="粘贴", command=lambda: self.score_text_input3.event_generate("<<Paste>>"))
        menu.add_separator()
        menu.add_command(label="全选", command=lambda: self.score_text_input3.event_generate("<<SelectAll>>"))
        menu.post(event.x_root, event.y_root)

    def clear_placeholder1(self, event):
        if self.score_text_input1.cget("fg") == "gray":
            self.score_text_input1.delete("1.0", tk.END)
            self.score_text_input1.config(fg="black")

    def clear_placeholder2(self, event):
        if self.score_text_input2.cget("fg") == "gray":
            self.score_text_input2.delete("1.0", tk.END)
            self.score_text_input2.config(fg="black")

    def clear_placeholder3(self, event):
        if self.score_text_input3.cget("fg") == "gray":
            self.score_text_input3.delete("1.0", tk.END)
            self.score_text_input3.config(fg="black")

    def toggle_textbox(self, textbox, button):
        if textbox.winfo_ismapped():
            textbox.pack_forget()
            button.config(text=f"显示文本框{button.cget('text')[-1]}")
        else:
            textbox.pack()
            button.config(text=f"切换文本框{button.cget('text')[-1]}显示")

    def get_instrument_number(self, instrument_name):
        # 这里简单映射,实际可能需要更复杂的映射
        if instrument_name == "大钢琴(声学钢琴)":
            return 0
        elif instrument_name == "明亮的钢琴":
            return 1
        elif instrument_name == "电钢琴":
            return 2

        return 0

    def set_instrument(self, instrument_number):
        # 发送程序改变消息来设置乐器
        msg = Message('program_change', program=instrument_number, time=0)
        self.output_port.send(msg)

    def get_transpose(self, mode):
        # 调式转换表
        mode_transpose = {
            "C大调": 0,
            "G大调": 7,
            "D大调": 2,
            "A大调": 9,
            "E大调": 4,
            "B大调": 11,
            "F大调": -5
        }
        return mode_transpose.get(mode, 0)

    def export_midi(self):
        # 打开文件保存对话框
        file_path = filedialog.asksaveasfilename(defaultextension=".mid", filetypes=[("MIDI Files", "*.mid")])
        if file_path:
            try:
                # 创建一个新的 MIDI 文件
                mid = mido.MidiFile()

                # 获取速度
                try:
                    bpm = float(self.speed_input.get()) if self.speed_input.get() else 120
                except ValueError:
                    print("输入的播放速度不是有效的数字,使用默认值 120 BPM")
                    bpm = 120
                tempo = mido.bpm2tempo(bpm)

                # 处理第一个简谱输入框
                score1 = self.score_text_input1.get("1.0", tk.END).strip()
                track1 = mido.MidiTrack()
                mid.tracks.append(track1)

                # 设置速度
                track1.append(mido.MetaMessage('set_tempo', tempo=tempo))

                # 设置拍子
                time_signature = self.time_signature_var.get()
                numerator, denominator = map(int, time_signature.split('/'))
                track1.append(mido.MetaMessage('time_signature', numerator=numerator, denominator=denominator))

                # 设置音色
                instrument_name1 = self.instrument_var1.get()
                instrument_number1 = self.get_instrument_number(instrument_name1)
                track1.append(Message('program_change', program=instrument_number1, time=0))

                # 设置调式
                mode1 = self.mode_var1.get()
                transpose1 = self.get_transpose(mode1)

                self._add_notes_to_track(track1, score1, transpose1, bpm)

                # 处理第二个简谱输入框
                score2 = self.score_text_input2.get("1.0", tk.END).strip()
                track2 = mido.MidiTrack()
                mid.tracks.append(track2)

                # 设置速度
                track2.append(mido.MetaMessage('set_tempo', tempo=tempo))

                # 设置拍子
                time_signature = self.time_signature_var1.get()
                numerator, denominator = map(int, time_signature.split('/'))
                track2.append(mido.MetaMessage('time_signature', numerator=numerator, denominator=denominator))

                # 设置音色
                instrument_name2 = self.instrument_var2.get()
                instrument_number2 = self.get_instrument_number(instrument_name2)
                track2.append(Message('program_change', program=instrument_number2, time=0))

                # 设置调式
                mode2 = self.mode_var2.get()
                transpose2 = self.get_transpose(mode2)

                self._add_notes_to_track(track2, score2, transpose2, bpm)

                # 处理第三个简谱输入框
                score3 = self.score_text_input3.get("1.0", tk.END).strip()
                track3 = mido.MidiTrack()
                mid.tracks.append(track3)

                # 设置速度
                track3.append(mido.MetaMessage('set_tempo', tempo=tempo))

                # 设置拍子
                time_signature = self.time_signature_var2.get()
                numerator, denominator = map(int, time_signature.split('/'))
                track3.append(mido.MetaMessage('time_signature', numerator=numerator, denominator=denominator))

                # 设置音色
                instrument_name3 = self.instrument_var3.get()
                instrument_number3 = self.get_instrument_number(instrument_name3)
                track3.append(Message('program_change', program=instrument_number3, time=0))

                # 设置调式
                mode3 = self.mode_var3.get()
                transpose3 = self.get_transpose(mode3)

                self._add_notes_to_track(track3, score3, transpose3, bpm)

                # 保存 MIDI 文件
                mid.save(file_path)
                print(f"MIDI 文件已保存到 {file_path}")
            except Exception as e:
                print(f"保存 MIDI 文件时出错: {e}")

    def _add_notes_to_track(self, track, score, transpose, bpm):
        quarter_note_duration = 60 / bpm
        i = 0
        while i < len(score):
            note, duration = self.parse_note(score, i, quarter_note_duration)
            if note is not None:
                midi_note = self.note_to_midi(note)
                if midi_note is not None:
                    # 根据调式进行转换
                    midi_note += transpose
                    # 发送音符开启消息
                    msg_on = mido.Message('note_on', note=midi_note, velocity=64, time=0)
                    track.append(msg_on)
                    # 发送音符关闭消息
                    msg_off = mido.Message('note_off', note=midi_note, velocity=64, time=int(duration * 1000))
                    track.append(msg_off)
            i += len(note) if note else 1


if __name__ == "__main__":
    root = tk.Tk()
    app = SimpleScoreEditor(root)
    root.mainloop()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

EasySoft易软

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

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

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

打赏作者

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

抵扣说明:

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

余额充值