// file: $ISIP_IFC/class/java/AudioSignal/AudioSignal.java
// version: $Id: AudioSignal.java 10227 2005-09-09 19:07:49Z stanley $

// import necessary java libraries
//
import java.io.*;
import javax.sound.sampled.*;

/**
 * A class describes a digital auido signal. It includes AudioFormat from
 * java definition and a matrix of float to hold the data.
 */
public class AudioSignal {

    //-------------------------------------------------------------------------
    //
    // public constants
    //
    //-------------------------------------------------------------------------


    //------------------------------------------------------------------------
    //
    // protected data
    //
    //-----------------------------------------------------------------------

    /**
     * Matrix of input signal data.
     */
    protected float[][] data_d;
    
    /**
     * The number of samples for each channel. We assume each channel
     * has the same number of samples right now although it is
     * possible we have a different length of samples for each channel
     * in the future.
     */
    protected  long num_samples_d;

    /**
     * This object is used to hold the format of the audio data such as
     * the encoding, sample size, sample rate, number of channels,
     * byte order, frame rate and frame size.
     */
    protected AudioFormat afmt_d;

    // declare all the audio parameters
    //

    /**
     * The audio encoding technique used by this format.
     */
    protected AudioFormat.Encoding encoding_d;
    
    /**
     * The number of samples played or recorded per second, for sounds
     * that have this format.
     */
    protected float sample_rate_d;

    /**
     * The number of bits in each sample of a sound that has this
     * format.
     */
    protected int sample_size_d;

    /**
     * The number of channels for this signal
     */
    protected int num_channels_d;

    /**
     * The number of bytes in each frame of a sound that has this
     * format.
     */
    protected int frame_size_d;

    /**
     * The number of frames played or recorded per second, for sounds
     * that have this format.
     */
    protected float frame_rate_d;

    /**
     * Indicates whether the audio data is stored in big-endian or
     * little-endian order.
     */
    protected boolean big_endian_d;

    /**
     * This flag is used to determine if the audio format is
     * updated with the new set value
     */
    protected boolean is_valid_d;
    
    //-------------------------------------------------------------------------
    //
    // public methods
    //
    //-------------------------------------------------------------------------
       
    /**
     * Constructor, use the default data format.
     */    
    public AudioSignal() {

	// construct a default format for speech signal
	// encoding - PCM_SIGNED
	// sample frequency = 16,000
	// sample size in bits = 16
	// number of channels = 1
	// frame size = 2
	// frame rate = 16,000
	// bigEndian? = false ---> little endian
	//
	encoding_d = AudioFormat.Encoding.PCM_SIGNED;
	sample_rate_d = 16000.9F;
	sample_size_d = 16;
	num_channels_d = 1;
	frame_size_d = 2;
	frame_rate_d = 16000.0F;
	big_endian_d = false;

	afmt_d =  new AudioFormat(encoding_d,
				  sample_rate_d,
				  sample_size_d,
				  num_channels_d,
				  frame_size_d,
				  frame_rate_d,
				  big_endian_d);
	is_valid_d = true;
    }

    /**
     * Alternative constructor, data format is specified.
     *
     * @param format_a the audio format for data in this signal.
     */
    public AudioSignal(AudioFormat format_a) {

	afmt_d = format_a;
	is_valid_d = true;
    }

    /**
     * Alternative constructor, both data and data format are
     * specified
     *
     * @param data_a the signal data
     * @param format_a the audio format for this data in this signal
     *
     */
    public AudioSignal(float[][] data_a, AudioFormat format_a) {

	int num_channels = afmt_d.getChannels();
	if (num_channels != data_a.length) {
	    System.out.println("The data channels and format channels are not matched");
	}

	data_d = data_a;
	afmt_d = format_a;
	if (data_d != null)
	    num_samples_d = data_d[0].length;

	is_valid_d = true;
    }

    //-------------------------------------------------------------------------
    //
    // class-specific public methods:
    //  set methods
    //
    //-------------------------------------------------------------------------

    /**
     * Set the data for this signal.
     *
     * @param data_a matrix of float. 
     *
     * @return a boolean value indicating status.
     */
    public boolean setData(float [][] data_a) {

	if (!is_valid_d)
	    afmt_d =  new AudioFormat(encoding_d,
				      sample_rate_d,
				      sample_size_d,
				      num_channels_d,
				      frame_size_d,
				      frame_rate_d,
				      big_endian_d);
	
	int num_channels = afmt_d.getChannels();
	if (num_channels != data_a.length) {
	    System.out.println("The channels from data and format is not matched");
	    System.out.println(" Number of channels from data = " + 
			       data_a.length);
	    System.out.println(" Number of channels from audio format = " 
			       + num_channels);
 
	    System.exit(1);
	}

	data_d = data_a;
	num_channels_d = data_d.length;
	num_samples_d = data_d[0].length;

	return true;
    }

