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

/*
 *  Copyright (c) 2000-2006 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.share.sampled;

import java.util.*;
import javax.sound.sampled.*;

/**
 * Utility functions for handling data in normalized float arrays. Each sample
 * is linear in the range of [-1.0f, +1.0f].
 * <p>
 * Currently, the following bit sizes are supported:
 * <ul>
 * <li>8-bit
 * <li>16-bit
 * <li>packed 24-bit (stored in 3 bytes)
 * <li>unpacked 24-bit (stored in 4 bytes)
 * <li>32-bit
 * </ul>
 * 8-bit data can be unsigned or signed. All other data is only supported in
 * signed encoding.
 * 
 * @see FloatSampleBuffer
 * @author Florian Bomers
 */

public class FloatSampleTools {

	/** default number of bits to be dithered: 0.7f */
	public static final float DEFAULT_DITHER_BITS = 0.7f;

	private static Random random = null;

	// sample width (must be in order !)
	static final int F_8 = 1;
	static final int F_16 = 2;
	static final int F_24_3 = 3;
	static final int F_24_4 = 4;
	static final int F_32 = 5;
	static final int F_SAMPLE_WIDTH_MASK = F_8 | F_16 | F_24_3 | F_24_4 | F_32;

	// format bit-flags
	static final int F_SIGNED = 8;
	static final int F_BIGENDIAN = 16;

	// supported formats
	static final int CT_8S = F_8 | F_SIGNED;
	static final int CT_8U = F_8;
	static final int CT_16SB = F_16 | F_SIGNED | F_BIGENDIAN;
	static final int CT_16SL = F_16 | F_SIGNED;
	static final int CT_24_3SB = F_24_3 | F_SIGNED | F_BIGENDIAN;
	static final int CT_24_3SL = F_24_3 | F_SIGNED;
	static final int CT_24_4SB = F_24_4 | F_SIGNED | F_BIGENDIAN;
	static final int CT_24_4SL = F_24_4 | F_SIGNED;
	static final int CT_32SB = F_32 | F_SIGNED | F_BIGENDIAN;
	static final int CT_32SL = F_32 | F_SIGNED;

	// ///////////////////////// initialization ////////////////////// //

	/** prevent instanciation */
	private FloatSampleTools() {
	}

	// /////////////// FORMAT / FORMAT TYPE /////////////////////////// //

	/**
	 * only allow "packed" samples -- currently no support for 18, 20 bits --
	 * except 24 bits stored in 4 bytes.
	 * 
	 * @throws IllegalArgumentException
	 */
	static void checkSupportedSampleSize(int ssib, int channels, int frameSize) {
		if (ssib == 24 && frameSize == 4 * channels) {
			// 24 bits stored in 4 bytes is OK (24_4)
			return;
		}
		if ((ssib * channels) != frameSize * 8) {
			throw new IllegalArgumentException("unsupported sample size: "
					+ ssib + " bits stored in " + (frameSize / channels)
					+ " bytes.");
		}
	}

	/**
	 * Get the formatType code from the given format.
	 * 
	 * @throws IllegalArgumentException
	 */
	static int getFormatType(AudioFormat format) {
		boolean signed = format.getEncoding().equals(
				AudioFormat.Encoding.PCM_SIGNED);
		if (!signed
				&& !format.getEncoding().equals(
						AudioFormat.Encoding.PCM_UNSIGNED)) {
			throw new IllegalArgumentException(
					"unsupported encoding: only PCM encoding supported.");
		}
		if (!signed && format.getSampleSizeInBits() != 8) {
			throw new IllegalArgumentException(
					"unsupported encoding: only 8-bit can be unsigned");
		}
		checkSupportedSampleSize(format.getSampleSizeInBits(),
				format.getChannels(), format.getFrameSize());

		int formatType = getFormatType(format.getSampleSizeInBits(),
				format.getFrameSize() / format.getChannels(), signed,
				format.isBigEndian());
		return formatType;
	}

