package auditory.sampled;

import java.io.*;
import javax.sound.sampled.*;

import io.ResourceFinder;




/**
 * A utility class that can be used to create BufferedSound
 * objects in various ways.
 *
 * Notes: One millisecond is 1/1,000     of a second
 *        One microsecond is 1/1,000,000 of a second
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class BufferedSoundFactory
{
//[0signature.
    /**
     * Create a BufferedSound from a sine wave with a 
     * particular frequency
     *
     * The length of the sound is measured in microseconds
     * to be consistent with the Clip interface
     *
     * @param frequency   The frequency of the wave (in Hz)
     * @param length      The length of the sound (in microseconds)
     * @param sampleRate  The number of samples per second
     * @param amplitude   The maximum amplitude of the wave in [0.0, 32767.0]
     */
    public static BufferedSound createBufferedSound(
                                         double frequency,
                                         int    length,
                                         float  sampleRate,
                                         double amplitude)
//]0signature.
//[0open.
    {
//]0open.
//[0declarations.
       BufferedSound     sound;       
       double            radians,radiansPerSample, rmsValue;
       double[]          signal;
       
       int n;
//]0declarations.
//[0parta.       
       //samples =      samples/sec * sec
       n         = (int)(sampleRate * (double)length/1000000.0);
       
       signal    = new double[n];
//]0parta.
//[0partb.
       //  rads/sample  = ( rads/cycle * cycles/sec)/ samples/sec 
       radiansPerSample = (Math.PI*2.0 * frequency) / sampleRate;       
//]0partb.
//[0partc.
       for (int i=0; i<signal.length; i++)
       {
          // rad  =   rad/sample     * sample
          radians = radiansPerSample * i;

          signal[i] = amplitude * Math.sin(radians);
       }
//]0partc.
//[0partd.
       sound = new BufferedSound(sampleRate);
       sound.addChannel(signal);
       return sound;
//]0partd.
//[0close.
    }
//]0close.

//[3.
    /**
     * Create a BufferedSound from a resource/file
     *
     * @param name    The name of the resource
     */
    public static BufferedSound createBufferedSound(String name)
                                throws IOException, 
                                       UnsupportedAudioFileException
    {
       AudioInputStream        stream;
       InputStream             is;
       ResourceFinder          finder;
       
       finder = ResourceFinder.createInstance();
       
       is     = finder.findInputStream(name);

       stream = AudioSystem.getAudioInputStream(is);

       return createBufferedSound(stream);        
    }
//]3.
    
//[1signatgure.
    /**
     * Create a BufferedSound from an AudioInputStream
     *
     * @param stream    The stream to read from
     */
    public static BufferedSound createBufferedSound(AudioInputStream inStream)
                                throws IOException, 
                                       UnsupportedAudioFileException
