/*
 * @(#)AudioFormat.java	1.40 02/08/21
 *
 * Copyright (c) 1996-2002 Sun Microsystems, Inc.  All rights reserved.
 */

package javax.media.format;

import javax.media.Format;

/**
 * Encapsulates format information for audio data.
 * The attributes  of an <code>AudioFormat</code> include the sample rate, 
 * bits per sample, and number of channels. 
 * @since JMF 2.0
 */

public class AudioFormat extends Format {

    public static final int BIG_ENDIAN = 1;
    public static final int LITTLE_ENDIAN = 0;

    public static final int SIGNED = 1;
    public static final int UNSIGNED = 0;
    
    protected double sampleRate = NOT_SPECIFIED;

    protected int sampleSizeInBits = NOT_SPECIFIED;

    protected int channels = NOT_SPECIFIED;

    protected int endian = NOT_SPECIFIED;

    protected int signed = NOT_SPECIFIED;

    protected double frameRate = NOT_SPECIFIED;

    protected int frameSizeInBits = NOT_SPECIFIED;


    // Standard audio encoding strings
    //    public static final String LINEAR = "linear";
    public static final String LINEAR = "LINEAR";

    // public static final String ULAW = "ulaw";
    public static final String ULAW = "ULAW";
    public static final String ULAW_RTP = "ULAW/rtp";

    public static final String ALAW = "alaw";

    public static final String IMA4 = "ima4";
    public static final String IMA4_MS = "ima4/ms";
    public static final String MSADPCM = "msadpcm";
    public static final String DVI = "dvi";
    public static final String DVI_RTP = "dvi/rtp";
    
    public static final String G723 = "g723";
    public static final String G723_RTP = "g723/rtp";
    
    public static final String G728 = "g728";
    public static final String G728_RTP = "g728/rtp";
    public static final String G729 = "g729";
    public static final String G729_RTP = "g729/rtp";
    public static final String G729A = "g729a";
    public static final String G729A_RTP = "g729a/rtp";

    public static final String GSM = "gsm";
    public static final String GSM_MS = "gsm/ms";
    public static final String GSM_RTP = "gsm/rtp";

    public static final String MAC3 = "MAC3";
    public static final String MAC6 = "MAC6";

    public static final String TRUESPEECH = "truespeech";
    public static final String MSNAUDIO = "msnaudio";
    public static final String MPEGLAYER3 = "mpeglayer3";
    public static final String VOXWAREAC8 = "voxwareac8";
    public static final String VOXWAREAC10 = "voxwareac10";
    public static final String VOXWAREAC16 = "voxwareac16";
    public static final String VOXWAREAC20 = "voxwareac20";
    public static final String VOXWAREMETAVOICE = "voxwaremetavoice";
    public static final String VOXWAREMETASOUND = "voxwaremetasound";
    public static final String VOXWARERT29H = "voxwarert29h";
    public static final String VOXWAREVR12 = "voxwarevr12";
    public static final String VOXWAREVR18= "voxwarevr18";
    public static final String VOXWARETQ40= "voxwaretq40";
    public static final String VOXWARETQ60 = "voxwaretq60";
    public static final String MSRT24 = "msrt24";
    
    public static final String MPEG = "mpegaudio";
    public static final String MPEG_RTP = "mpegaudio/rtp";
    public static final String DOLBYAC3 = "dolbyac3";

  /**
   * Constructs an <CODE>AudioFormat</CODE> with the specified encoding type. 
   * @param encoding  The audio encoding type.
   */

    public AudioFormat(String encoding) {
	super(encoding);
    }

  /**
   * Constructs an <CODE>AudioFormat</CODE> with the specified attributes.
   *  
   * @param encoding  A <CODE>String</CODE> that describes the encoding type 
   * for this  <CODE>AudioFormat</CODE>.
   * @param sampleRate  The sample rate.
   * @param sampleSizeInBits  The sample size in bits.
   * @param channels  The number of channels as an integer. 
   * For example, 1 for mono, 2 for stereo.
   */
    public AudioFormat(String encoding, double sampleRate,
		       int sampleSizeInBits, int channels) {
	this(encoding);
	this.sampleRate = sampleRate;
	this.sampleSizeInBits = sampleSizeInBits;
	this.channels = channels;
    }