	/**
	 * @throws IllegalArgumentException
	 */
	static int getFormatType(int ssib, int bytesPerSample, boolean signed,
			boolean bigEndian) {
		int res = 0;
		if (ssib == 24 || (bytesPerSample == ssib / 8)) {
			if (ssib == 8) {
				res = F_8;
			} else if (ssib == 16) {
				res = F_16;
			} else if (ssib == 24) {
				if (bytesPerSample == 3) {
					res = F_24_3;
				} else if (bytesPerSample == 4) {
					res = F_24_4;
				}
			} else if (ssib == 32) {
				res = F_32;
			}
		}
		if (res == 0) {
			throw new IllegalArgumentException(
					"ConversionTool: unsupported sample size of " + ssib
							+ " bits per sample in " + bytesPerSample
							+ " bytes.");
		}
		if (!signed && bytesPerSample > 1) {
			throw new IllegalArgumentException(
					"ConversionTool: unsigned samples larger than "
							+ "8 bit are not supported");
		}
		if (signed) {
			res |= F_SIGNED;
		}
		if (bigEndian && (ssib != 8)) {
			res |= F_BIGENDIAN;
		}
		return res;
	}

	static int getSampleSize(int formatType) {
		switch (formatType & F_SAMPLE_WIDTH_MASK) {
		case F_8:
			return 1;
		case F_16:
			return 2;
		case F_24_3:
			return 3;
		case F_24_4:
			return 4;
		case F_32:
			return 4;
		}
		return 0;
	}

	/**
	 * Return a string representation of this format
	 */
	static String formatType2Str(int formatType) {
		String res = "" + formatType + ": ";
		switch (formatType & F_SAMPLE_WIDTH_MASK) {
		case F_8:
			res += "8bit";
			break;
		case F_16:
			res += "16bit";
			break;
		case F_24_3:
			res += "24_3bit";
			break;
		case F_24_4:
			res += "24_4bit";
			break;
		case F_32:
			res += "32bit";
			break;
		}
		res += ((formatType & F_SIGNED) == F_SIGNED) ? " signed" : " unsigned";
		if ((formatType & F_SAMPLE_WIDTH_MASK) != F_8) {
			res += ((formatType & F_BIGENDIAN) == F_BIGENDIAN) ? " big endian"
					: " little endian";
		}
		return res;
	}

	// /////////////////// BYTE 2 FLOAT /////////////////////////////////// //

	private static final float twoPower7 = 128.0f;
	private static final float twoPower15 = 32768.0f;
	private static final float twoPower23 = 8388608.0f;
	private static final float twoPower31 = 2147483648.0f;

	private static final float invTwoPower7 = 1 / twoPower7;
	private static final float invTwoPower15 = 1 / twoPower15;
	private static final float invTwoPower23 = 1 / twoPower23;
	private static final float invTwoPower31 = 1 / twoPower31;

	/**
	 * @see #byte2float(byte[] input, int inByteOffset, Object[] output, int
	 *      outOffset, int frameCount, AudioFormat format, boolean
	 *      allowAddChannel)
	 */
	public static void byte2float(byte[] input, int inByteOffset,
			List<float[]> output, int outOffset, int frameCount,
			AudioFormat format) {

		byte2float(input, inByteOffset, output, outOffset, frameCount, format,
				true);
	}

	/**
	 * @param output an array of float[] arrays
	 * @throws ArrayIndexOutOfBoundsException if output does not
	 *             format.getChannels() elements
	 * @see #byte2float(byte[] input, int inByteOffset, Object[] output, int
	 *      outOffset, int frameCount, AudioFormat format, boolean
	 *      allowAddChannel)
	 */
	public static void byte2float(byte[] input, int inByteOffset,
			Object[] output, int outOffset, int frameCount, AudioFormat format) {

		byte2float(input, inByteOffset, output, outOffset, frameCount, format,
				true);
	}