//]1signatgure.
//[1open.
    {
//]1open.
//[1declarations.
       AudioFormat        inFormat, pcmFormat;
       AudioInputStream   pcmStream;
       BufferedSound      sound;       
       byte[]             rawBytes;       
       double[]           leftSignal, monoSignal, rightSignal;       
       int                bufferSize, sampleLength;
       int[]              signal;       
//]1declarations.
       
//[format.

       inFormat = inStream.getFormat();

	// Convert ULAW and ALAW to PCM
	if ((inFormat.getEncoding() == AudioFormat.Encoding.ULAW) ||
	    (inFormat.getEncoding() == AudioFormat.Encoding.ALAW)   ) {

	    pcmFormat = new AudioFormat(
                              AudioFormat.Encoding.PCM_SIGNED,
			      inFormat.getSampleRate(),
                              inFormat.getSampleSizeInBits()*2,
                              inFormat.getChannels(),
			      inFormat.getFrameSize()*2,
			      inFormat.getFrameRate(),
			      true);

	    pcmStream = AudioSystem.getAudioInputStream(pcmFormat, 
                                                        inStream);
	}
        else // It is PCM
        {
           pcmFormat = inFormat;           
           pcmStream = inStream;           
        }
//]format.
//[buffer.

        // Create a buffer and read the raw bytes
	bufferSize = (int)(pcmStream.getFrameLength()) 
                           * pcmFormat.getFrameSize();

	rawBytes = new byte[bufferSize];
	pcmStream.read(rawBytes);
//]buffer.
//[rawbytes0.

        // Convert the raw bytes
        if (pcmFormat.getSampleSizeInBits() == 8)
        {
           signal = processEightBitQuantization(rawBytes, pcmFormat);
        }
        else
        {
           signal = processSixteenBitQuantization(rawBytes, pcmFormat);
        }
//]rawbytes0.
//[channels.       
        sound = new BufferedSound(pcmFormat.getSampleRate());

        // Process the individual channels
        if (pcmFormat.getChannels() == 1)  // Mono
        {
           sampleLength = signal.length;
           monoSignal   = new double[sampleLength];           

           for (int i=0; i<sampleLength; i++)
           {
              monoSignal[i]  = signal[i]; // Convert to double
           }
           sound.addChannel(monoSignal);
        }
        else                               // Stereo
        {
           sampleLength = signal.length/2;
           leftSignal   = new double[sampleLength];           
           rightSignal  = new double[sampleLength];           

           for (int i=0; i<sampleLength; i++)
           {
              leftSignal[i]  = signal[2*i];
              rightSignal[i] = signal[2*i+1];
           }

           sound.addChannel(leftSignal);
           sound.addChannel(rightSignal);
        }
//]channels.

        
        return sound;        
//[1close.
    }
//]1close.

//[rawbytes1.

    /**
     * Convert the raw bytes for 8-bit samples
     *
     * @param rawBytes   The array of raw bytes
     * @param format     The AudioFormat
     */
    private static int[] processEightBitQuantization(
                                        byte[]      rawBytes,
                                        AudioFormat format)
    {
       int         lsb, msb;       
       int[]       signal;
       String      encoding;
       
       
       signal = new int[rawBytes.length];
       encoding = format.getEncoding().toString();

       if (encoding.startsWith("PCM_SIGN"))
       {
          for (int i=0; i<rawBytes.length; i++) 
             signal[i] = rawBytes[i];
       }
       else
       {
          for (int i=0; i<rawBytes.length; i++) 
             signal[i] = rawBytes[i]-128;
       }
       
       return signal;       
    }
//]rawbytes1.
    

//[2signature.

    /**
     * Convert the raw bytes for 16-bit samples
     *
     * @param rawBytes   The array of raw bytes
     * @param format     The AudioFormat
     */
    private static int[] processSixteenBitQuantization(
                                          byte[]      rawBytes,
                                          AudioFormat format)
//]2signature.
//[2open.
    {
//]2open.
//[2initializations.
       int         lsb, msb;       
       int[]       signal;

       signal = new int[rawBytes.length / 2];
//]2initializations.

//[2bigendian.       
       if (format.isBigEndian())  // Big-endian
       {

          for (int i=0; i<signal.length; i++)
          {
             // First byte is high-order byte
             msb = (int) rawBytes[2*i];

             // Second byte is low-order byte
             lsb = (int) rawBytes[2*i+1];

             signal[i] = msb << 8 | (255 & lsb);
          }
       } 
//]2bigendian.       
//[2littleendian.       
       else                       // Little-endian
       {
          for (int i=0; i<signal.length; i++)
          {
             // First byte is low-order byte
             lsb = (int) rawBytes[2*i];

             // Second byte is high-order byte
             msb = (int) rawBytes[2*i+1];

             signal[i] = msb << 8 | (255 & lsb);
          }
       }
//]2littleendian.       
//[2return.
       return signal;       
//]2return.
//[2close.
    }
//]2close.
}