  /**
   * Constructs an <CODE>AudioFormat</CODE>  with the specified attributes.
   * @param encoding   A <CODE>String</CODE> that describes the encoding 
   * type for this  <CODE>AudioFormat</CODE>.
   * @param sampleRate  The sample rate.
   * @param sampleSizeInBits  The sample size in bits.
   * @param channels  The number of channels.
   * @param endian  The sample byte ordering used for this 
   * <code>AudioFormat</code>--<CODE>BIG_ENDIAN</CODE> or <CODE>LITTLE_ENDIAN</CODE>.
   * @param signed  Indicates whether the samples are stored in a signed or unsigned 
   * format. Specify <CITE><CODE>true</CODE></CITE> 
   * if the <code>AudioFormat</code> is signed, <CODE>false</CODE> if the 
   * <code>AudioFormat</code> is unsigned. 
   */
    public AudioFormat(String encoding, double sampleRate,
		       int sampleSizeInBits, int channels,
		       int endian, int signed) {
	this(encoding, sampleRate, sampleSizeInBits, channels);
	this.endian = endian;
	this.signed = signed;
    }

  /**
   * Constructs an <CODE>AudioFormat</CODE> with the specified attributes.
   * @param encoding  A <CODE>String</CODE> that describes the encoding type 
   * for this  <CODE>AudioFormat</CODE>.
   * @param sampleRate  The sample rate.
   * @param sampleSizeInBits  The sample size.
   * @param channels  The number of channels.
   * @param endian The sample byte ordering used for this 
   * <code>AudioFormat</code>--<CODE>BIG_ENDIAN</CODE> or <CODE>LITTLE_ENDIAN</CODE>.
   * @param signed  Indicates whether the samples are stored in a signed or unsigned 
   * format. Specify <CITE><CODE>true</CODE></CITE> 
   * if the <code>AudioFormat</code> is signed, <CODE>false</CODE> if the 
   * <code>AudioFormat</code> is unsigned. 
   * @param frameSizeInBits  The frame size.
   * @param frameRate  The frame rate.
   * @param dataType  The type of the data. For example, byte array.
   */
    public AudioFormat(String encoding, double sampleRate,
		       int sampleSizeInBits, int channels,
		       int endian, int signed, 
		       int frameSizeInBits, double frameRate, Class dataType) {
	this(encoding, sampleRate, sampleSizeInBits, channels, endian, signed);
	this.frameSizeInBits = frameSizeInBits;
	this.frameRate = frameRate;
	this.dataType = dataType;
    }

    /**
     * Gets the audio sample rate.
     * @return The sample rate.
     */
    public double getSampleRate() {
	return sampleRate;
    }

    /**
     * Gets the size of a sample.
     * @return The sample size in bits.
     */
    public int getSampleSizeInBits() {
	return sampleSizeInBits;
    }

    /**
     * Gets the number of channels.
     * @return The number of channels as an integer.
     */
    public int getChannels() {
	return channels;
    }

    /**
     * Gets an integer that indicates whether the sample byte order is big endian 
     * or little endian.
     * @return The sample byte order of this <code>AudioFormat</code>, 
     * <CODE>BIG_ENDIAN</CODE> or <CODE>LITTLE_ENDIAN</CODE>.
     */
    public int getEndian() {
	return endian;
    }

    /**
     * Gets a boolean that indicates whether the samples are stored in signed format
     * or an unsigned format.
     * @return <CODE>SIGNED</CODE> if this <CODE>VideoFormat</CODE>  is signed, 
     * <CODE>UNSIGNED</CODE> if it is not.
     */
    public int getSigned() {
	return signed;
    }

    /**
     * Gets the frame size of this <code>AudioFormat</code>. This method is 
     * used primarily  for compressed audio.
     * @return The frame size of this <code>AudioFormat</code> in bits.
     */
    public int getFrameSizeInBits() {
	return frameSizeInBits;
    }

    /**
     * Gets the frame rate of this <code>AudioFormat</code>.
     * @return The frame rate.
     */
    public double getFrameRate() {
	return frameRate;
    }


    /**
     * For computing the duration of the sample.
     */
    double multiplier = -1f;
    int margin = 0;
    boolean init = false;


