我正在尝试用Python做一些声音实验,我需要一个体面的play_tone(freq, dur)
函数实现。我已经看了很长一段时间,并且到目前为止已经发现了三种实现方式,其中只有一种实现了在播放快速音符序列时所需的基本声音平滑度。
我还没有研究过声音生成的数学/物理,所以我在信任上花了很多代码 - 一旦我知道为什么这些方法中最好的一种效果最好,我会用它作为进一步研究的一个起点。
所以我正在寻找一个解释为什么两个“最差”的版本有如此多的剪辑和点击,而最好的版本(使用struct
)是如此平滑。我希望this answer与它有关,但我不确定如何。
另外,我很想找到一种方法来制作最流畅版本的最后一个音符而不是点击最后一个音符 - 即我希望它能够顺利结束。
# This is the smoothest version I can find
import math
import struct
import pyaudio
def play_tone(frequency, duration, stream, amplitude=0.5, fs=44100):
N = int(fs / frequency)
T = int(frequency * duration) # repeat for T cycles
dt = 1.0 / fs
# 1 cycle
tone = (amplitude * math.sin(2 * math.pi * frequency * n * dt)
for n in range(N))
# Notice the b to transform the operation in a bytes operation
data = b''.join(struct.pack('f', samp) for samp in tone)
for n in range(T):
stream.write(data)
#Usage
fs = 48000
p = pyaudio.PyAudio()
stream = p.open(
format=pyaudio.paFloat32,
channels=1,
rate=fs,
output=True)
a = 2 ** (1 / 24)
f0 = 110
qts = [f0 * a ** p for p in range(96)]
for i in range(0, len(qts) - 24, 3):
for j in range(i, i + 24, 4):
play_tone(qts[j], 0.1, stream)
stream.close()
p.terminate()
# This is the second smoothest version I can find
import math
import numpy
import pyaudio
def sine(frequency, length, rate):
length = int(length * rate)
factor = float(frequency) * (math.pi * 2) / rate
return numpy.sin(numpy.arange(length) * factor)
def play_tone(stream, frequency=440, length=1, rate=44100):
chunks = []
chunks.append(sine(frequency, length, rate))
chunk = numpy.concatenate(chunks) * 0.25
stream.write(chunk.astype(numpy.float32).tostring())
#Usage
fs = 48000
p = pyaudio.PyAudio()
stream = p.open(
format=pyaudio.paFloat32,
channels=1,
rate=fs,
output=True)
a = 2 ** (1 / 24)
f0 = 110
qts = [f0 * a ** p for p in range(96)]
for i in range(0, len(qts) - 24, 3):
for j in range(i, i + 24, 4):
play_tone(stream, qts[j], 0.1)
stream.close()
p.terminate()
# This is the least smooth version I can find
import numpy as np
import pyaudio
def play_tone(freq, dur, stream, fs=44100):
volume = 0.5 # range [0.0, 1.0]
duration = dur # in seconds, may be float
f = freq # sine frequency, Hz, may be float
# generate samples, note conversion to float32 array
samples = (np.sin(2*np.pi*np.arange(fs*duration)*f/fs)).astype(np.float32)
# play. May repeat with different volume values (if done interactively)
stream.write(volume*samples)
#Usage
fs = 48000
p = pyaudio.PyAudio()
stream = p.open(
format=pyaudio.paFloat32,
channels=1,
rate=fs,
output=True)
a = 2 ** (1 / 24)
f0 = 110
qts = [f0 * a ** p for p in range(96)]
for i in range(0, len(qts) - 24, 3):
for j in range(i, i + 24, 4):
play_tone(qts[j], 0.5, stream)
stream.close()
p.terminate()
修改波形发生器,使振幅从零开始,在一定时间内(例如总持续时间的1/10)斜坡上升到所需值,并在结束时的同一时间段内减小到零。
这样,无论频率或相位如何,信号在每个音调的结束和开始时始终为零。这应该会产生平稳的过渡。
我不是一个经验丰富的程序员(也更习惯于javascript)。此外,这是一个老问题,但我没有在任何地方找到一个好的答案。所以我对此有所了解。
import pyaudio
import numpy as np
import math
def play_tone(frequency, dur):
p = pyaudio.PyAudio()
volume = 0.8 # range [0.0, 1.0]
fs = 44100 # sampling rate, Hz, must be integer
# duration = 0.3 # in seconds, may be float
duration=dur #"dur" parameter can be removed and set directly
f=frequency
# We need to ramp up (I used an exponential growth formula)
# from low volume to the volume we want.
# For some reason (I can't bothered to figure that out) the
# following factor is needed to calculate how many steps are
# needed to reach maximum volume:
# 0.693147 = -LN(0.5)
stepstomax = 50
stepstomax_mod = int(round(stepstomax/0.693147))
ramprate = 1/(math.exp(0.5)*stepstomax_mod)
decayrate = 0.9996
#Decay could be programmed better. It doesn't take tone duration into account.
#That means it might not reach an inaudible level before the tone ends.
#sine wave
samples1=(np.sin(2*np.pi*np.arange(0,fs*duration,1)*f/fs))
stepcounter=0
for nums in samples1:
thisnum=samples1[stepcounter]
if stepcounter<stepstomax_mod:
#the ramp up stage
samples1[stepcounter]=volume*thisnum*(pow(ramprate+1,stepcounter+1)-1)
else:
#the decay stage
samples1[stepcounter]=volume*thisnum*(pow(decayrate,stepcounter-stepstomax))
stepcounter+=1
samples = samples1.astype(np.float32).tobytes()
stream = p.open(format=pyaudio.paFloat32,
channels=1,
rate=fs,
output=True)
stream.write(samples)
stream.stop_stream()
stream.close()
p.terminate()
play_tone(261.6, 0.3)
play_tone(329.6, 0.3)
play_tone(392, 0.3)
play_tone(523.3, 0.6)
我在javascript中编写类似的东西,并且在webaudio中有一个very good article几乎相同的问题(摆脱点击;发出好听的声音)。我尝试做的是将注释开头的点击删除从webaudio / javascript转换为python。