import numpy as np
import pygame
import tkinter as tk
from tkinter import messagebox
from tkinter import Menu
import random # 添加随机模块导入
# 初始化音频系统
pygame.init()
pygame.mixer.init()
# 标准简谱音符频率(修正版,注意键名格式统一)
NOTES = {
'1..': 130.81, '2..': 146.83, '3..': 164.81, '4..': 174.61, '5..': 196.00, '6..': 220.00, '7..': 246.94,
'1.': 261.63, '2.': 293.66, '3.': 329.63, '4.': 349.23, '5.': 392.00, '6.': 440.00, '7.': 493.88,
'1': 523.25, '2': 587.33, '3': 659.25, '4': 698.46, '5': 783.99, '6': 880.00, '7': 987.77,
'1*': 1046.50, '2*': 1174.66, '3*': 1318.51, '4*': 1396.91, '5*': 1567.98, '6*': 1760.00, '7*': 1975.53, # 修正高音区键名
'1**': 2093.00, '2**': 2349.32, '3**': 2637.02, '4**': 2793.83, '5**': 3135.96, '6**': 3520.00, '7**': 3951.07, # 修正倍高音键名
'-': 0 # 休止符(连写表示时长)
}
# 以下代码与原代码完全相同,仅修正了NOTES字典中的高音键名格式
# 默认简谱(无空格,含换行)
DEFAULT_SCORE = """-1**1**1**2**2**1**7*6*5*5*6*5*3*---1**1**1**2**2**1**7*6*5*5*6*5*6*---1*-1*1*1*2*2*--1*765-56563-----1*-1*1*1*2*2*--1*765-56566-----1**-1**1**1**2**2**-2**1**7*6*5*-5*6*5*6*3*-2*-1*-1**-1**1**1**2**2**-2**1**7*6*5*-5*6*5*6*6*------3*-61*3*2*3*-61*3*2*3*-61*4*3*4*-61*4*3*4*-4*3*4*4*5*-5*6*5*6*3*-----3**-6*1**3**2**3**-6*1**3**2**3**-6*1**4**3**4**-6*1**4**3**4**-4**3**4**4**5**-5**6**5**6**3**--3**-6*1**3**2**3**-6*1**3**2**3**-6*1**4**3**4**-6*1**4**3**4**-4**3**4**4**5**-5**6**5**6**3**---"""
DEFAULT_SPEED = 1.0 # 默认速度
def generate_sound(freq, duration=0.3):
if freq == 0: return None
t = np.linspace(0, duration, int(44100*duration))
mono = np.sin(2*np.pi*freq*t)
stereo = np.column_stack((mono, mono))
return pygame.sndarray.make_sound((stereo*32767).astype(np.int16))
def parse_score(score_text):
notes = []
i = 0
n = len(score_text)
while i < n:
char = score_text[i]
if char == '-':
# 纯休止符(直接处理)
start = i
while i < n and score_text[i] == '-':
i += 1
notes.append('-' * (i - start))
else:
# 处理音符(可能后跟休止符)
if char in '1234567':
# 提取完整音符(包含./*标记)
start = i
if i + 1 < n and score_text[i+1] in ('.', '*'):
i += 1
if i + 1 < n and score_text[i+1] == '*': # 处理**
i += 1
note = score_text[start:i+1] # 包含当前字符
notes.append(note)
i += 1 # 移动到音符末尾
# 处理音符后的休止符(可能多个-)
while i < n and score_text[i] == '-':
start_rest = i
while i < n and score_text[i] == '-':
i += 1
notes.append('-' * (i - start_rest))
else:
i += 1 # 跳过无效字符
return notes
def play_sequence():
try:
# 获取文本框速度值,默认使用DEFAULT_SPEED
speed = float(speed_entry.get()) if speed_entry.get() else DEFAULT_SPEED
score_text = entry.get("1.0", tk.END).strip() or DEFAULT_SCORE
score_text = ''.join([c for c in score_text if c in '1234567.*-'])
notes = parse_score(score_text)
valid_notes = []
for note in notes:
if note.startswith('-'):
valid_notes.append(note)
else:
note_upper = note.upper() # 统一转为大写(原代码已处理,此处保留)
if note_upper in NOTES:
valid_notes.append(note_upper)
else:
print(f"无效音符: {note}")
if not valid_notes:
messagebox.showwarning("提示", "无有效音符!")
return
duration_base = 0.3 * speed # 根据速度调整时长
for note in valid_notes:
if note.startswith('-'):
length = len(note)
pygame.time.delay(int(length * duration_base * 1000))
else:
freq = NOTES[note]
sound = generate_sound(freq, duration_base)
if sound:
sound.play()
pygame.time.delay(int(duration_base * 1000) + 20) # 微小延迟防重叠
except ValueError:
messagebox.showerror("错误", "速度值必须为数字(如0.5-2.0)")
except Exception as e:
messagebox.showerror("错误", f"播放失败:{str(e)}")
def create_text_right_click_menu(text_widget):
"""创建文本框右键菜单"""
right_click_menu = Menu(root, tearoff=0)
right_click_menu.add_command(label="全选", command=lambda: text_widget.tag_add(tk.SEL, "1.0", tk.END))
right_click_menu.add_command(label="复制", command=lambda: text_widget.event_generate("<<Copy>>"))
right_click_menu.add_command(label="粘贴", command=lambda: text_widget.event_generate("<<Paste>>"))
right_click_menu.add_command(label="剪切", command=lambda: text_widget.event_generate("<<Cut>>"))
right_click_menu.add_separator()
right_click_menu.add_command(label="删除", command=lambda: text_widget.delete(tk.SEL_FIRST, tk.SEL_LAST))
def show_menu(event):
try:
right_click_menu.tk_popup(event.x_root, event.y_root)
finally:
right_click_menu.grab_release()
text_widget.bind("<Button-3>", show_menu) # 绑定右键事件(Windows/Linux)
text_widget.bind("<Button-2>", show_menu) # 绑定中键事件(macOS可能需要)
# 创建界面
root = tk.Tk()
root.title("简谱播放器(文本框速度输入)")
root.geometry("800x500")
# -------------------- 速度控制文本框 --------------------
speed_frame = tk.Frame(root)
speed_frame.pack(pady=60, padx=20, fill=tk.X)
speed_label = tk.Label(speed_frame, text="播放速度(0.5-2.0倍):")
speed_label.pack(side=tk.LEFT, padx=5)
# 创建速度输入文本框,设置默认值
speed_entry = tk.Entry(speed_frame, font=('Arial', 10), width=8)
speed_entry.insert(0, str(DEFAULT_SPEED)) # 设置默认速度
speed_entry.pack(side=tk.LEFT, padx=5)
speed_hint = tk.Label(speed_frame, text="如:1.0为正常速度, 0.5为快2倍")
speed_hint.pack(side=tk.LEFT, padx=10)
# -------------------- 多行文本框(带右键菜单) --------------------
label = tk.Label(root, text="输入简谱(无空格,-表示休止符,.低音*高音,支持右键菜单):")
label.pack(pady=10)
entry = tk.Text(root, font=('Arial', 12), width=60, height=12, wrap=tk.WORD) # 调整宽度和高度
entry.pack(pady=10, padx=20)
entry.insert(tk.END, DEFAULT_SCORE) # 设置默认简谱
# 定义音符列表(仅包含纯音符,不含休止符)
a = ["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**"]
# 休止符列表(单独生成,可附加在音符后)
rests = ["", "-", "--", "---"] # "" 表示无休止符,其他为1-3拍休止
def generate_random_notes():
random_notes = []
for _ in range(110):
# 随机生成音符 + 随机休止符(允许混合)
note = random.choice(a)
rest = random.choice(rests) # 可能为空(无休止符)
random_notes.append(note + rest)
entry.delete("1.0", tk.END)
entry.insert(tk.END, "".join(random_notes))
# 创建随机谱曲按钮(此时函数已定义)
generate_button = tk.Button(root, text="随机谱曲", command=generate_random_notes)
generate_button.pack(pady=25, padx=20) # 靠右放置
# 绑定右键菜单
create_text_right_click_menu(entry)
# -------------------- 播放按钮 --------------------
play_btn = tk.Button(root, text="开始播放", command=play_sequence,
font=('Arial', 12), bg="#4CAF50", fg="white", width=20)
play_btn.pack(pady=80)
root.mainloop()
pygame.quit()
手机端Python语言Beep简谱播放器带随机谱曲功能程序代码QZQ-2025-5-23
于 2025-05-23 06:25:01 首次发布