	/**
	 * @param output an array of float[] arrays
	 * @param allowAddChannel if true, and output has fewer channels than
	 *            format, then only output.length channels are filled
	 * @throws ArrayIndexOutOfBoundsException if output does not
	 *             format.getChannels() elements
	 * @see #byte2float(byte[] input, int inByteOffset, Object[] output, int
	 *      outOffset, int frameCount, AudioFormat format, boolean
	 *      allowAddChannel)
	 */
	public static void byte2float(byte[] input, int inByteOffset,
			Object[] output, int outOffset, int frameCount, AudioFormat format,
			boolean allowAddChannel) {

		int channels = format.getChannels();
		if (!allowAddChannel && channels > output.length) {
			channels = output.length;
		}
		if (output.length < channels) {
			throw new ArrayIndexOutOfBoundsException(
					"too few channel output array");
		}
		for (int channel = 0; channel < channels; channel++) {
			float[] data = (float[]) output[channel];
			if (data.length < frameCount + outOffset) {
				data = new float[frameCount + outOffset];
				output[channel] = data;
			}

			byte2floatGeneric(input, inByteOffset, format.getFrameSize(), data,
					outOffset, frameCount, format);
			inByteOffset += format.getFrameSize() / format.getChannels();
		}
	}

	/**
	 * Conversion function to convert an interleaved byte array to a List of
	 * interleaved float arrays. The float arrays will contain normalized
	 * samples in the range [-1.0, +1.0]. The input array provides bytes in the
	 * format specified in <code>format</code>.
	 * <p>
	 * Only PCM formats are accepted. The method will convert all byte values
	 * from <code>input[inByteOffset]</code> to
	 * <code>input[inByteOffset + (frameCount * format.getFrameSize()) - 1]</code>
	 * to floats from <code>output(n)[outOffset]</code> to
	 * <code>output(n)[outOffset + frameCount - 1]</code>
	 * 
	 * @param input the audio data in an byte array
	 * @param inByteOffset index in input where to start the conversion
	 * @param output list of float[] arrays which receive the converted audio
	 *            data. if the list does not contain enough elements, or
	 *            individual float arrays are not large enough, they are
	 *            created.
	 * @param outOffset the start offset in <code>output</code>
	 * @param frameCount number of frames to be converted
	 * @param format the input format. Only packed PCM is allowed
	 * @param allowAddChannel if true, channels may be added to
	 *            <code>output</code> to match the number of input channels,
	 *            otherwise, only the first output.size() channels of input data
	 *            are converted.
	 * @throws IllegalArgumentException if one of the parameters is out of
	 *             bounds
	 * @see #byte2floatInterleaved(byte[],int,float[],int,int,AudioFormat)
	 */
	public static void byte2float(byte[] input, int inByteOffset,
			List<float[]> output, int outOffset, int frameCount,
			AudioFormat format, boolean allowAddChannel) {

		int channels = format.getChannels();
		if (!allowAddChannel && channels > output.size()) {
			channels = output.size();
		}
		for (int channel = 0; channel < channels; channel++) {
			float[] data;
			if (output.size() < channel) {
				data = new float[frameCount + outOffset];
				output.add(data);
			} else {
				data = output.get(channel);
				if (data.length < frameCount + outOffset) {
					data = new float[frameCount + outOffset];
					output.set(channel, data);
				}
			}

			byte2floatGeneric(input, inByteOffset, format.getFrameSize(), data,
					outOffset, frameCount, format);
			inByteOffset += format.getFrameSize() / format.getChannels();
		}
	}

	/**
	 * Conversion function to convert an interleaved byte array to an
	 * interleaved float array. The float array will contain normalized samples
	 * in the range [-1.0f, +1.0f]. The input array provides bytes in the format
	 * specified in <code>format</code>.
	 * <p>
	 * Only PCM formats are accepted. The method will convert all byte values
	 * from <code>input[inByteOffset]</code> to
	 * <code>input[inByteOffset + (frameCount * format.getFrameSize()) - 1]</code>
	 * to floats from <code>output[outOffset]</code> to
	 * <code>output[outOffset + (frameCount * format.getChannels()) - 1]</code>
	 * 
	 * @param input the audio data in an byte array
	 * @param inByteOffset index in input where to start the conversion
	 * @param output the float array that receives the converted audio data
	 * @param outOffset the start offset in <code>output</code>
	 * @param frameCount number of frames to be converted
	 * @param format the input format. Only packed PCM is allowed
	 * @throws IllegalArgumentException if one of the parameters is out of
	 *             bounds
	 * @see #byte2float(byte[],int,List,int,int,AudioFormat)
	 */
	public static void byte2floatInterleaved(byte[] input, int inByteOffset,
			float[] output, int outOffset, int frameCount, AudioFormat format) {

		byte2floatGeneric(input, inByteOffset, format.getFrameSize()
				/ format.getChannels(), output, outOffset, frameCount
				* format.getChannels(), format);
	}

