降低在Java中产生纯音的谐波

7
我正在尝试开发一个Java静态方法来生成纯音。一开始看起来很简单,但是当我尝试将双精度数组写入扬声器时,我发现谐波太多了。我用频谱分析仪(声强计)进行测试,然后也在图形上绘制了结果数组。当我这样做时,我发现了问题:它与波形有关,它是突兀的。我想平滑这个数组,但是我不知道该如何做。以下是代码:
/**
 * Genera un tono puro.
 * @param bufferSize Tamaño del buffer.
 * @param fs Frecuencia de muestreo.
 * @param f0 Frecuencia central. 
 * @return El tono puro.
 */
public static double[] generateTone(int bufferSize, int fs, int f0) {
    double[] tone = new double[bufferSize]; // Tono
    double angle; // Ángulo del tono

    // Sólo hace falta recorrer la mitad del array, ya que hay simetría:
    for (int i = 0; i < tone.length / 2; i++) {
        angle = 2 * Math.PI * f0 * i / fs; // Calculamos la variación del ángulo

        // Tenemos que conseguir que la señal sea menos abrupta para reducir al máximo los armónicos):
        tone[2 * i + 1] = tone[2 * i] = Math.sin(angle); // Aprovechamos la simetría
    }

    return tone;
} // getSinus()

3
这看起来不像是Java编程问题;我认为Java代码没有任何问题。这个问题可能需要由更熟悉声音生成的人来回答。也许"波形"和"声音合成"标签会更有用,尽管只有少数跟随者。 - ajb
4个回答

3

将相同的值写入连续的两个位置会在波形中引入一个步进。任何与平滑正弦曲线偏离的情况都会添加谐波。如果您想要纯音调,请不要这样做。如果您想这样做,请不要期望得到纯音调。


1
您需要为每个“bufferLength”计算角度和正弦,而不是从每个第二个值开始计算。您所做的实质上是使用插值进行欠采样。我没有看到任何关于这方面的“对称性”。

1
也许就是这么简单:for (int i = 0; i < tone.length; i++) { angle = 2 * Math.PI * f0 * i / fs; // Calculate the angle variation tone[i] = Math.sin(angle); // Each sample is obtained from the sine of the angle } - Gabi Moreno

0

我不是很确定,但我认为我对@EJP所说的有些困惑:我需要逐步获取每个值。对称性是一种快速但信号质量较差的方法。

这是新代码:

/**
 * Genera un tono puro.
 * @param bufferSize Tamaño del buffer.
 * @param fs Frecuencia de muestreo.
 * @param f0 Frecuencia central. 
 * @return El tono puro.
 */
public static double[] generateTone(int bufferSize, int fs, int f0) {
    double[] tone = new double[bufferSize]; // Tono
    double angle; // Ángulo del tono

    for (int i = 0; i < tone.length; i++) {
        angle = 2 * Math.PI * f0 * i / fs; // Calculamos la variación del ángulo
        tone[i] = Math.sin(angle); // Cada muestra se obtiene a partir del seno del ángulo
    }

    return tone;
} // generateTone()

这个实现还可以,但是另一方面,我发现了一个大问题。我得到的谐波更多地与波形相位有关。对于缓冲区中第一个样本,完全需要使用可被采样率整除的中心频率,该样本是前一个缓冲区中最后一个样本的下一个。我认为这是一个比较容易理解的解释,不是吗? - Gabi Moreno
终于找到了!f0必须是(fs / bufferSize)的倍数。顺便说一下,f0不会是整数,而是一个double或者更好的float类型,以获得更快的速度。 - Gabi Moreno
f0 只能是 double 类型。如果是 float 类型,有时会丢失信息。 - Gabi Moreno

0

这是最终的代码:

/**
 * Genera un tono puro.
 * @param bufferSize Tamaño del buffer.
 * @param fs Frecuencia de muestreo.
 * @param f0 Frecuencia central. 
 * @return El tono puro.
 */
public static double[] generateTone(int bufferSize, int fs, double f0) {
    double[] tone = new double[bufferSize]; // Tono
    double angle; // Ángulo del tono

    for (int i = 0; i < tone.length; i++) {
        angle = 2 * Math.PI * f0 * i / fs; // Calculamos la variación del ángulo
        tone[i] = Math.sin(angle); // Cada muestra se obtiene a partir del seno del ángulo
    }

    return tone;
} // generateTone()

一个用于测试的main()函数:
public static void main(String[] args) {
    double[] x; // Señal de entrada (en nuestro caso es un tono puro a 250 Hz)
    int bufferSize = 1024;
    int fs = 44100;
    double f0 = ((float) fs / (float) bufferSize);
    System.out.println("f0 =  " + f0);

    x = GSignals.generateTone(bufferSize, fs, f0); // Generamos la señal de entrada
} // main()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接