Monday, April 18, 2011

Sound Programming - Part 1

Beeping in Java

The objective of this article is to write a beep() method in Java. It will just play a tone in a specified frequency. Before we look into the details of the beep() method, let's have a look at the caller first.

  // main program to call the beep() method
  public static void main(String[] args) throws Exception
  {
    int frequency=400;   // hz
    int duration =2000;  // milliseconds
    beep(frequency,duration);
  }

Physics of Sound

Before we can define the beep() method, we must know the mathematical modeling of a sound wave. Mathematically, a "pure tone" can be represented by a sine wave. The following program will generate a sine wave. Just compile the program and go to the "Visualize" section below.

/******************************************************************************
* File : Sine.java
* Author : http://java.macteki.com/
* Description :
*   Create sample data points of a Sine wave
* Tested with : JDK 1.6
******************************************************************************/

class Sine
{

  static short[] makeSineSample()
  {
    double frequency = 400;
    double sampleRate = 16000;  // 16000 samples per second

    int bytesPerSample = 2;
    short[] audioData = new short[160];   // 160 sample points = 10 ms 

    int sampleLength = audioData.length;


    double volume = 8192;
    for(int i = 0; i < sampleLength; i++){
      double time = i/sampleRate;
      double angle = 2*Math.PI*frequency*time;
      double sinValue = Math.sin(angle);
      short amplitude = (short) (volume*sinValue);
      audioData[i]=amplitude;
    }

    return audioData;
  }

  public static void main(String[] args) throws Exception
  {
    short[] buffer=makeSineSample();

    for (int i=0;i<160;i++)
    {
      System.out.println(buffer[i]);
    }
  }
}

The above program will create 160 sample pixels of the sine wave with frequency 400hz. That is, the sine wave will complete 400 cycles in 1 second. If we generate 10 ms of data, there would be 4 cycles. Since the sampling rate is 16000 samples per second, 160 samples will contains 10 ms of data.

Visualize the Sample Data

First execute the program :

java Sine >out.txt
Load the output file into a excel file, then insert a graph with the sample data. You should see 4 cycles of sine wave as expected.

Java Sound API

The javax.sound.sampled package provides API to play a sound wave. All we have to do is to feed the sound wave sample data points to a SourceDataLine object. The following is a complete sample of playing a beep sound.

/******************************************************************************
* File : Beep.java
* Author : http://java.macteki.com/
* Description :
*   Play a pure tone with specified frequency
* Tested with : JDK 1.6
******************************************************************************/

class Beep
{

  static void beep(double frequency, int duration) throws Exception
  {
    
    int nChannel = 1;         // number of channel : 1 or 2

    // samples per second
    float sampleRate = 16000;  // valid:8000,11025,16000,22050,44100
    int nBit = 16;             // 8 bit or 16 bit sample

    int bytesPerSample = nChannel*nBit/8;

    double durationInSecond = duration/1000;
    int bufferSize = (int) (nChannel*sampleRate*durationInSecond*bytesPerSample);
    byte[] audioData = new byte[bufferSize];

    // "type cast" to ShortBuffer
    java.nio.ByteBuffer byteBuffer = java.nio.ByteBuffer.wrap(audioData);
    java.nio.ShortBuffer shortBuffer = byteBuffer.asShortBuffer();


    int sampleLength = audioData.length/bytesPerSample;

    // generate the sine wave
    double volume = 8192;   // 0-32767
    double PI = Math.PI;
    for(int i = 0; i < sampleLength; i++){
      double time = i/sampleRate;
      double freq = frequency;
      double angle = 2*PI*freq*time;
      double sinValue = Math.sin(angle);

      short amplitude = (short) (volume*sinValue);

      for (int c=0;c<nChannel;c++)
      {
        shortBuffer.put(amplitude);
      }

    }//end generating sound wave sample


    boolean isSigned=true;
    boolean isBigEndian=true;

    // Define audio format
    javax.sound.sampled.AudioFormat audioFormat =
      new javax.sound.sampled.AudioFormat(sampleRate, nBit, nChannel, isSigned,isBigEndian);


    javax.sound.sampled.DataLine.Info dataLineInfo =
      new javax.sound.sampled.DataLine.Info(
         javax.sound.sampled.SourceDataLine.class, audioFormat);

    // get the SourceDataLine object
    javax.sound.sampled.SourceDataLine sourceDataLine = 
      (javax.sound.sampled.SourceDataLine)
      javax.sound.sampled.AudioSystem.getLine(dataLineInfo);

    sourceDataLine.open(audioFormat);
    sourceDataLine.start();

    // actually play the sound
    sourceDataLine.write(audioData,0,audioData.length);

    // "flush",  wait until the sound is completed
    sourceDataLine.drain();

  }

  
  public static void main(String[] args) throws Exception
  {
    int frequency=400;   // hz
    int duration =2000;  // milliseconds
    beep(frequency,duration);
  }
}

7 comments:

  1. Can you tell me why double volume = 8192; ? Why this value?

    ReplyDelete
    Replies
    1. The volume level 8192 is just an arbitrary choice between 0 and 32767.

      Delete
  2. This comment has been removed by the author.

    ReplyDelete
  3. why volume is a number from 0 to 32767

    ReplyDelete
    Replies
    1. Because it is a 16 bit sound sample (see line 19 of Beep.java).
      For a 16 bit sample, the amplitude is stored in a short integer (see line 43).

      And for a 16 bit short integer, the range is from -32768 to +32767. That means your sine wave will have a maximum amplitude of 32767. And in Physics, amplitude is volume.

      Hope this help.

      Delete
    2. thx you for these sound posts. i was looking for this for quite some time.

      why sample rates must be // valid:8000,11025,16000,22050,44100 ?

      Delete
    3. They are typical rates that most codec would support. Selecting a value from one of them is a safe choice.

      Delete