	/**
	 * Generic conversion function to convert a byte array to a float array.
	 * <p>
	 * Only PCM formats are accepted. The method will convert all bytes from
	 * <code>input[inByteOffset]</code> to
	 * <code>input[inByteOffset + (sampleCount * (inByteStep - 1)]</code> to
	 * samples from <code>output[outOffset]</code> to
	 * <code>output[outOffset+sampleCount-1]</code>.
	 * <p>
	 * The <code>format</code>'s channel count is ignored.
	 * <p>
	 * For mono data, set <code>inByteOffset</code> to
	 * <code>format.getFrameSize()</code>.<br>
	 * For converting interleaved input data, multiply <code>sampleCount</code>
	 * by the number of channels and set inByteStep to
	 * <code>format.getFrameSize() / format.getChannels()</code>.
	 * 
	 * @param sampleCount number of samples to be written to output
	 * @param inByteStep how many bytes advance for each output sample in
	 *            <code>output</code>.
	 * @throws IllegalArgumentException if one of the parameters is out of
	 *             bounds
	 * @see #byte2floatInterleaved(byte[],int,float[],int,int,AudioFormat)
	 * @see #byte2float(byte[],int,List,int,int,AudioFormat)
	 */
	static void byte2floatGeneric(byte[] input, int inByteOffset,
			int inByteStep, float[] output, int outOffset, int sampleCount,
			AudioFormat format) {
		int formatType = getFormatType(format);

		byte2floatGeneric(input, inByteOffset, inByteStep, output, outOffset,
				sampleCount, formatType);
	}

	/**
	 * Central conversion function from a byte array to a normalized float
	 * array. In order to accomodate interleaved and non-interleaved samples,
	 * this method takes inByteStep as parameter which can be used to flexibly
	 * convert the data.
	 * <p>
	 * E.g.:<br>
	 * mono->mono: inByteStep=format.getFrameSize()<br>
	 * interleaved_stereo->interleaved_stereo:
	 * inByteStep=format.getFrameSize()/2, sampleCount*2<br>
	 * stereo->2 mono arrays:<br>
	 * ---inByteOffset=0, outOffset=0, inByteStep=format.getFrameSize()<br>
	 * ---inByteOffset=format.getFrameSize()/2, outOffset=1,
	 * inByteStep=format.getFrameSize()<br>
	 */
	static void byte2floatGeneric(byte[] input, int inByteOffset,
			int inByteStep, float[] output, int outOffset, int sampleCount,
			int formatType) {
		// if (TDebug.TraceAudioConverter) {
		// TDebug.out("FloatSampleTools.byte2floatGeneric, formatType="
		// +formatType2Str(formatType));
		// }
		int endCount = outOffset + sampleCount;
		int inIndex = inByteOffset;
		for (int outIndex = outOffset; outIndex < endCount; outIndex++, inIndex += inByteStep) {
			// do conversion
			switch (formatType) {
			case CT_8S:
				output[outIndex] = ((float) input[inIndex]) * invTwoPower7;
				break;
			case CT_8U:
				output[outIndex] = ((float) ((input[inIndex] & 0xFF) - 128))
						* invTwoPower7;
				break;
			case CT_16SB:
				output[outIndex] = ((float) ((input[inIndex] << 8) | (input[inIndex + 1] & 0xFF)))
						* invTwoPower15;
				break;
			case CT_16SL:
				output[outIndex] = ((float) ((input[inIndex + 1] << 8) | (input[inIndex] & 0xFF)))
						* invTwoPower15;
				break;
			case CT_24_3SB:
				output[outIndex] = ((float) ((input[inIndex] << 16)
						| ((input[inIndex + 1] & 0xFF) << 8) | (input[inIndex + 2] & 0xFF)))
						* invTwoPower23;
				break;
			case CT_24_3SL:
				output[outIndex] = ((float) ((input[inIndex + 2] << 16)
						| ((input[inIndex + 1] & 0xFF) << 8) | (input[inIndex] & 0xFF)))
						* invTwoPower23;
				break;
			case CT_24_4SB:
				output[outIndex] = ((float) ((input[inIndex + 1] << 16)
						| ((input[inIndex + 2] & 0xFF) << 8) | (input[inIndex + 3] & 0xFF)))
						* invTwoPower23;
				break;
			case CT_24_4SL:
				// TODO: verify the indexes
				output[outIndex] = ((float) ((input[inIndex + 3] << 16)
						| ((input[inIndex + 2] & 0xFF) << 8) | (input[inIndex + 1] & 0xFF)))
						* invTwoPower23;
				break;
			case CT_32SB:
				output[outIndex] = ((float) ((input[inIndex] << 24)
						| ((input[inIndex + 1] & 0xFF) << 16)
						| ((input[inIndex + 2] & 0xFF) << 8) | (input[inIndex + 3] & 0xFF)))
						* invTwoPower31;
				break;
			case CT_32SL:
				output[outIndex] = ((float) ((input[inIndex + 3] << 24)
						| ((input[inIndex + 2] & 0xFF) << 16)
						| ((input[inIndex + 1] & 0xFF) << 8) | (input[inIndex] & 0xFF)))
						* invTwoPower31;
				break;
			default:
				throw new IllegalArgumentException("unsupported format="
						+ formatType2Str(formatType));
			}
		}
	}