    /**
     * Sets the format for the audio data that is to be play or record.
     *
     * @param format_a an AudioFormat object.
     *
     * @return a boolean value indicating status.
     */
    public boolean setFormat(AudioFormat format_a) {

	afmt_d = format_a;

	// update the local format variables
	//
	encoding_d = format_a.getEncoding();
	sample_rate_d = format_a.getSampleRate();
	sample_size_d = format_a.getSampleSizeInBits();
	num_channels_d = format_a.getChannels();
	frame_size_d = format_a.getFrameSize();
	frame_rate_d = format_a.getFrameRate();
	big_endian_d = format_a.isBigEndian();

	// exit gracefully
	//
	return true;
    }

    /**
     * Set the encoding (PCM_SIGNED, PCM_UNSIGNED, ULAW, ALAW).
     *
     * @param encoding_a a string object containing the encoding type.
     *
     * @return  a boolean value indicating status.
     */
    public boolean setEncoding(AudioFormat.Encoding encoding_a) {
	encoding_d = encoding_a;
	is_valid_d = false;
	return true;
    }
    
    /**
     * Set the sample size.
     *
     * @param sample_size_a an integer containing the sample size.
     *
     * @return a boolean value indicating status.
     */
    public boolean setSampleSize(int sample_size_a) {
	sample_size_d = sample_size_a;
	is_valid_d = false;
	return true;
    }
     
    /**
     * Set the sample rate.
     *
     * @param  sample_rate_a a float containing the sample rate.
     *
     * @return  a boolean value indicating status.
     */
    public boolean setSampleRate(float sample_rate_a) {
	sample_rate_d = sample_rate_a;
	is_valid_d = false;
	return true;
    }
    
    /**
     * Set the number of channels.
     *
     * @param num_channels_a an integer containing the number of
     * channels.
     *
     * @return a boolean value indicating status.
     */
    public boolean setNumChannels(int num_channels_a) {
	num_channels_d =num_channels_a;
	is_valid_d = false;
	return true;
    }
    
    /**
     * Set the byte order (BigEndian, LittleEndian).
     *
     * @param big_endian_a a boolean to represent if big-endian is
     * true or false.
     *
     * @return a boolean value indicating status.
     */    
    public boolean setBigEndian(boolean big_endian_a) {
	big_endian_d = big_endian_a;
	is_valid_d = false;
	return true;
    }
        
    /**
     * Set the frame size.
     *
     * @param frame_size_a an integer containing the frame size.
     *
     * @return a boolean value indicating status.
     */
    public boolean setFrameSize(int frame_size_a) {
	frame_size_d = frame_size_a;
	is_valid_d = false;
	return true;
    }
    
    /**
     * Set the frame rate.
     *
     * @param frame_rate_a an integer containing the frame rate.
     *
     * @return  a boolean value indicating status.
     */    
    public boolean setFrameRate(int frame_rate_a) {
	frame_rate_d = frame_rate_a;
	is_valid_d = false;
	return true;
    }

    //-------------------------------------------------------------------------
    //
    // class-specific public methods:
    //  get methods
    //
    //-------------------------------------------------------------------------

    /**
     * Get the data from this signal.
     *
     * @return the matrix of float including the data.
     */
    public float[] [] getData() {
	return data_d;
    }
    
    /**
     * Get the audio format for the audio data.
     *
     * @return an AudioFormat represents the audio format for the data.
     */
    public AudioFormat getFormat() {
	
	if (!is_valid_d) {
	    afmt_d =  new AudioFormat(encoding_d,
				      sample_rate_d,
				      sample_size_d,
				      num_channels_d,
				      frame_size_d,
				      frame_rate_d,
				      big_endian_d);
	}

	return afmt_d;
    }

    /**
     * Get the encoding.
     *
     * @return an AudioFormat.Encoding object represents the encoding
     * type.
     */    
    public AudioFormat.Encoding getEncoding() {	
	return afmt_d.getEncoding();
    }
    
    /**
     * Get the sample size.
     *
     * @return an int value represents the sample size.
     */    
    public int getSampleSize() {
	return afmt_d.getSampleSizeInBits();
    }
    
    /**
     * Get the sample rate.
     *
     * @return a float value represents the sample rate.
     */    
    public float getSampleRate() {
	return afmt_d.getSampleRate();
    }
    
    /**
     * Get the number of channels.
     *
     * @return an int value represents the number of channels.
     */    
    public int getNumChannels() {
	return afmt_d.getChannels();
    }
    