    /**
     * Returns the duration of the media based on the given length 
     * of the data.
     * @param length length of the data in this format.
     * @return the duration in nanoseconds computed from the length of 
     * the data in this format.  Returns -1 if the duration cannot be
     * computed.
     */
    public long computeDuration(long length) {

	if (init) {

	    // We don't know how to compute this format
	    if (multiplier < 0)
		return -1;

	    return (long)((length - margin) * multiplier) * 1000;
	}

	if (encoding == null) {

	    init = true;
	    return -1;

	} else if (encoding.equalsIgnoreCase(AudioFormat.LINEAR) ||
		   encoding.equalsIgnoreCase(AudioFormat.ULAW)) {

	    if (sampleSizeInBits > 0 && channels > 0 && sampleRate > 0)
		multiplier = (1000000 * 8)/sampleSizeInBits/channels/sampleRate;

	} else if (encoding.equalsIgnoreCase(AudioFormat.ULAW_RTP)) {

	    if (sampleSizeInBits > 0 && channels > 0 && sampleRate > 0)
		multiplier = (1000000 * 8)/sampleSizeInBits/channels/sampleRate;

	} else if (encoding.equalsIgnoreCase(AudioFormat.DVI_RTP)) {

	    if (sampleSizeInBits > 0 && sampleRate > 0)
		multiplier = (1000000 * 8)/sampleSizeInBits/sampleRate;
	    margin = 4;

        } else if (encoding.equalsIgnoreCase(AudioFormat.GSM_RTP)) {

	    if (sampleRate > 0)
		multiplier = (160 * 1000000 / 33) / sampleRate;

        } else if (encoding.equalsIgnoreCase(AudioFormat.G723_RTP)) {

	    if (sampleRate > 0)
		multiplier = (240/24 * 1000000) / sampleRate;

	} else if (frameSizeInBits != Format.NOT_SPECIFIED &&
		   frameRate != Format.NOT_SPECIFIED) {

	    // We don't know this codec, but we can compute the
	    // rate by using the frame rate and size.

	    if (frameSizeInBits > 0 && frameRate > 0)
		multiplier = (1000000 * 8)/frameSizeInBits/frameRate;
	}

	init = true;

	if (multiplier > 0)
	    return (long)((length - margin) * multiplier) * 1000;
	else
	    return -1;
    }


    /**
     * Gets a <CODE>String</CODE> representation of the attributes of this 
     * <code>AudioFormat</code>. For example: "PCM, 44.1 KHz, Stereo, Signed".
     * @return A <CODE>String</CODE> that describes the <code>AudioFormat</code> 
     * attributes.
     */
    public String toString() {
	String strChannels = "";
	String strEndian = "";
	
	if (channels == 1)
	    strChannels = ", Mono";
	else if (channels == 2)
	    strChannels = ", Stereo";
	else if (channels != NOT_SPECIFIED)
	    strChannels = ", " + channels + "-channel";

	if (sampleSizeInBits > 8) {
	    if (endian == BIG_ENDIAN)
		strEndian = ", BigEndian";
	    else if (endian == LITTLE_ENDIAN)
		strEndian = ", LittleEndian";
	}
	
	return getEncoding() + 
	    ( (sampleRate != NOT_SPECIFIED) ?  (", " + sampleRate + " Hz")
	                                    :  ", Unknown Sample Rate"  )+
	    ( (sampleSizeInBits != NOT_SPECIFIED) ?  (", " + sampleSizeInBits + "-bit")
	                                          :  ""  ) +
	    strChannels +
	    strEndian +
	    ( (signed != NOT_SPECIFIED) ?  ((signed == SIGNED ? ", Signed"
					                      : ", Unsigned"))
	                                :  ""  ) +
	    ( (frameRate != NOT_SPECIFIED) ?  (", " + frameRate + " frame rate")
	                                   :  ""  )+
	    ( (frameSizeInBits != NOT_SPECIFIED) ?  (", FrameSize=" + frameSizeInBits + " bits")
	                                         :  ""  )+
	    ( (dataType != Format.byteArray && dataType != null) ? ", " + dataType
	                                                         : "");
    }

    /**
     * Compares the specified <CODE>Format</CODE> with this <code>AudioFormat</code>. 
     * Returns <CODE>true</CODE> 
     * only if the specified <CODE>Format</CODE> is an <CODE>AudioFormat</CODE> and 
     * all of its attributes are
     * identical to this <code>AudioFormat</code>.
     * @param format  The <CODE>Format</CODE> to compare with this one.
     * @return <CODE>true</CODE> if the specified <CODE>Format</CODE> is the same, 
     * <CODE>false</CODE> if it is not.
     */
    public boolean equals(Object format) {
	if (format instanceof AudioFormat) {
	    AudioFormat other = (AudioFormat) format;
	    
	    return super.equals(format) && 
		sampleRate == other.sampleRate &&
		sampleSizeInBits == other.sampleSizeInBits &&
		channels == other.channels &&
		endian == other.endian &&
		signed == other.signed &&
		frameSizeInBits == other.frameSizeInBits &&
		frameRate == other.frameRate;
	}
	return false;
    }

