当前位置: 首页 > news >正文

MIDI简谱播放器1.1程序代码QZQ-2025-8-20

```python
# pip install pygame mido
# pip install pyinstaller
# pyinstaller --onefile main.py
# pyinstaller --onefile --icon=1.ico main.py


import random
import tkinter as tk
from tkinter import filedialog, messagebox # 导入messagebox模块
import pygame.midi
import time
import threading
import mido # 导入mido库用于创建和保存MIDI文件

# 初始化 pygame.midi
pygame.midi.init()

# 获取 MIDI 输出设备
try:
midi_out = pygame.midi.Output(0)
except pygame.midi.MidiException:
print("无法打开MIDI输出设备,程序将使用默认设置")
midi_out = None

# 乐器名称映射
instrument_names = {
0: "大钢琴(声学钢琴)",
1: "明亮的钢琴",
2: "电钢琴",
}

# 播放控制标志
is_playing = False
is_paused = False
pause_start_time = 0
current_note_index = 0
all_notes = []
loop_playback = False # 新增:循环播放标志

text_boxes = []
play_buttons = []
track_settings = [] # 存储每个音轨的设置(调式、乐器、速度)
threads = [] # 存储所有线程

# 记录当前显示的文本框索引
current_displayed_index = 0

# 音量设置,初始值为127
volume = 127


def play_note(note_number, duration, instrument, velocity):
global is_playing, is_paused, pause_start_time, midi_out
if midi_out:
midi_out.set_instrument(instrument)
midi_out.note_on(note_number, velocity)
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, velocity)
return
midi_out.note_off(note_number, velocity)


def play_next_note(notes, instrument, tune, speed):
global is_playing, loop_playback
while is_playing:
index = 0
while is_playing and index < len(notes):
note = notes[index]
note_number, duration, velocity = process_note(note, speed)
note_number = convert_tune(note_number, tune)
if note_number is not None:
play_note(note_number, duration, instrument, velocity)
index += 1

# 如果不循环播放,则退出循环
if not loop_playback:
break


def play_all_tracks_together():
global is_playing, threads, loop_playback
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("MIDI简谱播放器 V1.1")
root.geometry("900x800")

# 创建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 大调,40,200\n5-351*--76-1*-5---5-123-212----5-351*--76-1*-5---5-234--7.1----6-1*-1*---7-671*---671*665312----5-351*--76-1*-5---5-234--7.1------")

# 第3个速度设置,数量越少速度越快。

# 设置默认显示第一个音轨文本框的内容
if i == 10:
text_box.insert(tk.END,
"A 大调,10,400\n5-351*--76-1*-5---5-123-212----5-351*--76-1*-5---5-234--7.1----6-1*-1*---7-671*---671*665312----5-351*--76-1*-5---5-234--7.1------")

# 第3个速度设置,数量越少速度越快。

# 设置默认显示第一个音轨文本框的内容
if i == 15:
text_box.insert(tk.END,
"C 大调,0,600\n5-351*--76-1*-5---5-123-212----5-351*--76-1*-5---5-234--7.1----6-1*-1*---7-671*---671*665312----5-351*--76-1*-5---5-234--7.1------")


# 第3个速度设置,数量越少速度越快。

# 创建文本框的右键菜单
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.add_command(
label="全选复制",
command=lambda: (
event.widget.tag_add("sel", "1.0", "end"), # 全选文本
event.widget.event_generate("<<Copy>>") # 触发复制
)
)

# 新增:全选删除功能(直接删除所有文本)
menu.add_separator() # 分隔线优化菜单布局
menu.add_command(
label="全选删除",
command=lambda: event.widget.delete("1.0", "end")
)

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, loop_playback
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 in '1234567':
if current_note: # 遇到新音符,保存前一个音符
notes.append(current_note)
current_note = char # 开始新音符
elif char in '-.*': # 时值、低音、高音标记
current_note += char
# 忽略其他字符

if current_note: # 添加最后一个音符
notes.append(current_note)

return notes, instrument, speed, tune


def process_note(note_str, base_duration):
if not note_str or note_str[0] not in '1234567':
return None, base_duration, 127 # 休止符或无效音符

# 基本音符映射 (C大调中央C=60)
note_mapping = {'1': 60, '2': 62, '3': 64, '4': 65, '5': 67, '6': 69, '7': 71}

# 计算八度偏移(支持任意数量的.和*)
octave_offset = (note_str.count('*') - note_str.count('.')) * 12

# 计算时值(每个-使时值翻倍)
duration = base_duration * (1.5 ** note_str.count('-'))

# 获取最终音符编号(限制在MIDI范围0-127内)
note_number = max(0, min(127, note_mapping[note_str[0]] + octave_offset))

return note_number, duration, 127 # 固定力度127


def convert_tune(note_number, tune):
if note_number is None:
return None
if tune == "C 大调":
return note_number
elif tune == "D 大调":
return note_number + 2
elif tune == "E 大调":
return note_number + 4
elif tune == "F 大调":
return note_number + 5
elif tune == "G 大调":
return note_number + 7
elif tune == "A 大调":
return note_number + 9
elif tune == "B 大调":
return note_number + 11
return note_number


def export_midi():
try:
file_path = filedialog.asksaveasfilename(defaultextension=".mid", filetypes=[("MIDI Files", "*.mid")])
if not file_path:
return

mid = mido.MidiFile(ticks_per_beat=480)
all_tracks = []
max_track_length = 0

for i in range(16):
notes_str = text_boxes[i].get("1.0", "end-1c")
if not notes_str.strip():
continue

notes, instrument, speed, tune = parse_notes_and_settings(notes_str)
if not notes:
continue

track_events = []
current_time = 0

# 为每个音轨添加Program Change消息,指定乐器
program_msg = mido.Message('program_change', program=instrument, channel=i, time=0)
track_events.append((0, program_msg))

for note in notes:
note_number, duration, velocity = process_note(note, speed)
note_number = convert_tune(note_number, tune)

bpm = 120
ms_per_beat = 60000 / bpm
ms_per_tick = ms_per_beat / mid.ticks_per_beat
tick_duration = int(duration / ms_per_tick)

if note_number is not None:
track_events.append(
(current_time, mido.Message('note_on', note=note_number, velocity=velocity, channel=i)))
track_events.append((current_time + tick_duration,
mido.Message('note_off', note=note_number, velocity=velocity, channel=i)))
current_time += tick_duration

if track_events:
all_tracks.append((instrument, tune, track_events))
max_track_length = max(max_track_length, current_time)

if not all_tracks:
messagebox.showinfo("导出提示", "没有可导出的音轨")
return

for i, (instrument, tune, track_events) in enumerate(all_tracks):
track = mido.MidiTrack()
mid.tracks.append(track)

track_name = f'Track {i + 1} - {tune}'
name_bytes = track_name.encode('utf-8')
track.append(mido.MetaMessage('track_name', name=name_bytes.decode('latin-1', 'replace')))

track_events.sort(key=lambda x: x[0])
prev_time = 0

for time_ticks, event in track_events:
delta = time_ticks - prev_time
track.append(event.copy(time=delta))
prev_time = time_ticks

mid.save(file_path)
messagebox.showinfo("导出成功", f"文件已成功导出到 {file_path}")

except Exception as e:
messagebox.showerror("导出错误", f"导出过程中出现错误: {str(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


# 定义音符列表
z = ["1..", "2..", "3..", "4..", "5..", "6..", "7..", "1.", "2.", "3.", "4.", "5.", "6.", "7.", "1", "2", "3", "4", "5",
"6", "7", "1*", "2*", "3*", "4*", "5*", "6*", "7*", "1**", "2**", "3**", "4**", "5**", "6**", "7**"]
# 定义音符列表
a = ["", "-", "--"]


# 定义生成随机音符的函数
def generate_random_notes():
random_notes = []
for _ in range(110):
random_note = random.choice(z) + random.choice(a)
random_notes.append(random_note)

# 清空文本框3并插入随机生成的音符
text_boxes[2].delete("1.0", tk.END) # 清空文本框3
text_boxes[2].insert(tk.END, "G 大调,0,500\n" + "".join(random_notes)) # 将随机音符插入文本框3


# 创建生成随机音符的按钮
generate_button = tk.Button(root, text="随机谱曲音轨3", command=generate_random_notes)
generate_button.pack(pady=20)
generate_button.place(x=700, y=80, width=100, height=30)

# 定义音符列表
z1 = ["1..", "2..", "3..", "4..", "5..", "6..", "7..", "1.", "2.", "3.", "4.", "5.", "6.", "7.", "1", "2", "3", "4",
"5", "6", "7", "1*", "2*", "3*", "4*", "5*", "6*", "7*", "1**", "2**", "3**", "4**", "5**", "6**", "7**"]
# 定义音符列表
a1 = ["", "-", "--"]


# 定义生成随机音符的函数
def generate_random_notes1():
random_notes = []
for _ in range(110):
random_note = random.choice(z1) + random.choice(a1)
random_notes.append(random_note)

# 清空文本框4并插入随机生成的音符
text_boxes[3].delete("1.0", tk.END) # 清空文本框4
text_boxes[3].insert(tk.END, "G 大调,0,500\n" + "".join(random_notes)) # 将随机音符插入文本框3


# 创建生成随机音符的按钮
generate_button = tk.Button(root, text="随机谱曲音轨4", command=generate_random_notes1)
generate_button.pack(pady=20)
generate_button.place(x=700, y=110, width=100, height=30)

# 定义音符列表
z2 = ["1..", "2..", "3..", "4..", "5..", "6..", "7..", "1.", "2.", "3.", "4.", "5.", "6.", "7.", "1", "2", "3", "4",
"5", "6", "7", "1*", "2*", "3*", "4*", "5*", "6*", "7*", "1**", "2**", "3**", "4**", "5**", "6**", "7**"]
# 定义音符列表
a2 = ["", "-", "--"]


# 定义生成随机音符的函数
def generate_random_notes2():
random_notes = []
for _ in range(110):
random_note = random.choice(z2) + random.choice(a2)
random_notes.append(random_note)

# 清空文本框5并插入随机生成的音符
text_boxes[4].delete("1.0", tk.END) # 清空文本框5
text_boxes[4].insert(tk.END, "G 大调,0,500\n" + "".join(random_notes)) # 将随机音符插入文本框3


# 创建生成随机音符的按钮
generate_button = tk.Button(root, text="随机谱曲音轨5", command=generate_random_notes2)
generate_button.pack(pady=20)
generate_button.place(x=700, y=140, width=100, height=30)


def clear_all_textboxes():
for text_box in text_boxes:
text_box.delete("1.0", tk.END)


# 创建清空所有文本框的按钮
clear_button = tk.Button(root, text="清空所有", command=clear_all_textboxes)
clear_button.pack(pady=20)
clear_button.place(x=550, y=210, width=100, height=50)

# 创建暂停按钮
pause_button = tk.Button(root, text="暂停", command=pause_music)
pause_button.pack(pady=20)
pause_button.place(x=550, y=60, width=100, height=50)

# 创建停止按钮
stop_button = tk.Button(root, text="停止", command=stop_music)
stop_button.pack(pady=20)
stop_button.place(x=550, y=110, width=100, height=50)

# 创建导出按钮
export_button = tk.Button(root, text="导出", command=export_midi)
export_button.pack(pady=20)
export_button.place(x=550, y=160, 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=10, width=100, height=50)


# 新增:循环播放复选框
def toggle_loop():
global loop_playback
loop_playback = loop_var.get()


loop_var = tk.BooleanVar()
loop_checkbox = tk.Checkbutton(root, text="循环播放", variable=loop_var, command=toggle_loop)
loop_checkbox.pack(pady=5)
loop_checkbox.place(x=700, y=10, width=100, height=30)

# 初始显示第一个文本框
display_textbox(0)


def on_closing():
global midi_out
if midi_out:
midi_out.close()
pygame.midi.quit()
root.destroy()


root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()


```

 


