You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Pygame按频率发声:音符持音不流畅、多音叠加异常问题求助

问题与解决方案

问题概述

我用Pygame写了一个简易钢琴程序,通过A/S/D/F按键播放对应音符,但遇到三个问题:

  1. 音符无法流畅持续发声,有明显卡顿
  2. 同时按下两个按键时会产生怪异的杂音
  3. 必须激活Pygame的小窗口才能触发按键绑定,操作不便

原代码:

import pygame
import numpy as np
import math

pygame.init()

# Set the sample rate and bit size for the audio
pygame.mixer.init(frequency=44100, size=-16, channels=10, buffer=2**12)

# Create a small window in the background (1x1 size)
pygame.display.set_mode((1, 1), pygame.NOFRAME)

# Define the piano sounds
frequencies = [
    {"Note": "C2", "Frequency": 65.41},
    {"Note": "C", "Frequency": 130.81},
    {"Note": "D", "Frequency": 146.83},
    {"Note": "E", "Frequency": 164.81},
    # Add more notes as needed
]

# Create a dictionary to map key codes to notes and channels
key_mapping = {
    pygame.K_a: {"Note": "C2", "Channel": None},
    pygame.K_s: {"Note": "C", "Channel": None},
    pygame.K_d: {"Note": "D", "Channel": None},
    pygame.K_f: {"Note": "E", "Channel": None},
    # Add more key mappings as needed
}

# Create sound objects for each note
sounds = {}
for note_info in frequencies:
    note = note_info["Note"]
    frequency = note_info["Frequency"]

    wave_array = np.array([int(32767.0 * math.sin(2.0 * math.pi * frequency * t / 44100.0)) for t in range(44100)], dtype=np.int16)
    sound = pygame.mixer.Sound(wave_array)
    sounds[note] = sound

# Main loop
running = True

while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            key = event.key
            if key in key_mapping:
                note_info = key_mapping[key]
                note = note_info["Note"]
                if note_info["Channel"] is None:
                    prit(f"Played {note}")
                    channel = pygame.mixer.find_channel()
                    if channel:
                        channel.play(sounds[note], loops=-1)
                        note_info["Channel"] = channel
        elif event.type == pygame.KEYUP:
            key = event.key
            if key in key_mapping:
                note_info = key_mapping[key]
                note = note_info["Note"]
                if note_info["Channel"]:
                    print(f"Stopped {note}")
                    note_info["Channel"].stop()
                    note_info["Channel"] = None

# Clean up
pygame.quit()

解决方案

1. 解决音符卡顿问题

原代码的正弦波仅生成1秒样本,循环播放时首尾相位不连续,导致卡顿。修改方案:

  • 生成更长的波形(比如2秒)
  • 给波形添加淡入淡出处理,让首尾音量平滑过渡,消除衔接杂音
  • 修复代码中的笔误:prit改为print

2. 解决多音叠加杂音问题

纯正弦波叠加会产生拍频效应(音量周期性波动的杂音),真实钢琴音色是基波+泛音的复合波形。修改方案:

  • 给每个音符添加多阶泛音,模拟真实音色
  • 归一化波形音量,避免音频过载失真

3. 解决需激活窗口才能按键的问题

Pygame默认仅在窗口激活时接收键盘事件,用pynput库实现全局键盘监听,无需激活窗口。需要先安装:pip install pynput


修改后的完整代码

import pygame
import numpy as np
import math
from pynput.keyboard import Key, Listener

pygame.init()

# 缩小缓冲区减少音频延迟,保留足够通道数
pygame.mixer.init(frequency=44100, size=-16, channels=10, buffer=2**10)

# 创建无框窗口并最小化,避免干扰操作
screen = pygame.display.set_mode((1, 1), pygame.NOFRAME)
pygame.display.iconify()

# 定义音符,新增泛音参数模拟真实音色
frequencies = [
    {"Note": "C2", "Frequency": 65.41, "Harmonics": [1, 0.5, 0.3, 0.2]},
    {"Note": "C", "Frequency": 130.81, "Harmonics": [1, 0.6, 0.4, 0.25]},
    {"Note": "D", "Frequency": 146.83, "Harmonics": [1, 0.55, 0.35, 0.2]},
    {"Note": "E", "Frequency": 164.81, "Harmonics": [1, 0.6, 0.3, 0.15]},
]

# 按键映射,记录每个按键对应的音符和播放通道
key_mapping = {
    'a': {"Note": "C2", "Channel": None},
    's': {"Note": "C", "Channel": None},
    'd': {"Note": "D", "Channel": None},
    'f': {"Note": "E", "Channel": None},
}

# 生成带泛音和淡入淡出的音效
sounds = {}
sample_rate = 44100
duration = 2  # 生成2秒波形,保证循环衔接自然
fade_samples = 1000  # 淡入淡出的样本长度,消除卡顿

for note_info in frequencies:
    note = note_info["Note"]
    base_freq = note_info["Frequency"]
    harmonics = note_info["Harmonics"]
    
    # 生成基波+多阶泛音的复合波形
    t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
    wave = np.zeros_like(t)
    for harmonic_idx, amp in enumerate(harmonics):
        wave += amp * np.sin(2 * math.pi * base_freq * (harmonic_idx + 1) * t)
    
    # 归一化音量,防止叠加时失真
    wave = wave / np.max(np.abs(wave)) * 0.7 * 32767
    
    # 淡入淡出处理,平滑波形首尾
    wave[:fade_samples] *= np.linspace(0, 1, fade_samples)
    wave[-fade_samples:] *= np.linspace(1, 0, fade_samples)
    
    wave_array = wave.astype(np.int16)
    sound = pygame.mixer.Sound(wave_array)
    sounds[note] = sound

# 全局按键按下事件处理
def on_press(key):
    try:
        char = key.char.lower()
        if char in key_mapping:
            note_info = key_mapping[char]
            if note_info["Channel"] is None:
                note = note_info["Note"]
                print(f"Played {note}")
                channel = pygame.mixer.find_channel()
                if channel:
                    channel.play(sounds[note], loops=-1)
                    note_info["Channel"] = channel
    except AttributeError:
        # 处理非字符按键(如Shift、Ctrl),忽略即可
        pass

# 全局按键释放事件处理
def on_release(key):
    try:
        char = key.char.lower()
        if char in key_mapping:
            note_info = key_mapping[char]
            if note_info["Channel"]:
                note = note_info["Note"]
                print(f"Stopped {note}")
                note_info["Channel"].stop()
                note_info["Channel"] = None
        # 按ESC键退出程序
        if key == Key.esc:
            pygame.quit()
            return False
    except AttributeError:
        pass

# 启动全局键盘监听
listener = Listener(on_press=on_press, on_release=on_release)
listener.start()

# 保持程序运行,直到监听结束
while listener.is_alive():
    pygame.time.Clock().tick(10)

listener.join()

内容的提问来源于stack exchange,提问作者CodingStark09

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 15:24:58