如何在 Java 中实现音高效果? (FFT、IFFT、幅度、相位)

How to implement a Pitch Effect in Java? (FFT, IFFT, Amplitude, Phase)

我使用 apache commons 数学库来转换我的音频样本缓冲区上的 FFt 和 IFFT。 FFT 的输出给了我一组复数。频率在中间镜像。样本缓冲区大小为 4096 个样本,我得到 2048 个有用的复数。

我在 Java 中有两个实现,一个在 IFFT 之前遍历最终数组,并计算应该从中获取复数的位置的插值。所以基本上我所做的是在另一个频率范围内扭曲复数。

FastFourierTransformer fft = new FastFourierTransformer(DftNormalization.STANDARD);
Complex[] freq, inverse, freqn;

for(int c = 0; c < in.length; c++){

    freq = fft.transform(in[c], TransformType.FORWARD);
    freqn = new Complex[freq.length];

    freqn[0] = Complex.valueOf(freq[0].getReal(), freq[0].getImaginary());

    for (int i = 1; i <= freq.length/2; i++) {

        double fOrig = i / factor + shift;

        int left = (int) Math.floor(fOrig);
        int right = (int) Math.ceil(fOrig);
        double weighting = fOrig - left;

        double new_Re = 0, new_Im = 0;

        if(left > 0 && left < freq.length / 2 && right > 0 && right < freq.length / 2){
            new_Re = interpolate(freq[left].getReal(), freq[right].getReal(), weighting);
            new_Im = interpolate(freq[left].getImaginary(), freq[right].getImaginary(), weighting);
        }
        freqn[i] = Complex.valueOf(new_Re, new_Im);
        freqn[freq.length-i] = Complex.valueOf(new_Re, new_Im);
    }
    inverse = fft.transform(freqn, TransformType.INVERSE);

    for(int i = 0; i < inverse.length; i++){
        in[c][i] = inverse[i].getReal();
    }
}

由于我的输入音频信号的采样率,我从一个频率中得到了多个音调频率,因此此实现主要在高音区域音调具有副作用。我的其他实现计算传入复数的振幅和相位。然后它仅将振幅标度扭曲到新位置,然后在原始相位值和新振幅值的帮助下计算新的复数。在矩形与极坐标之间转换并返回矩形时,我失去了我的标志。由于我只更改复数向量的长度,因此我可以在输出复数上强制输入符号。

FastFourierTransformer fft = new FastFourierTransformer(DftNormalization.STANDARD);
Complex[] freq, inverse;

for(int c = 0; c < in.length; c++){

    freq = fft.transform(in[c], TransformType.FORWARD);

    double[] ampl = new double[freq.length];
    double[] angl = new double[freq.length];

    double re, im;

    boolean[] unitRe = new boolean[freq.length];
    boolean[] unitIm = new boolean[freq.length];

    double fctr = factor;

    for(int f = 0; f < freq.length; f++){
        re = freq[f].getReal();
        im = freq[f].getImaginary();
        unitRe[f] = re >= 0;
        unitIm[f] = im >= 0;

        ampl[f] = op.magn(re, im);
        angl[f] = op.agl(re, im);
    }

    for(int f = 0; f < freq.length; f++){
        int val = f < freq.length / 2 ? f : freq.length / 2 - (f - freq.length / 2);
        double weighting = ((double)val / fctr + shift) % 1;

        int left = (int) Math.floor(val / fctr + shift);
        int right = (int) Math.ceil(val / fctr + shift);
        double new_ampl = 0;

        if(left >= 0 && left < freq.length / 2 && right >= 0 && right < freq.length / 2){
            new_ampl = interpolate(ampl[left], ampl[right], weighting);
        }

        re = op.real(new_ampl, angl[f]);
        im = op.imag(new_ampl, angl[f]);

        re = unitRe[f] ? Math.abs(re) : Math.abs(re) * -1;
        im = unitIm[f] ? Math.abs(im) : Math.abs(im) * -1;

        freq[f] = Complex.valueOf(re, im);
    }

    inverse = fft.transform(freq, TransformType.INVERSE);

    for(int i = 0; i < inverse.length; i++){
        in[c][i] = inverse[i].getReal();
    }
}

第二个实现听起来比第一个好得多。它实际上什至比我使用的大多数 DJ 应用程序听起来更好,但我不知道为什么?难道我做错了什么?我在 Java 中找不到任何其他实现可以与之比较。他们通常只是将整个频率标度与振幅和相位一起扭曲到一个新的标度中,还是只是获取振幅并将其强制到另一个标度中的原始相位上?

你的第二种算法类似于时间音高修改的相位声码器方法。据报道,许多音频处理库使用相位声码器技术的变体,但通常只有在有足够的处理能力时才使用。