	// /////////////////// FLOAT 2 BYTE /////////////////////////////////// //

	private static byte quantize8(float sample, float ditherBits) {
		if (ditherBits != 0) {
			sample += random.nextFloat() * ditherBits;
		}
		if (sample >= 127.0f) {
			return (byte) 127;
		} else if (sample <= -128.0f) {
			return (byte) -128;
		} else {
			return (byte) (sample < 0 ? (sample - 0.5f) : (sample + 0.5f));
		}
	}

	private static int quantize16(float sample, float ditherBits) {
		if (ditherBits != 0) {
			sample += random.nextFloat() * ditherBits;
		}
		if (sample >= 32767.0f) {
			return 32767;
		} else if (sample <= -32768.0f) {
			return -32768;
		} else {
			return (int) (sample < 0 ? (sample - 0.5f) : (sample + 0.5f));
		}
	}

	private static int quantize24(float sample, float ditherBits) {
		if (ditherBits != 0) {
			sample += random.nextFloat() * ditherBits;
		}
		if (sample >= 8388607.0f) {
			return 8388607;
		} else if (sample <= -8388608.0f) {
			return -8388608;
		} else {
			return (int) (sample < 0 ? (sample - 0.5f) : (sample + 0.5f));
		}
	}

	private static int quantize32(float sample, float ditherBits) {
		if (ditherBits != 0) {
			sample += random.nextFloat() * ditherBits;
		}
		if (sample >= 2147483647.0f) {
			return 2147483647;
		} else if (sample <= -2147483648.0f) {
			return -2147483648;
		} else {
			return (int) (sample < 0 ? (sample - 0.5f) : (sample + 0.5f));
		}
	}

