Search code examples
javanoisejavax.sound.sampledclicking

java SourceDataLine sine waves clicks


I want to generate plain sine waves using javax.sound.sampled.SourceDataLine. For one constant frequency, it works fine, but there is always a kind of clicking noise when changing frequency. What am I doing wrong, what can I do to avoid that ?

            line.start();
            final byte[] toneBuffer = new byte[SAMPLE_CHUNK];

            while(run) {
                createSineWaveBuffer(frequency, toneBuffer);
                line.write(toneBuffer, 0, toneBuffer.length);
            }

            line.drain();
            line.close();

where

private double alpha = 0.0;
private static final double step_alpha = (2.0*Math.PI)/SAMPLE_RATE;

private void createSineWaveBuffer(final double freq, final byte[] buffer) {
    for(int i = 0; i < buffer.length; ++i) {
        buffer[i] = (byte)(Math.sin(freq*alpha)*127.0);
        alpha += step_alpha;

        if(alpha >= 2.0*Math.PI) {
            alpha = 0.0;
        }
    }
}

Solution

  • You experience the click because when freq changes, the whole sine wave is shifted. As an example with an easier-to-draw triangle wave:

    1 Hz
      /\    /\    /
     /  \  /  \  /
    /    \/    \/
    
    .5 Hz_
        / \
      /     \     /
    /         \_/
    

    If you switch between these at an arbitrary time:

      /\  |\
     /  \ |  \     /
    /    \|    \_/
    

    there is discontinuity, which you hear as a click.

    This is fundamentally because you are causing a sudden jump from sin(freq*alpha) to sin(.5*freq*(alpha+step_alpha)). Not only is the derivative of the input to sin() changing discontinuously (which is what you need to change the frequency), the value is also changing discontinuously.

    The way around this is to only change the derivative of the input to sin(). You can do this by keeping a counter that increments based on the frequency:

    private void createSineWaveBuffer(final double freq, final byte[] buffer) {
    
        for(int i = 0; i < buffer.length; ++i) {
            buffer[i] = (byte)(Math.sin(alpha)*127.0);
            alpha += freq*step_alpha;
    
            if(alpha >= 2.0*Math.PI) {
                alpha -= 2.0*Math.PI;
            }
        }
    
        return t;
    }
    

    Here I have changed your alpha to increase at a rate controlled by the current freq.

    Back to the triangle example, this will look like:

               _
      /\      / \
     /  \   /     \
    /    \/         \