我是一名音乐家,我制作了一个采用波形文件的脚本,并将每个频率从傅里叶变换中捕捉到最近的音乐谐波。感谢我在这里发布的另一个问题的帮助,那部分有效,但我现在需要做的就是在整个声波中这样做,它一次调整一个频率,这样在输出的开头它听起来像是输入,到最后它听起来像一个乐器。
如果我只是希望它们相互淡化那么这很容易,我可以只是在大胆中交叉淡入淡出或者对原始傅立叶变换和输出变换加权平均,但我想要做的是调整一个频率一次。这意味着在通过输出的50%的路径上,50%的频率被捕捉到最接近的谐波,而另外50%的频率未受影响。如果不单独计算每个输出样本,我怎么能做到这一点?
另外,我也在考虑随着时间的推移减少MAX_HARMONIC,但它会遇到类似的问题。
这是我测试的样本(将其重命名为missile.wav): https://my.mixtape.moe/iltlos.wav
到目前为止,这是脚本:
import struct
import wave
import numpy as np
# import data from wave
wav_file = wave.open("missile.wav", 'r')
num_samples = wav_file.getnframes()
sampling_rate = wav_file.getframerate() / 2
data = wav_file.readframes(num_samples)
wav_file.close()
data = struct.unpack('{n}h'.format(n=num_samples), data)
data = np.array(data)
# fast fourier transform makes an array of the frequencies of sine waves that comprise the sound
data_fft = np.fft.rfft(data)
# the higher MAX_HARMONIC is, the more it sounds like the original,
# the lower it is, the more it sounds like an instrument
MAX_HARMONIC = 2
# generate list of ratios that can be used for tuning (not octave reduced)
valid_ratios = []
for i in range(1, MAX_HARMONIC + 1):
for j in range(1, MAX_HARMONIC + 1):
if i % 2 != 0 and j % 2 != 0:
valid_ratios.append(i/float(j))
valid_ratios.append(j/float(i))
# remove dupes
valid_ratios = list(set(valid_ratios))
# find all the frequencies with the valid ratios
valid_frequencies = []
multiple = 2
while(multiple < num_samples / 2):
multiple *= 2
for ratio in valid_ratios:
frequency = ratio * multiple
if frequency < num_samples / 2:
valid_frequencies.append(frequency)
# remove dupes and sort and turn into a numpy array
valid_frequencies = np.sort(np.array(list(set(valid_frequencies))))
# bin the data_fft into the nearest valid frequency
valid_frequencies = valid_frequencies.astype(np.int64)
boundaries = np.concatenate([[0], np.round(np.sqrt(0.25 + valid_frequencies[:-1] * valid_frequencies[1:])).astype(np.int64)])
select = np.abs(data_fft) > 1
filtered_data_fft = np.zeros_like(data_fft)
filtered_data_fft[valid_frequencies] = np.add.reduceat(np.where(select, data_fft, 0), boundaries)
# do the inverse fourier transform to get a sound wave back
recovered_signal = np.fft.irfft(filtered_data_fft)
# write sound wave to wave file
comptype="NONE"
compname="not compressed"
nchannels=1
sampwidth=2
wav_file=wave.open("missile_output.wav", 'w')
wav_file.setparams((nchannels, sampwidth, int(sampling_rate), num_samples, comptype, compname))
for s in recovered_signal:
wav_file.writeframes(struct.pack('h', s))
wav_file.close()