	/**
	 * Conversion function to convert a non-interleaved float audio data to an
	 * interleaved byte array. The float arrays contains normalized samples in
	 * the range [-1.0f, +1.0f]. The output array will receive bytes in the
	 * format specified in <code>format</code>. Exactly
	 * <code>format.getChannels()</code> channels are converted regardless of
	 * the number of elements in <code>input</code>. If <code>input</code>
	 * does not provide enough channels, an </code>IllegalArgumentException<code>
	 * is thrown.
	 * <p>
	 * Only PCM formats are accepted. The method will convert all samples from
	 * <code>input(n)[inOffset]</code> to
	 * <code>input(n)[inOffset + frameCount - 1]</code> to byte values from
	 * <code>output[outByteOffset]</code> to
	 * <code>output[outByteOffset + (frameCount * format.getFrameSize()) - 1]</code>
	 * <p>
	 * Dithering should be used when the output resolution is significantly
	 * lower than the original resolution. This includes if the original data
	 * was 16-bit and it is now converted to 8-bit, or if the data was generated
	 * in the float domain. No dithering need to be used if the original sample
	 * data was in e.g. 8-bit and the resulting output data has a higher
	 * resolution. If dithering is used, a sensitive value is
	 * DEFAULT_DITHER_BITS.
	 * 
	 * @param input a List of float arrays with the input audio data
	 * @param inOffset index in the input arrays where to start the conversion
	 * @param output the byte array that receives the converted audio data
	 * @param outByteOffset the start offset in <code>output</code>
	 * @param frameCount number of frames to be converted.
	 * @param format the output format. Only packed PCM is allowed
	 * @param ditherBits if 0, do not dither. Otherwise the number of bits to be
	 *            dithered
	 * @throws IllegalArgumentException if one of the parameters is out of
	 *             bounds
	 * @see #DEFAULT_DITHER_BITS
	 * @see #float2byteInterleaved(float[],int,byte[],int,int,AudioFormat,float)
	 */
	public static void float2byte(List<float[]> input, int inOffset,
			byte[] output, int outByteOffset, int frameCount,
			AudioFormat format, float ditherBits) {
		for (int channel = 0; channel < format.getChannels(); channel++) {
			float[] data = (float[]) input.get(channel);
			float2byteGeneric(data, inOffset, output, outByteOffset,
					format.getFrameSize(), frameCount, format, ditherBits);
			outByteOffset += format.getFrameSize() / format.getChannels();
		}
	}

	/**
	 * @param input an array of float[] arrays
	 * @throws ArrayIndexOutOfBoundsException if one of the parameters is out of
	 *             bounds
	 * @see #float2byte(List<float[]>[] input, int inOffset, byte[] output, int
	 *      outByteOffset, int frameCount, AudioFormat format, float ditherBits)
	 */
	public static void float2byte(Object[] input, int inOffset, byte[] output,
			int outByteOffset, int frameCount, AudioFormat format,
			float ditherBits) {
		int channels = format.getChannels();
		for (int channel = 0; channel < channels; channel++) {
			float[] data = (float[]) input[channel];
			float2byteGeneric(data, inOffset, output, outByteOffset,
					format.getFrameSize(), frameCount, format, ditherBits);
			outByteOffset += format.getFrameSize() / format.getChannels();
		}
	}

	/**
	 * @param input an array of float[] arrays
	 * @param channels how many channels to use from the input array
	 * @param frameSize only as optimization, the number of bytes per sample
	 *            frame
	 * @throws ArrayIndexOutOfBoundsException if one of the parameters is out of
	 *             bounds
	 * @see #float2byte(List<float[]>[] input, int inOffset, byte[] output, int
	 *      outByteOffset, int frameCount, AudioFormat format, float ditherBits)
	 */
	static void float2byte(Object[] input, int inOffset, byte[] output,
			int outByteOffset, int frameCount, int formatCode, int channels,
			int frameSize, float ditherBits) {
		int sampleSize = frameSize / channels;
		for (int channel = 0; channel < channels; channel++) {
			float[] data = (float[]) input[channel];
			float2byteGeneric(data, inOffset, output, outByteOffset, frameSize,
					frameCount, formatCode, ditherBits);
			outByteOffset += sampleSize;
		}
	}

