0
votes

I want to generate sine wave with combined frequencies. I'm going this way:

public static byte[] combineSineWave(Collection<Double> frequencies) {
    double[] sample = new double[SAMPLE_RATE];
    byte[] result = new byte[2 * SAMPLE_RATE];

    double coefficient = 2 * Math.PI;

    for (int i = 0; i < SAMPLE_RATE; i++) {
        double sum = 0;

        for (double frequency : frequencies) {
            sum += Math.sin(coefficient * i * frequency / SAMPLE_RATE);
        }

        sample[i] = sum / frequencies.size();
    }

    convertToPCM16Bit(sample, result);

    return result;
}

private static void convertToPCM16Bit(double[] sample, byte[] result) {
    int idx = 0;
    for (final double dVal : sample) {
        // scale to maximum amplitude
        final short val = (short) ((dVal * 32767));
        // in 16 bit wav PCM, first byte is the low order byte
        result[idx++] = (byte) (val & 0x00ff);
        result[idx++] = (byte) ((val & 0xff00) >>> 8);
    }
}

And playing generated bytes by this code:

AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SoundWaveGenerator.SAMPLE_RATE,
            AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, 2 * SoundWaveGenerator.SAMPLE_RATE,
            AudioTrack.MODE_STREAM);

    audioTrack.play();

    while (true) {
        byte[] bytes = SoundWaveGenerator.combineSineWave(frequencies);
        audioTrack.write(bytes, 0, bytes.length);
    }

But the problem is that I hear a click about every second. What's wrong?

1

1 Answers

1
votes

The click you are hearing is the discontinuity between each call to combineSineWave. In each call to combineSineWave each frequency will start out a zero degrees of phase. But, depending on the frequency, may not produce an even number of cycles by the end of the 1 second interval. Say one of the frequencies is such that the last sample of the second is at 90 deg (1.0) then there is a discontinuity when you generate the next interval.

To solve this, you need to keep track of some state between successive calls. I've coded an example below, but I don't know how to pass by reference in Java. Also, you'll need to deal with the possibility of i rolling over.

int i = 0;
while (true) {
    byte[] bytes = SoundWaveGenerator.combineSineWave(frequencies, ref i);
    audioTrack.write(bytes, 0, bytes.length);
}


public static byte[] combineSineWave(Collection<Double> frequencies, ref int i) {
    double[] sample = new double[SAMPLE_RATE];
    byte[] result = new byte[2 * SAMPLE_RATE];

    double coefficient = 2 * Math.PI;

    for (int j = 0; j < SAMPLE_RATE; j++) {
        double sum = 0;

        for (double frequency : frequencies) {
            sum += Math.sin(coefficient * i++ * frequency / SAMPLE_RATE);
        }
        sample[i] = sum / frequencies.size();
    }

    convertToPCM16Bit(sample, result);

    return result;
}

P.S. you can divide by SAMPLE_RATE outside of the loop.

double coefficient = 2 * Math.PI / SAMPLE_RATE;