    /**
     * Checks whether or not the specified <CODE>Format</CODE> <EM>matches</EM> 
     * this <CODE>AudioFormat</CODE>.
     * Matches only compares the attributes that are defined in the specified 
     * <CODE>Format</CODE>,  unspecified attributes are ignored.
     * <p>
     * The two <CODE>Format</CODE> objects do not have to be of the same class to 
     * match.  For example, if "A" are "B" are being compared, a
     * match is possible if "A" is derived from "B"
     * or "B" is derived from "A". (The compared attributes must still match, or 
     * <CODE>matches</CODE> fails.)  
     * @param format The <CODE>Format</CODE> to compare with this one.
     * @return <CODE>true</CODE> if the specified <CODE>Format</CODE> matches this one, 
     * <CODE>false</CODE> if it does not.
     */
    public boolean matches(Format format) {
	if (!super.matches(format))
	    return false;
	if (!(format instanceof AudioFormat))
	    return true;

	AudioFormat other = (AudioFormat) format;
	
	return
	    (sampleRate == NOT_SPECIFIED || other.sampleRate == NOT_SPECIFIED ||
	     sampleRate == other.sampleRate) &&
	    (sampleSizeInBits == NOT_SPECIFIED || other.sampleSizeInBits == NOT_SPECIFIED ||
	     sampleSizeInBits == other.sampleSizeInBits) &&
	    (channels == NOT_SPECIFIED || other.channels == NOT_SPECIFIED ||
	     channels == other.channels) &&
	    (endian == NOT_SPECIFIED || other.endian == NOT_SPECIFIED ||
	     endian == other.endian) &&
	    (signed == NOT_SPECIFIED || other.signed == NOT_SPECIFIED ||
	     signed == other.signed) &&
	    (frameSizeInBits == NOT_SPECIFIED || other.frameSizeInBits == NOT_SPECIFIED ||
	     frameSizeInBits == other.frameSizeInBits) &&
	    (frameRate == NOT_SPECIFIED || other.frameRate == NOT_SPECIFIED ||
	     frameRate == other.frameRate);
    }

    /**
     * Finds the attributes shared by two matching <CODE>Format</CODE> objects.
     * If the specified <CODE>Format</CODE> does not match this one, the result is
     * undefined.  
     * @param The matching <CODE>Format</CODE> to intersect with this 
     * <CODE>AudioFormat</CODE>.
     * @return A <CODE>Format</CODE> object
     * with its attributes set to those attributes common to both 
     * <CODE>Format</CODE> objects. 
     * @see #matches
      */
    public Format intersects(Format format) {
	Format fmt;
	if ((fmt = super.intersects(format)) == null)
	    return null;
	if (!(fmt instanceof AudioFormat))
	    return fmt;
	AudioFormat other = (AudioFormat)format;
	AudioFormat res = (AudioFormat)fmt;
	res.sampleRate = (sampleRate != NOT_SPECIFIED ?
			  sampleRate : other.sampleRate);
	res.sampleSizeInBits = (sampleSizeInBits != NOT_SPECIFIED ?
				sampleSizeInBits : other.sampleSizeInBits);
	res.channels = (channels != NOT_SPECIFIED ?
			channels : other.channels);
	res.endian = (endian != NOT_SPECIFIED ?
			 endian : other.endian);
	res.signed = (signed != NOT_SPECIFIED ?
		      signed : other.signed);
	res.frameSizeInBits = (frameSizeInBits != NOT_SPECIFIED ?
			       frameSizeInBits : other.frameSizeInBits);
	res.frameRate = (frameRate != NOT_SPECIFIED ?
			 frameRate : other.frameRate);
	return res;
    }

    /**
     * Creates a clone of this <code>AudioFormat</code> by copying each 
     * field to the clone.
     * @return A clone of this <code>AudioFormat</code>.
     */
    public Object clone() {
	AudioFormat f = new AudioFormat(encoding);
	f.copy(this);
	return f;
    }

    /**
     * Copies the attributes from the specified 
     * <CODE>Format</CODE> into this <CODE>AudioFormat</CODE>.
     * @param f The <CODE>Format</CODE> to copy the attributes from.
     */
    protected void copy(Format f) {
	super.copy(f);
	AudioFormat other = (AudioFormat) f;
	
	sampleRate = other.sampleRate;
	sampleSizeInBits = other.sampleSizeInBits;
	channels = other.channels;
	endian = other.endian;
	signed = other.signed;
	frameSizeInBits = other.frameSizeInBits;
	frameRate = other.frameRate;
    }

}