	/**
	 * Conversion function to convert an interleaved float array to an
	 * interleaved byte array. The float array contains normalized samples in
	 * the range [-1.0f, +1.0f]. The output array will receive bytes in the
	 * format specified in <code>format</code>.
	 * <p>
	 * Only PCM formats are accepted. The method will convert all samples from
	 * <code>input[inOffset]</code> to
	 * <code>input[inOffset + (frameCount * format.getChannels()) - 1]</code>
	 * to byte values from <code>output[outByteOffset]</code> to
	 * <code>output[outByteOffset + (frameCount * format.getFrameSize()) - 1]</code>
	 * <p>
	 * Dithering should be used when the output resolution is significantly
	 * lower than the original resolution. This includes if the original data
	 * was 16-bit and it is now converted to 8-bit, or if the data was generated
	 * in the float domain. No dithering need to be used if the original sample
	 * data was in e.g. 8-bit and the resulting output data has a higher
	 * resolution. If dithering is used, a sensitive value is
	 * DEFAULT_DITHER_BITS.
	 * 
	 * @param input the audio data in normalized samples
	 * @param inOffset index in input where to start the conversion
	 * @param output the byte array that receives the converted audio data
	 * @param outByteOffset the start offset in <code>output</code>
	 * @param frameCount number of frames to be converted.
	 * @param format the output format. Only packed PCM is allowed
	 * @param ditherBits if 0, do not dither. Otherwise the number of bits to be
	 *            dithered
	 * @throws IllegalArgumentException if one of the parameters is out of
	 *             bounds
	 * @see #DEFAULT_DITHER_BITS
	 * @see #float2byte(List,int,byte[],int,int,AudioFormat,float)
	 */
	public static void float2byteInterleaved(float[] input, int inOffset,
			byte[] output, int outByteOffset, int frameCount,
			AudioFormat format, float ditherBits) {
		float2byteGeneric(input, inOffset, output, outByteOffset,
				format.getFrameSize() / format.getChannels(), frameCount
						* format.getChannels(), format, ditherBits);
	}

	/**
	 * Generic conversion function to convert a float array to a byte array.
	 * <p>
	 * Only PCM formats are accepted. The method will convert all samples from
	 * <code>input[inOffset]</code> to
	 * <code>input[inOffset+sampleCount-1]</code> to byte values from
	 * <code>output[outByteOffset]</code> to
	 * <code>output[outByteOffset + (sampleCount * (outByteStep - 1)]</code>.
	 * <p>
	 * The <code>format</code>'s channel count is ignored.
	 * <p>
	 * For mono data, set <code>outByteOffset</code> to
	 * <code>format.getFrameSize()</code>.<br>
	 * For converting interleaved input data, multiply <code>sampleCount</code>
	 * by the number of channels and set outByteStep to
	 * <code>format.getFrameSize() / format.getChannels()</code>.
	 * 
	 * @param sampleCount number of samples in input to be converted.
	 * @param outByteStep how many bytes advance for each input sample in
	 *            <code>input</code>.
	 * @throws IllegalArgumentException if one of the parameters is out of
	 *             bounds
	 * @see #float2byteInterleaved(float[],int,byte[],int,int,AudioFormat,float)
	 * @see #float2byte(List,int,byte[],int,int,AudioFormat,float)
	 */
	static void float2byteGeneric(float[] input, int inOffset, byte[] output,
			int outByteOffset, int outByteStep, int sampleCount,
			AudioFormat format, float ditherBits) {
		int formatType = getFormatType(format);

		float2byteGeneric(input, inOffset, output, outByteOffset, outByteStep,
				sampleCount, formatType, ditherBits);
	}

