views:

777

answers:

2

I'm getting a slight distortion (sounds like buzzing) in the background when I run the following code. Because of its subtle nature it makes believe there is some sort of aliasing going on with the byte casting.

AudioFormat = PCM_SIGNED 44100.0 Hz, 16 bit, stereo, 4 bytes/frame, big-endian

Note: code assumes (for now) that the data is in big endian.

public static void playFreq(AudioFormat audioFormat, double frequency, SourceDataLine sourceDataLine)
{
 System.out.println(audioFormat);
 double sampleRate = audioFormat.getSampleRate();
 int sampleSizeInBytes = audioFormat.getSampleSizeInBits() / 8;
 int channels = audioFormat.getChannels();

 byte audioBuffer[] = new byte[(int)Math.pow(2.0, 19.0) * channels * sampleSizeInBytes];

 for ( int i = 0; i < audioBuffer.length; i+=sampleSizeInBytes*channels )
 {
  int wave = (int) (127.0 * Math.sin( 2.0 * Math.PI * frequency * i / (sampleRate * sampleSizeInBytes * channels) )  );

  //wave = (wave > 0 ? 127 : -127);

  if ( channels == 1 )
  {
   if ( sampleSizeInBytes == 1 )
   {
    audioBuffer[i] = (byte) (wave);
   }

   else if ( sampleSizeInBytes == 2 )
   {
    audioBuffer[i] = (byte) (wave);
    audioBuffer[i+1] = (byte)(wave >>> 8);
   }
  }

  else if ( channels == 2 )
  {
   if ( sampleSizeInBytes == 1 )
   {
    audioBuffer[i] = (byte) (wave);
    audioBuffer[i+1] = (byte) (wave);
   }

   else if ( sampleSizeInBytes == 2 )
   {
    audioBuffer[i] = (byte) (wave);
    audioBuffer[i+1] = (byte)(wave >>> 8);

    audioBuffer[i+2] = (byte) (wave);
    audioBuffer[i+3] = (byte)(wave >>> 8);
   }
  }
 }

 sourceDataLine.write(audioBuffer, 0, audioBuffer.length);
}
+1  A: 

I assume you are calling this code repeatedly to play a long sound.

Is there a chance that the wave you are generating is not getting to complete a full period before it is written?

If the wave gets "cut-off" before it completes a full period and then the next wave is written to the output, you will certainly hear something strange and I assume that may be what is causing the buzzing.

For example:

        /-------\              /-------\              /-------\
  -----/         \       -----/         \       -----/         \
                  \                      \                      \
                   \-----                 \-----                 \-----

Notice the disconnect between parts of this wave. That might be causing the buzzing.

Jack
Yes, it's not that issue because the buffer is large enough for the sound to through many cycles. It does account for the click but not for the constant buzzing sound.
srand
this isn't the right answer - the code clearly generates plenty of samples and there's no truncation at the end of each cycle
Alnitak
and I've tested the code to check - it's an inadvertent 8 bit quantisation error that's causing the buzz.
Alnitak
+7  A: 

Your comments say that the code assumes big-endian.

Technically you're actually outputting in little-endian, however it doesn't seem to matter because through a lucky quirk your most significant byte is always 0.

EDIT: to explain that further - when your value is at its maximum value of 127, you should be writing (0x00, 0x7f), but the actual output from your code is (0x7f, 0x00) which is 32512. This happens to be near the proper 16 bit maximum value of 32767, but with the bottom 8 bits all zero. It would be better to always use 32767 as the maximum value, and then discard the bottom 8 bits if required.

This means that even though you're outputting 16-bit data, the effective resolution is only 8 bit. This seems to account for the lack of sound quality.

I've made a version of your code that just dumps the raw data to a file, and can't see anything otherwise wrong with the bit shifting itself. There's no unexpected changes of sign or missing bits, but there is a buzz consistent with 8 bit sample quality.

Also, for what it's worth your math will be easier if you calculate the wave equation based on sample counts, and then worry about byte offsets separately:

int samples = 2 << 19;
byte audioBuffer[] = new byte[samples * channels * sampleSizeInBytes];

for ( int i = 0, j = 0; i < samples; ++i )
{
    int wave = (int)(32767.0 * Math.sin(2.0 * Math.PI * frequency * i / sampleRate));
    byte msb = (byte)(wave >>> 8);
    byte lsb = (byte) wave;

    for (int c = 0; c < channels; ++c) {
        audioBuffer[j++] = msb;
        if (sampleSizeInBytes > 1) {
            audioBuffer[j++] = lsb;
        }
    }
 }
Alnitak
Ah! See the error now, after fixing the amplitude part the error became very apparent, but your code is more efficient too. Thanks!
srand
Well done analysis!
Thorbjørn Ravn Andersen