手机端Python多波形频率蜂鸣简谱播放器带随机谱曲功能程序代码新


import numpy as np
import pygame
import tkinter as tk
from tkinter import messagebox, Menu, ttk
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         # 休止符(连写表示时长)
}

# 支持的波形类型及生成函数(与音符格式无关,仅处理频率)
WAVEFORMS = {
    '正弦波': lambda t, f: np.sin(2 * np.pi * f * t),
    '三角波': lambda t, f: 2 * np.abs(2 * (f * t - np.floor(f * t + 0.5)) - 1) - 1,
    '方波': lambda t, f: np.sign(np.sin(2 * np.pi * f * t)),
    '锯齿波': lambda t, f: 2 * (f * t - np.floor(f * t + 0.5))
}

DEFAULT_SCORE = "5-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------"  # 示例简谱(低音6、5、3接高音2、3、5、6)
DEFAULT_SPEED = 0.5
DEFAULT_WAVEFORM = '正弦波'

def generate_wave_sound(freq, duration, waveform):
    if freq == 0:
        return None
    sample_rate = 44100
    t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
    wave = WAVEFORMS[waveform](t, freq)
    stereo = np.column_stack((wave, wave))  # 立体声
    return pygame.sndarray.make_sound((stereo * 32767).astype(np.int16))

def parse_score(score_text):
    """解析符合 NOTES 键名格式的简谱文本"""
    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))
        elif char in '1234567':
            # 解析音符(支持 . 和 * 后缀,包括 **)
            start = i
            if i + 1 < n and score_text[i+1] in ('.', '*'):
                i += 1
                if score_text[i] == '*' and i + 1 < n and score_text[i+1] == '*':  # 处理 **
                    i += 1
            note = score_text[start:i+1]  # 提取完整音符(如 1**, 2., 3* 等)
            notes.append(note.upper())  # 转换为大写以匹配 NOTES 键名
            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:
        speed = float(speed_entry.get()) if speed_entry.get() else DEFAULT_SPEED
        waveform = waveform_combobox.get()
        score_text = entry.get("1.0", tk.END).strip() or DEFAULT_SCORE
        notes = parse_score(score_text)
        
        valid_notes = [note for note in notes if note in NOTES or note.startswith('-')]
        
        if not valid_notes:
            messagebox.showwarning("提示", "无有效音符或休止符!")
            return
        
        duration_base = 0.3 * speed  # 基础时长(可通过修改0.3调整整体音符长度)
        
        for note in valid_notes:
            if note.startswith('-'):
                # 休止符:每个-对应 duration_base 秒
                length = len(note)
                pygame.time.delay(int(length * duration_base * 1000))
            else:
                freq = NOTES[note]
                sound = generate_wave_sound(freq, duration_base, waveform)
                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("<>"))
    right_click_menu.add_command(label="粘贴", command=lambda: text_widget.event_generate("<>"))
    right_click_menu.add_command(label="剪切", command=lambda: text_widget.event_generate("<>"))
    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("", show_menu)  # Windows/Linux 右键
    text_widget.bind("", show_menu)  # macOS 可能需要中键

# 创建主窗口
root = tk.Tk()
root.title("简谱播放器(支持自定义音符格式)")
root.geometry("800x550")

# -------------------- 波形选择组件 --------------------
waveform_frame = tk.Frame(root)
waveform_frame.pack(pady=15, padx=20, fill=tk.X)

waveform_label = tk.Label(waveform_frame, text="选择波形:")
waveform_label.pack(side=tk.LEFT, padx=5)

waveform_combobox = ttk.Combobox(waveform_frame, values=list(WAVEFORMS.keys()), width=12)
waveform_combobox.set(DEFAULT_WAVEFORM)
waveform_combobox.pack(side=tk.LEFT, padx=5)

# -------------------- 速度调节组件 --------------------
speed_frame = tk.Frame(root)
speed_frame.pack(pady=10, 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=正常速度,2.0=慢2倍")
speed_hint.pack(side=tk.LEFT, padx=10)

# -------------------- 简谱输入文本框 --------------------
label = tk.Label(root, text="输入简谱(格式如 '1..2.34*5**---',-为休止符):")
label.pack(pady=10)

entry = tk.Text(root, font=('Courier New', 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=20)

# 运行程序
root.mainloop()
pygame.quit()





你可能感兴趣的:(python,开发语言)