7

I'm trying to develop an static method in Java to generate a pure tone.

In the begining it seemed easy, but when I've try to write the double array to the loudspeakers I appreciate too much harmonics.

I test it with an spectrum analyzer (sonometer) and then, also I've drawn in a graphic the array resultant. When I've done it I've seen the problem:

It's about the wave form, it's abrupted. I want to smooth this array, but I don't know how to do it.

This is the code:

/**
 * 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()
tshepang
  • 10,772
  • 21
  • 84
  • 127
Gabi Moreno
  • 701
  • 6
  • 18
  • 3
    This doesn't look like a Java programming problem; I don't see anything wrong with the Java code. It probably needs to be answered by people more familiar with sound generation. Maybe the "waveform" and "sound-synthesis" tags would be more useful, although there are only a handful of followers. – ajb Jan 24 '14 at 22:55

4 Answers4

3

Writing the same value to two consecutive locations introduces a step into the waveform. Any deviation from a smooth sine curve adds harmonics. If you want a pure tone, Don't Do That. If you want to do that, Don't Expect A Pure Tone.

keshlam
  • 7,681
  • 1
  • 15
  • 31
1

You need to compute the angle and sine for every value of 'bufferLength', not from every second value. What you're doing is essentially under-sampling with interpolation. I don't see any 'symmetry' about that.

user207421
  • 289,834
  • 37
  • 266
  • 440
  • 1
    Maybe is as simple as this: 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 } – Gabi Moreno Jan 24 '14 at 23:01
0

I'm not really sure, but I think I've confused about @EJP was talking about: I needed obtain each value step by step. Symmetry it was a fast but worse method about the quality of the signal.

This is the new code:

/**
 * 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
  • 701
  • 6
  • 18
  • This implementation is OK, but, on the other hand, I'm seeing the big problem about this. The harmonics I'm obtaining are more related about the waveform phase. It's completely necessary using central frequencies divisible by samplerate for the first sample in a buffer was the next of last sample in previous buffer. I think is more or less an understandable explanation, isn't it? – Gabi Moreno Jan 25 '14 at 06:12
  • Finally I found it! f0 have to be a multiple of (fs / bufferSize). By the way, f0 won't be an integer rather a double or almost better a float to earn a little bit of speed. – Gabi Moreno Jan 25 '14 at 06:41
  • f0 only must be a double. If it would be a float we lose information sometimes. – Gabi Moreno Jan 25 '14 at 06:53
0

This is the definitive code:

/**
 * 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()

A main() to test it:

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()
Gabi Moreno
  • 701
  • 6
  • 18