pygame更换Sound播放的音频数据时如何避免爆音实现平滑过渡
爆音问题原因与解决方案
问题原因
你的代码每次调整频率后直接终止旧音频、播放全新生成的正弦波,爆音来源于两个核心问题:
- 波形相位不连续:旧音频被切断时的瞬时幅值不一定为0,新生成的正弦波默认从0相位(幅值0)起始,两个波形的幅值跳变就会产生爆音
- 播放硬切换:旧声音直接截断、新声音直接满音量启动,没有过渡缓冲
解决方案
我们通过记忆相位偏移保证波形连续性,搭配短时间交叉淡入实现平滑切换,修改后的代码如下:
# -*- coding: utf-8 -*- import numpy import pygame import pygame.freetype freq = 100 sampleRate = 44100 # 新增:记录上一次音频结束的相位偏移 phase_offset = 0 # 过渡帧长度,20ms的过渡时间人耳基本感知不到,同时避免爆音 fade_len = int(0.02 * sampleRate) pygame.freetype.init() (width, height) = (1280,720) screen = pygame.display.set_mode((width, height)) myfont = pygame.freetype.Font('Ubuntu-Regular.ttf',220) myfont.antialiased = True pygame.mixer.init(44100,-16,2,512) if __name__ == "__main__": pygame.display.set_caption('Signaaligeneraator') screen.fill((255,255,255)) pygame.display.flip() running = True # 首次生成音频 arr = numpy.array([4096 * numpy.sin(2.0 * numpy.pi * freq * x / sampleRate + phase_offset) for x in range(0, sampleRate)]).astype(numpy.int16) # 计算当前使用的最终相位,作为下一次生成的偏移 phase_offset = (2.0 * numpy.pi * freq * sampleRate / sampleRate + phase_offset) % (2*numpy.pi) arr2 = numpy.c_[arr,arr] sound = pygame.sndarray.make_sound(arr2) channel = sound.play(-1) # 循环播放,不用每次重新调用play while running: screen.fill((255,255,255)) unit = "Hz" if freq > 1000: unit = "kHz" # 修正原代码中单位逻辑错误 myfont.render_to(screen, ((width/2)-(len(str(freq)+" "+unit)*120)/2, (height/2)-120/2), str(freq)+" "+unit, (0, 0, 0)) pygame.display.flip() for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_UP: freq += 10 # 生成新音频,用上一次的相位偏移作为起始 new_arr = numpy.array([4096 * numpy.sin(2.0 * numpy.pi * freq * x / sampleRate + phase_offset) for x in range(0, sampleRate)]).astype(numpy.int16) # 更新相位偏移 phase_offset = (2.0 * numpy.pi * freq * sampleRate / sampleRate + phase_offset) % (2*numpy.pi) # 交叉淡入处理:前fade_len帧旧音从1降到0,新音从0升到1,后面正常播放 fade_curve = numpy.linspace(0, 1, fade_len) new_arr[:fade_len] = (arr[-fade_len:] * (1 - fade_curve) + new_arr[:fade_len] * fade_curve).astype(numpy.int16) arr = new_arr arr2 = numpy.c_[arr,arr] new_sound = pygame.sndarray.make_sound(arr2) # 替换当前播放的声音,无缝衔接 channel.queue(new_sound) pygame.quit()
额外优化说明
如果需要更丝滑的频率调整效果,也可以改用pygame的音频流回调模式实时生成音频,不需要预先生成1秒的音频块,调整频率时直接修改参数即可,相位偏移的计算会更精准,适合需要高频调整参数的场景。
内容的提问来源于stack exchange,提问作者Rando Hinn
相关产品推荐
相关产品推荐

