异步和同时播放变化音高的声音

时间:2017-07-03 23:53:49

标签: python multithreading audio pitch sample-rate

我的目标是使用Python在计算机游戏环境中播放具有以下要求的声音。

  1. 取一些输入WAV文件并随机改变音高至原始的+/- 50%。使用PyDub更改采样率似乎是一种简单的方法。

  2. 播放声音。

  3. 能够快速调用此功能,以便长时间和短时间的声音在实际播放中重叠。

  4. 我花了超过24个工时来寻找满足所有这些要求的方法。我之前在Visual Basic中已经完成了这项工作,我对Python的难度感到惊讶。

    目前我所知道的是:

    1. PyGame.Mixer可以同时播放重叠的声音,但必须以相同的采样率播放它们。似乎没有办法改变音高。

    2. PyDub可以通过更改采样率来改变音高,但它不能通过基本播放播放重叠的声音。并且,我必须将输出声音写入文件,然后立即将其加载回来,这感觉很浪费。

    3. WinSound可以播放PyDub的不同采样率声音,但不能同时播放,甚至不能使用线程播放。

    4. Playsound包不适用于python 3.6。

    5. 如果我使用线程,PyAudio可以播放PyDub的并发播放的并发播放声音,但是,任何次数都会导致可怕的内存问题,导致Python崩溃。

    6. 我的问题:如何在不造成问题的情况下实现上述3个目标?

      这是我到目前为止最好的结果(这是PyAudio版本,如果测试超过一次或两次会导致崩溃):

      from pydub import AudioSegment
      from random import random, seed
      from time import sleep
      import os
      import threading
      import pyaudio
      import wave
      
      def PlayAsyncWithRandPitch(WavPath):
          MyBaseFilename = os.path.basename(WavPath)
          sound = AudioSegment.from_file(WavPath, format="wav")
          seed()
          octaves = ((random()-0.50))
          print("random octave factor for this sound is: "+str(octaves))
          print("current sound frame rate:"+str(sound.frame_rate))
          new_sample_rate = int(sound.frame_rate * (2.0 ** octaves))
          print("new sound frame rate:"+str(new_sample_rate))
          newpitchsound = sound._spawn(sound.raw_data, overrides={'frame_rate': new_sample_rate})
          MyTotalNewPath = os.getcwd()+"\\Soundfiles\\Temp\\Mod_"+MyBaseFilename
          newpitchsound.export(MyTotalNewPath, format="wav")
          SoundThread = threading.Thread(target=PAPlay, args=(MyTotalNewPath,))
          SoundThread.start()
      #=======================================================================================
      
      
      #This function is just code for playing a sound in PyAudio
      def PAPlay(filename):
          CHUNK = 1024
          wf = wave.open(filename, 'rb')
          p = pyaudio.PyAudio()
          stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
                          channels=wf.getnchannels(),
                          rate=wf.getframerate(),
                          output=True)
          data = wf.readframes(CHUNK)
          while data != '':
              stream.write(data)
              data = wf.readframes(CHUNK)
          stream.stop_stream()
          stream.close()
          p.terminate()
          return
      
      
      if __name__ == "__main__":
          #Example sounds to test if more than one can play at once
          PlayAsyncWithRandPitch(os.getcwd()+'\\Soundfiles\\RifleMiss.WAV')
          sleep(0.2)
          PlayAsyncWithRandPitch(os.getcwd()+'\\Soundfiles\\splash.wav')
          sleep(0.2)
          PlayAsyncWithRandPitch(os.getcwd()+'\\Soundfiles\\sparkhit1.WAV')
          sleep(5.0)
      

      提前感谢您的帮助!

2 个答案:

答案 0 :(得分:1)

感谢另一个小时的谷歌搜索,我能够通过找到一个关于PyDub的模糊注释来解决它。有一种方法可以实际更改采样率,但“实际上并非”更改采样率。它被称为花栗鼠方法。

https://github.com/jiaaro/pydub/issues/157#issuecomment-252366466

我真的不假装理解这里的细微差别,但似乎概念是“将声音,设置样本率改为某些修改后的值,然后转换采样率回到传统的44,100 HZ值。“

他们给出了这个非常有效的例子:

from pydub import AudioSegment
sound = AudioSegment.from_file('./test/data/test1.mp3')
# shift the pitch up by half an octave (speed will increase proportionally)
octaves = 0.5
new_sample_rate = int(sound.frame_rate * (2.0 ** octaves))
# keep the same samples but tell the computer they ought to be played at the 
# new, higher sample rate. This file sounds like a chipmunk but has a weird sample rate.
chipmunk_sound = sound._spawn(sound.raw_data, overrides={'frame_rate': new_sample_rate})
# now we just convert it to a common sample rate (44.1k - standard audio CD) to 
# make sure it works in regular audio players. Other than potentially losing audio quality (if
# you set it too low - 44.1k is plenty) this should now noticeable change how the audio sounds.
chipmunk_ready_to_export = chipmunk_sound.set_frame_rate(44100)

这对我来说没有多大意义,但确实有效:)希望这可以帮助那些人。

答案 1 :(得分:0)

这种方法似乎有点可疑。我解释了如何在以下link上使用Java对C ++人员进行varispeed。

主要思想是使用线性插值从样本之间获取值,并以1:1以外的速率逐步通过样本数据。如果您要求150%并且需要样本0,则样本1.5(在1和2之间的中间位置,插入值。