/*
 *	AiffAudioOutputStream.java
 *
 *	This file is part of Tritonus: http://www.tritonus.org/
 */

/*
 *  Copyright (c) 2000 by Florian Bomers <http://www.bomers.de>
 *
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Library General Public License as published
 *   by the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/*
|<---            this code is formatted to fit into 80 columns             --->|
*/

package org.tritonus.sampled.file;

import java.io.IOException;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioSystem;
import org.tritonus.share.TDebug;
import org.tritonus.share.sampled.file.TAudioOutputStream;
import org.tritonus.share.sampled.file.TDataOutputStream;


/**
 * AudioOutputStream for AIFF and AIFF-C files.
 *
 * @author Florian Bomers
 */
public class AiffAudioOutputStream extends TAudioOutputStream {

	// this constant is used for chunk lengths when the length is not known yet
	private static final int LENGTH_NOT_KNOWN=-1;

	private AudioFileFormat.Type m_FileType;

	public AiffAudioOutputStream(AudioFormat audioFormat,
	                             AudioFileFormat.Type fileType,
	                             long lLength,
	                             TDataOutputStream dataOutputStream) {
		super(audioFormat,
		      lLength,
		      dataOutputStream,
		      lLength == AudioSystem.NOT_SPECIFIED
		      && dataOutputStream.supportsSeek());
		// AIFF files cannot exceed 2GB
		if (lLength != AudioSystem.NOT_SPECIFIED && lLength>0x7FFFFFFFl) {
			throw new IllegalArgumentException(
			    "AIFF files cannot be larger than 2GB.");
		}
		// IDEA: write AIFF file instead of AIFC when encoding=PCM ?
		m_FileType=fileType;
		if (!audioFormat.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED)
		        && !audioFormat.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED)) {
			// only AIFC files can handle non-pcm data
			m_FileType=AudioFileFormat.Type.AIFC;
		}
	}

	protected void writeHeader()
	throws IOException {
		if (TDebug.TraceAudioOutputStream) {
			TDebug.out("AiffAudioOutputStream.writeHeader(): called.");
		}
		AudioFormat format = getFormat();
		boolean	bIsAifc = m_FileType.equals(AudioFileFormat.Type.AIFC);
		long lLength = getLength();
		TDataOutputStream dos = getDataOutputStream();
		int nCommChunkSize=18;
		int nFormatCode=AiffTool.getFormatCode(format);
		if (bIsAifc) {
			// encoding takes 4 bytes
			// encoding name takes at minimum 2 bytes
			nCommChunkSize+=6;
		}
		int nHeaderSize=4          // magic
		                +8+nCommChunkSize  // COMM chunk
		                +8;                // header of SSND chunk
		if (bIsAifc) {
			// add length for FVER chunk
			nHeaderSize+=12;
		}
		// if patching the header, and the length has not been known at first
		// writing of the header, just truncate the size fields, don't throw an exception
		if (lLength != AudioSystem.NOT_SPECIFIED && lLength+nHeaderSize>0x7FFFFFFFl) {
			lLength=0x7FFFFFFFl-nHeaderSize;
		}
		// chunks must be on word-boundaries
		long lSSndChunkSize=(lLength!=AudioSystem.NOT_SPECIFIED)?
		                    (lLength+(lLength%2)+8):AudioSystem.NOT_SPECIFIED;

		// write IFF container chunk
		dos.writeInt(AiffTool.AIFF_FORM_MAGIC);
		dos.writeInt((lLength!=AudioSystem.NOT_SPECIFIED)?
		             ((int) (lSSndChunkSize+nHeaderSize)):LENGTH_NOT_KNOWN);
		if (bIsAifc) {
			dos.writeInt(AiffTool.AIFF_AIFC_MAGIC);
			// write FVER chunk
			dos.writeInt(AiffTool.AIFF_FVER_MAGIC);
			dos.writeInt(4);
			dos.writeInt(AiffTool.AIFF_FVER_TIME_STAMP);
		} else {
			dos.writeInt(AiffTool.AIFF_AIFF_MAGIC);
		}

		// write COMM chunk
		dos.writeInt(AiffTool.AIFF_COMM_MAGIC);
		dos.writeInt(nCommChunkSize);
		dos.writeShort((short) format.getChannels());
		dos.writeInt((lLength!=AudioSystem.NOT_SPECIFIED)?
		             ((int) (lLength / format.getFrameSize())):LENGTH_NOT_KNOWN);
		if (nFormatCode==AiffTool.AIFF_COMM_ULAW) {
			// AIFF ulaw states 16 bits for ulaw data
			dos.writeShort(16);
		} else {
			dos.writeShort((short) format.getSampleSizeInBits());
		}
		writeIeeeExtended(dos, format.getSampleRate());
		if (bIsAifc) {
			dos.writeInt(nFormatCode);
			dos.writeShort(0); // no encoding name
			// TODO: write encoding.toString() ??
		}

		// write header of SSND chunk



		dos.writeInt(AiffTool.AIFF_SSND_MAGIC);
		// don't use lSSndChunkSize here !
		dos.writeInt((lLength!=AudioSystem.NOT_SPECIFIED)
		             ?((int) (lLength+8)):LENGTH_NOT_KNOWN);
		// 8 information bytes of no interest
		dos.writeInt(0); // offset
		dos.writeInt(0); // blocksize
	}




	protected void patchHeader()
	throws IOException {
		TDataOutputStream	tdos = getDataOutputStream();
		tdos.seek(0);
		setLengthFromCalculatedLength();
		writeHeader();
	}

	public void close() throws IOException {
		long nBytesWritten=getCalculatedLength();

		if ((nBytesWritten % 2)==1) {
			if (TDebug.TraceAudioOutputStream) {
				TDebug.out("AiffOutputStream.close(): adding padding byte");
			}
			// extra byte for to align on word boundaries
			TDataOutputStream tdos = getDataOutputStream();
			tdos.writeByte(0);
			// DON'T adjust calculated length !
		}



		super.close();
	}

	public void writeIeeeExtended(TDataOutputStream	dos, float sampleRate) throws IOException {
		// currently, only integer sample rates are written
		// TODO: real conversion
		// I don't know exactly how much I have to shift left the mantisse for normalisation
		// now I do it so that there are any bits set in the first 5 bits
		int nSampleRate=(int) sampleRate;
		short ieeeExponent=0;
		while ((nSampleRate!=0) && (nSampleRate & 0x80000000)==0) {
			ieeeExponent++;
			nSampleRate<<=1;
		}
		dos.writeShort(16414-ieeeExponent); // exponent
		dos.writeInt(nSampleRate);          // mantisse high double word
		dos.writeInt(0);                    // mantisse low double word
	}




}

/*** AiffAudioOutputStream.java ***/