源代码下载地址:[https://download.csdn.net/download/qq_32257509/91715331](https://download.csdn.net/download/qq_32257509/91715331)

 

 

软件项目下载地址:[https://download.csdn.net/download/qq_32257509/91715332](https://download.csdn.net/download/qq_32257509/91715332)

 


开源项目通过网盘分享的文件:MIDI简谱播放器V1.1.zip
链接: https://pan.baidu.com/s/15g6wZTeuEZtX3Kbt57uK8g?pwd=y9x7 提取码: y9x7

 

开源项目通过网盘分享的文件:MIDI简谱播放器V1.1软件.zip
链接: https://pan.baidu.com/s/1a-CuhWMThvvOe12ySkxRcw?pwd=hfxh 提取码: hfxh

 

开源项目通过网盘分享的文件:MIDI简谱播放器 V1.1.exe
链接: https://pan.baidu.com/s/1YB6sRbVBrm3WoDMGourpRA?pwd=urap 提取码: urap

 

http://www.agseo.cn/news/485/

相关文章:

  • python语言网页版MIDI钢琴软件代码QZQ
  • 【2024-2025第二学期】助教工作学期总结(算法与数据结构)
  • p型编码
  • 赣江游记
  • OTA 升级问题的分析
  • 初识Dataset
  • Day15可变参数
  • Nacos
  • 单词的长度
  • Python模块之 subprocess 具有可访问I/O流的子流程 子进程管理
  • 因爱而……(和谐版)
  • 初探CTF
  • P3195 [HNOI2008] 玩具装箱
  • Python模块之execjs
  • 模拟题
  • 软工第一次作业-自我介绍
  • 111
  • Vibe Coding,这种技术面试形式会成为新的趋势吗?
  • qt之捕获键盘组合键事件
  • ???记录?
  • LIN 的调度表周期和应用任务周期不一致的问题分析
  • 自我介绍与软工五问
  • 关于我的大三生活
  • CSP 赛前周记#2
  • 建立本地仓库
  • 厨房小白学做饭——2.苦瓜炒蛋
  • DAY2
  • Go
  • Discipline
  • 长乐一中 CSP-S 2025 提高级模拟赛 Day1