Tuesday, April 19, 2011

Sound Programming - Part 2

This is part 2 of the series. It is suggested to read the whole series from the beginning.

Attack, Decay, Sustain, Release

Let's recall the sound wave of a pure tone.


The amplitude of the vibration doesn't change. Hence the sound volume doesn't change over time.
In reality, sound volume is seldom constant. For example, if we hit a key on the piano, the sound volume go through four phases.
  1. Attack
    The volume bursts to the maximum level in a short period of time
  2. Decay
    The volume drops a little bit
  3. Sustain
    The volume holds constant for a period
  4. Release
    The sound starts to fade out.

This is known as the ADSR envelope. The following is a visualization of the ADSR envelope.


You may think of the envelope as a multiplier function. If we apply the envelope to our pure tone sine wave, the result would become :


Simplified Model

While ADSR is a good mathematical model for musical intrusment, it is a bit too complicated for our simple program. Hence we will use a simpler model. Our new model will contain two phases only, the sustain and the release phase. To further simplified the model, we will use linear fade out. The multiplier function would become :

Implementation


The implementation of linear fade out is very simple. It just involves a few lines of modifications of the beeping program in part 1.
// modifications of Beep.java
      double fade=1;
      int decay=sampleLength*1/3;  // start fade out at 2/3 of the total time
      if (i>=sampleLength-1-decay) fade=(double)(sampleLength-1-i)/decay;
 
      short amplitude = (short) (volume*fade*sinValue);


Full Source Code

/******************************************************************************
* File : FadeBeep.java
* Author : http://java.macteki.com/
* Description :
*   Play a pure tone with specified frequency, with fade out effect
* Tested with : JDK 1.6
******************************************************************************/
 
class FadeBeep
{
 
  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 = (double) duration/1000.0;
    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);
 
      double fade=1;
      int decay=sampleLength*1/3;  // start fade out at 2/3 of the total time
      if (i>=sampleLength-1-decay) fade=(double)(sampleLength-1-i)/decay;
 
      short amplitude = (short) (volume*fade*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);
  }
}

Result

The program will play a sound for 2 seconds, which will fade out gradually. The sound waveform is something like this (illustration only, not in accurate scale):

3 comments:

  1. Thanks for sharing this example. I have a question though, would it be the same using for instance a wav file that is on a device? You have any tutorials with that?

    ReplyDelete
    Replies
    1. Yes, go to the featured page and locate part 5 of sound programming.
      Thanks for reading.

      Delete
    2. Your example in Lesson 5 helped me. Thank you very much for sharing :D

      Delete