我试图编写一个数字波表合成器,它最终会包含实时音高/音量输入,但我仍然坚持让音频正常工作。基本思想是程序检查音调和音量输入大约每秒100次,使用scipy的插值函数来改变记录的波形周期的音高和音量,然后写入由改变的波形组成的信号对Pyaudio流重复持续至少0.01秒的时间。我最初尝试使用pygame,但是pygame很容易崩溃。
尝试使用回调模式和sleep()来计算每个周期的时间,由于音频悬崖或周期信号重叠,导致声音非常波动和粗糙,甚至当我测试那些不会有音频悬崖的音高时#39 ;在原始波形中不是一个奇怪的高音分量。
使用阻止模式可以产生更忠实的信号,因为不需要对周期进行计时;上一个周期必须在执行恢复之前完成。然而,产生每个周期信号的计算确实导致跳过,改变了波形的音色。变化很小,因为计算时间远小于0.01s周期,但如果我想增加程序的音高/音量更新频率会更糟。
有没有办法让stream.write()函数返回ASAP并允许信号计算但是在下一个stream.write()暂停执行直到所有样本都被播放?
import scipy as sp
from scipy import interpolate
from time import sleep, perf_counter
import pyaudio
def pyaudioplay():
# sampled waveform and linear interpolation
waveform = sp.array([ 884, 8343, 18370, 27897, 32767, 30861, 23163, 12692,
2225, -6780, -13815, -17404, -16557, -12404, -6286, 792,
7448, 12458, 15086, 15770, 15699, 15031, 13224, 10705,
8734, 7790, 7621, 7800, 7784, 6964, 5444, 4174,
4435, 6335, 8696, 10156, 10460, 10352, 9950, 9006,
7594, 5835, 3680, -21, -6574, -15732, -25302, -31550,
-31968, -25916, -15352, -3702, 6536, 14429, 19103, 19244,
15471, 9717, 2882, -4359, -10835, -15449, -17816, -18788,
-18685, -16665, -12778, -8588, -5434, -3593, -2882, -3311,
-4370, -5157, -5531, -6112, -7410, -9000, -10042, -10042,
-9206, -8213, -7268, -6188, -4489, -1167], dtype=sp.int16)
size = len(waveform)
wavefunc = interpolate.interp1d(range(size), waveform, kind = 'linear', axis = -1, copy = True, bounds_error = False, fill_value = (waveform[0], waveform[-1]))
# pitch and volume 1.5s time series for testing
length = 150; sample_pitch = 512.79069767441854; max_volume = 1.
t = sp.linspace(0., 3*sp.pi, num = length, endpoint = False)
pitches = (sample_pitch*3/4)+(sample_pitch*1/4)*sp.cos(t) # pitch goes down and up
volumes = max_volume*sp.ones(length) # hold at same volume
# PyAudio stream: blocking mode
p = pyaudio.PyAudio()
stream = p.open(format = pyaudio.paInt16,
channels = 1,
rate = 44100,
output = True,
frames_per_buffer = 0) # also specifies frame_count int for callback
updatePeriod = 441
begin = perf_counter()
record = 0
index = 0
while index < length:
start = perf_counter()
# get pitch and volume
Apitch = pitches[index]
Avolume = volumes[index]
# period with altered width and amplitude to change pitch and volume
Aperiod = (Avolume*wavefunc(sp.arange(0, size, Apitch/sample_pitch))).astype(sp.int16)
cycle = sp.tile(Aperiod, 1+updatePeriod//len(Aperiod)).tobytes()
index += 1
runtime = perf_counter() - start
if runtime > record:
record = runtime
stream.write(cycle)
print('total time:', perf_counter()-begin)
print('largest runtime lag:', record)
stream.stop_stream()
stream.close()
p.terminate()