Интерполяция между двумя преобразованиями Фурье во времени - PullRequest
0 голосов
/ 08 мая 2018

Я музыкант, и я делаю сценарий, который берет волновой файл и привязывает каждую из его частот от преобразований Фурье к ближайшей музыкальной гармонике. Благодаря помощи другого вопроса, который я разместил здесь, эта часть работает, но сейчас мне нужно сделать так, чтобы во время звуковой волны она настраивалась на одну частоту за раз, чтобы в начале вывода она звучала так же, как вход, и в конце он звучит как инструмент.

Если бы я просто хотел, чтобы они соединились друг с другом, это было бы легко, я мог бы просто пометить бесстрашно или взять средневзвешенное значение исходного преобразования Фурье и выходного, но вместо этого я хочу сделать одну настройку. частота за раз. Это означает, что на 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()
...