	/**
	 * Central conversion function from normalized float array to a byte array.
	 * In order to accomodate interleaved and non-interleaved samples, this
	 * method takes outByteStep as parameter which can be used to flexibly
	 * convert the data.
	 * <p>
	 * E.g.:<br>
	 * mono->mono: outByteStep=format.getFrameSize()<br>
	 * interleaved stereo->interleaved stereo:
	 * outByteStep=format.getFrameSize()/2, sampleCount*2<br>
	 * 2 mono arrays->stereo:<br>
	 * ---inOffset=0, outByteOffset=0, outByteStep=format.getFrameSize()<br>
	 * ---inOffset=1, outByteOffset=format.getFrameSize()/2,
	 * outByteStep=format.getFrameSize()<br>
	 */
	static void float2byteGeneric(float[] input, int inOffset, byte[] output,
			int outByteOffset, int outByteStep, int sampleCount,
			int formatType, float ditherBits) {
		// if (TDebug.TraceAudioConverter) {
		// TDebug.out("FloatSampleBuffer.float2byteGeneric, formatType="
		// +"formatType2Str(formatType));
		// }

		if (inOffset < 0 || inOffset + sampleCount > input.length
				|| sampleCount < 0) {
			throw new IllegalArgumentException("invalid input index: "
					+ "input.length=" + input.length + " inOffset=" + inOffset
					+ " sampleCount=" + sampleCount);
		}
		if (outByteOffset < 0
				|| outByteOffset + (sampleCount * outByteStep) >= (output.length + outByteStep)
				|| outByteStep < getSampleSize(formatType)) {
			throw new IllegalArgumentException("invalid output index: "
					+ "output.length=" + output.length + " outByteOffset="
					+ outByteOffset + " outByteStep=" + outByteStep
					+ " sampleCount=" + sampleCount + " format="
					+ formatType2Str(formatType));
		}

		if (ditherBits != 0.0f && random == null) {
			// create the random number generator for dithering
			random = new Random();
		}
		int endSample = inOffset + sampleCount;
		int iSample;
		int outIndex = outByteOffset;
		for (int inIndex = inOffset; inIndex < endSample; inIndex++, outIndex += outByteStep) {
			// do conversion
			switch (formatType) {
			case CT_8S:
				output[outIndex] = quantize8(input[inIndex] * twoPower7,
						ditherBits);
				break;
			case CT_8U:
				output[outIndex] = (byte) (quantize8(
						(input[inIndex] * twoPower7), ditherBits) + 128);
				break;
			case CT_16SB:
				iSample = quantize16(input[inIndex] * twoPower15, ditherBits);
				output[outIndex] = (byte) (iSample >> 8);
				output[outIndex + 1] = (byte) (iSample & 0xFF);
				break;
			case CT_16SL:
				iSample = quantize16(input[inIndex] * twoPower15, ditherBits);
				output[outIndex + 1] = (byte) (iSample >> 8);
				output[outIndex] = (byte) (iSample & 0xFF);
				break;
			case CT_24_3SB:
				iSample = quantize24(input[inIndex] * twoPower23, ditherBits);
				output[outIndex] = (byte) (iSample >> 16);
				output[outIndex + 1] = (byte) ((iSample >>> 8) & 0xFF);
				output[outIndex + 2] = (byte) (iSample & 0xFF);
				break;
			case CT_24_3SL:
				iSample = quantize24(input[inIndex] * twoPower23, ditherBits);
				output[outIndex + 2] = (byte) (iSample >> 16);
				output[outIndex + 1] = (byte) ((iSample >>> 8) & 0xFF);
				output[outIndex] = (byte) (iSample & 0xFF);
				break;
			case CT_24_4SB:
				// TODO: verify
				iSample = quantize24(input[inIndex] * twoPower23, ditherBits);
				output[outIndex + 0] = 0;
				output[outIndex + 1] = (byte) (iSample >> 16);
				output[outIndex + 2] = (byte) ((iSample >>> 8) & 0xFF);
				output[outIndex + 3] = (byte) (iSample & 0xFF);
				break;
			case CT_24_4SL:
				// TODO: verify
				iSample = quantize24(input[inIndex] * twoPower23, ditherBits);
				output[outIndex + 3] = (byte) (iSample >> 16);
				output[outIndex + 2] = (byte) ((iSample >>> 8) & 0xFF);
				output[outIndex + 1] = (byte) (iSample & 0xFF);
				output[outIndex + 0] = 0;
				break;
			case CT_32SB:
				iSample = quantize32(input[inIndex] * twoPower31, ditherBits);
				output[outIndex] = (byte) (iSample >> 24);
				output[outIndex + 1] = (byte) ((iSample >>> 16) & 0xFF);
				output[outIndex + 2] = (byte) ((iSample >>> 8) & 0xFF);
				output[outIndex + 3] = (byte) (iSample & 0xFF);
				break;
			case CT_32SL:
				iSample = quantize32(input[inIndex] * twoPower31, ditherBits);
				output[outIndex + 3] = (byte) (iSample >> 24);
				output[outIndex + 2] = (byte) ((iSample >>> 16) & 0xFF);
				output[outIndex + 1] = (byte) ((iSample >>> 8) & 0xFF);
				output[outIndex] = (byte) (iSample & 0xFF);
				break;
			default:
				throw new IllegalArgumentException("unsupported format="
						+ formatType2Str(formatType));
			}
		}
	}
}