    /**
     * Get the flag to indicate if it is big endian.
     *
     * @return a boolean value to indicate whether it is big endian.
     */    
    public boolean getBigEndian() {
	return big_endian_d;
    }
    
    /**
     * Get the frame size.
     *
     * @return an int value represents the frame size.
     */    
    public int getFrameSize() {
	return afmt_d.getFrameSize();
    }

    /**
     * Get the frame rate.
     *
     * @return a float value represents the frame rate.
     */    
    public float getFrameRate() {
	return afmt_d.getFrameRate();
    }


    //-------------------------------------------------------------------------
    //
    // class-specific other public methods:
    //  
    //
    //-------------------------------------------------------------------------


    //------------------------------------------------------------------------
    //
    // class-specific public methods:
    //  error handle code
    //
    //------------------------------------------------------------------------

    /**
     * Do the error handling whenever an exception gets thrown. It
     * prints the exception and also prints the trace of methods from
     * where the error originated.
     *
     * @param e an object of exception class.
     *
     * @return a boolean value indicating status.
     */    
    public boolean errorHandler(Exception e) {
	
	// print the Exception that occurred
	//
	System.out.println("Exception in ISIPAudioInterface: " + e);
	
	// print the the trace of methods from where the error originated
	//
	e.printStackTrace();

	// exit gracefully
	//
	return true;
    }
    
    //-------------------------------------------------------------------------
    //
    // debug method for this class
    //
    //-------------------------------------------------------------------------

    /**
     * A method used to print out debug information for this class.
     *
     * @return a String represents this object.
     */
    public String toString() {

	// define a string
	//
	String result = "The object of AudioSignal:\n";

	// display the number of samples the signal has
	//
	result += "num_samples_d = " + num_samples_d + "\n";

	// display the audio format the signal has
	//
	result += "The audio format is:\n " + afmt_d + "\n";

	// display the data for this signal
	//
	result += "The audio data is:\n";

	for (int i = 0; i < num_channels_d; i++) {
	    for (int j = 0; j <num_samples_d; j++) {
		result += data_d[i][j] + " ";
	    }
	    result += "\n";
	}

	// return a string to represent this object
	//
	return result;
    }

    //------------------------------------------------------------------------
    //
    // private methods
    //
    //-----------------------------------------------------------------------


    //-------------------------------------------------------------------------
    //
    // main diagnostic method for this class
    //
    //-------------------------------------------------------------------------

    /**
     * Diagnostic method for this class.
     *
     * @param args arguments from the command line.
     */
    public static void main(String args[] ) {
	
	// define a AudioSignal object
	//
	AudioSignal a1 = new AudioSignal();
	
	// local variables
	//
	int num_channels = 2;
	int num_samples = 10;
	
	// construct two channels data, each channels has 9 samples
	//
	float [][] signal_1 = new float[num_channels][num_samples];
	
	// assign value to the signal
	//
	for (int i = 0; i < signal_1.length; i++) {
	    for (int j = 0; j < signal_1[0].length ; j++) {
		signal_1[i][j]= i * 10 + j;
	    }
	}
	
	// set the number of channels
	//
	a1.setNumChannels(2);
	
	// assign the signal to audio signal object
	//
	a1.setData(signal_1);
	
	// define another signal
	//
	float[][] signal_2;
	
	// get the signal from audio signal class
	//
	signal_2 = a1.getData();
	
	// print the signal value
	//
	System.out.println("signal_2 = ");
	for (int i = 0; i < signal_2.length; i++) {
	    for (int j = 0; j < signal_2[0].length; j++) {
		System.out.print(signal_2[i][j] + " ");
	    }
	    System.out.println();
	}
	
	// print the local signal
	//
	System.out.println("signal_1 = ");
	for (int i = 0; i < signal_1.length; i++) {
	    for (int j = 0; j < signal_1[0].length ; j++) {
		System.out.print(signal_1[i][j] + " ");
	    }
	    System.out.println();
	}
	
	// print the signal
	//
	System.out.println("AudioSignal a1:");
	System.out.println(a1);
	
	// redefine the channel and samples for the signal
	//
	num_channels = 1;
	num_samples = 15;
	
	// constructor another signal
	//
	float[][] signal_3 = new float[num_channels][num_samples];
	
	// assign value to the signal
	//
	for (int i = 0; i < signal_3.length; i++) {
	    for (int j = 0; j < signal_3[0].length ; j++) {
		signal_3[i][j]= i + j * 3;
	    }
	}
	
	// set the number of channels
	//
	a1.setNumChannels(num_channels);
	
	// assign the signal to audio signal object
	//
	a1.setData(signal_3);
	
	// print the audio signal
	//
	System.out.println("AudioSignal a1(2)");
	System.out.println(a1);	
    }

} // end of AudioSignal class
